Production Debugging: от алерта до root cause за 5 минут
Production Debugging: от алерта до root cause за 5 минут
Цель урока
Научиться отлаживать реальные production проблемы используя distributed tracing. В этом уроке - 6 сценариев из реального опыта с пошаговым анализом.
Вы научитесь:
- Распознавать patterns проблем в Jaeger UI
- Находить root cause за минуты вместо часов
- Использовать trace spans для debugging
- Коррелировать traces с logs и metrics
Scenario 1: Retry Storm Detection
🔥 Алерт
[CRITICAL] P99 latency /api/checkout: 100ms → 5000ms (+4900%)
[WARNING] Error rate: 0.1% → 15% (+14900%)
[WARNING] Request rate: 1000 req/s → 5000 req/s (+400%)
Duration: Last 5 minutes📊 Что видим в Monitoring
Request rate spike - первый признак retry storm!
🔍 Trace Analysis в Jaeger
Открываем Jaeger:
- Service:
api-gateway - Operation:
POST /api/checkout - Lookback: Last 15 minutes
- Min Duration: 4000ms
Что видим:
Pattern: ОДИН span → МНОГО одинаковых child spans!
🎯 Root Cause
-
Click на один из error spans
-
Проверяем Tags:
http.status_code: 503 error: true error.type: ServiceUnavailableError error.message: "Payment service is down" -
Проверяем Logs/Events в span:
event: retry.attempt retry.count: 5 retry.delay: 0ms ← ⚠️ NO BACKOFF!
Проблема: Payment service временно недоступен → API Gateway retry БЕЗ backoff → amplification (1 request = 5 retries!)
✅ Fix
// ❌ BEFORE
const response = await fetch(paymentUrl, {
retry: {
count: 5,
delay: 0, // ← Проблема!
},
});
// ✅ AFTER
const response = await fetch(paymentUrl, {
retry: {
count: 3, // Меньше retries
delay: 1000, // 1s initial delay
backoff: "exponential", // 1s, 2s, 4s
maxDelay: 10000, // Max 10s
},
});✅ Verification в Jaeger
После fix:
Результат:
- ✅ Max 3 retries вместо 5
- ✅ Exponential backoff снижает load на payment service
- ✅ Request rate вернулся к норме: 1000 req/s
Scenario 2: N+1 Query Problem
🔥 Алерт
[WARNING] P99 latency /api/users/profile: 50ms → 800ms
[INFO] Database CPU: 30% → 85%🔍 Trace Analysis
Jaeger search:
- Operation:
GET /api/users/profile - Tags:
user.id=42
Trace visualization:
Pattern: МНОГО одинаковых DB queries подряд!
🎯 Root Cause
Click на spans: видим 50 SELECT queries для likes вместо одного!
-- ❌ N+1 queries (50 queries total)
SELECT * FROM posts WHERE user_id = 42; -- Returns 50 posts
-- Then for EACH post:
SELECT * FROM likes WHERE post_id = 1; -- 5ms
SELECT * FROM likes WHERE post_id = 2; -- 5ms
...
SELECT * FROM likes WHERE post_id = 50; -- 5ms
-- Total: 1 + 50 = 51 queries! ❌✅ Fix
// ❌ BEFORE - N+1 queries
const posts = await db.query("SELECT * FROM posts WHERE user_id = ?", [userId]);
for (const post of posts) {
post.likes = await db.query("SELECT * FROM likes WHERE post_id = ?", [
post.id,
]); // ← N queries!
}
// ✅ AFTER - Single query with JOIN
const posts = await db.query(
`SELECT posts.*,
COUNT(likes.id) as likes_count
FROM posts
LEFT JOIN likes ON likes.post_id = posts.id
WHERE posts.user_id = ?
GROUP BY posts.id`,
[userId]
);✅ Verification
Trace после fix:
Результат: 850ms → 55ms (~94% improvement!) 🎉
Scenario 3: Slow External API
🔥 Алерт
[WARNING] P99 latency /api/send-email: 200ms → 8000ms
[INFO] Timeout errors increasing🔍 Trace Analysis
🎯 Root Cause
Span tags показывают:
span: POST /v3/mail/send
http.url: https://api.sendgrid.com/v3/mail/send
http.status_code: 200
duration: 7850ms ← ⚠️ Slow external API!Проблема: SendGrid API медленный (их проблема, не наша!), но мы ждем синхронно.
✅ Fix: Async Processing
// ❌ BEFORE - Synchronous
app.post("/api/send-email", async (req, res) => {
await sendGridClient.send(email); // Ждем 8s!
res.json({ success: true });
});
// ✅ AFTER - Async with Queue
app.post("/api/send-email", async (req, res) => {
// Публикуем в очередь
await queue.publish("email.send", email); // ~5ms
res.json({ success: true, queued: true });
// User получает ответ сразу!
});
// Background worker обрабатывает очередь
worker.process("email.send", async (job) => {
await sendGridClient.send(job.data); // Может быть медленным, но не блокирует API
});✅ Verification
Trace после fix:
Результат: API latency 8000ms → 15ms! 🚀
Scenario 4: Circuit Breaker Failure
🔥 Алерт
[CRITICAL] Error rate /api/orders: 0% → 98%
[CRITICAL] Downstream service "inventory-service" unavailable🔍 Trace Pattern
Pattern: Каждый request ждет 30s timeout!
🎯 Root Cause
Span tags:
error.type: ETIMEDOUT
http.status_code: null
retry.count: 0
circuit_breaker.enabled: false ← ⚠️ Проблема!Проблема: Нет circuit breaker → каждый request ждет full timeout (30s)!
✅ Fix: Add Circuit Breaker
const CircuitBreaker = require("opossum");
// ❌ BEFORE
async function getInventory(productId) {
return await fetch(`http://inventory-service/products/${productId}`);
// Если сервис down → ждем 30s timeout каждый раз
}
// ✅ AFTER
const inventoryBreaker = new CircuitBreaker(
async (productId) => {
return await fetch(`http://inventory-service/products/${productId}`, {
timeout: 2000, // 2s timeout
});
},
{
timeout: 2000,
errorThresholdPercentage: 50, // Open при 50% errors
resetTimeout: 30000, // Retry после 30s
}
);
async function getInventory(productId) {
try {
return await inventoryBreaker.fire(productId);
} catch (err) {
if (err.code === "EOPENBREAKER") {
// Circuit open - fail fast!
throw new Error("Inventory service unavailable");
}
throw err;
}
}✅ Verification
Trace с circuit breaker:
Span tags:
circuit_breaker.state: OPEN
circuit_breaker.failures: 15
error.message: "Inventory service unavailable (circuit open)"Результат:
- ✅ Latency: 30s → 50ms (fail fast!)
- ✅ Сервис остается responsive
- ✅ Auto-recovery после 30s (circuit half-open)
Scenario 5: Cascading Failure
🔥 Алерт
[CRITICAL] Multiple services failing simultaneously:
- auth-service: 95% error rate
- user-service: 90% error rate
- order-service: 85% error rate
- payment-service: 50% error rate🔍 Service Dependency Graph
Pattern: Множество сервисов падают → ищем ОБЩУЮ dependency!
🎯 Root Cause via Traces
Открываем traces для КАЖДОГО failing service:
Auth Service trace:
span: getCachedUser
error.type: ECONNREFUSED
error.message: "Connection refused: redis:6379"User Service trace:
span: cacheUserProfile
error.type: ECONNREFUSED
error.message: "Connection refused: redis:6379"Order Service trace:
span: getCachedCart
error.type: ECONNREFUSED
error.message: "Connection refused: redis:6379"Common pattern: ALL failing на Redis!
✅ Fix
# Проверка Redis
kubectl get pods | grep redis
# redis-xxx 0/1 CrashLoopBackOff
# Check logs
kubectl logs redis-xxx
# Error: MISCONF Redis is configured to save RDB snapshots,
# but it is currently not able to persist on disk
# Причина: Disk full!
df -h
# /dev/sda1 100% ← Problem!
# Fix: Очистить место
rm /var/log/old-logs/*
kubectl delete pod redis-xxx # Restart✅ Verification
После fix Redis - все сервисы восстановились:
Lesson: Traces показали ОБЩУЮ точку отказа! ✨
Scenario 6: Memory Leak Detection
🔥 Алерт
[WARNING] Latency increasing linearly over time:
- Hour 1: P99 = 100ms
- Hour 2: P99 = 200ms
- Hour 3: P99 = 400ms
- Hour 4: P99 = 800ms (current)🔍 Correlation: Traces + Metrics
Pattern: Latency растет ≈ Memory растет → Memory leak!
🎯 Root Cause
Profiling + Traces:
// Span attributes показывают leak source:
span.setAttribute("cache.size", cacheMap.size);
// В Jaeger:
Hour 1: cache.size = 1000
Hour 2: cache.size = 5000
Hour 3: cache.size = 15000
Hour 4: cache.size = 50000 ← ⚠️ Растет!Код:
// ❌ BEFORE - Memory leak
const cache = new Map(); // Unbounded cache!
app.get("/users/:id", async (req, res) => {
let user = cache.get(req.params.id);
if (!user) {
user = await db.getUser(req.params.id);
cache.set(req.params.id, user); // ← Никогда не удаляется!
}
res.json(user);
});✅ Fix: LRU Cache with TTL
const LRU = require("lru-cache");
// ✅ AFTER - Bounded cache
const cache = new LRU({
max: 1000, // Max 1000 entries
ttl: 1000 * 60 * 5, // 5 min TTL
});
app.get("/users/:id", async (req, res) => {
let user = cache.get(req.params.id);
if (!user) {
user = await db.getUser(req.params.id);
cache.set(req.params.id, user);
}
// Track cache metrics in spans
const span = trace.getActiveSpan();
span.setAttribute("cache.size", cache.size);
span.setAttribute("cache.max", cache.max);
res.json(user);
});✅ Verification
Metrics после fix:
Hour 5: process_resident_memory_bytes = 250MB ✅
Hour 6: process_resident_memory_bytes = 260MB ✅
Hour 7: process_resident_memory_bytes = 255MB ✅
cache.size = 1000 (stable) ✅Best Practices для Production Debugging
1. Добавляйте Context в Spans
span.setAttribute("user.id", userId);
span.setAttribute("tenant.id", tenantId);
span.setAttribute("cart.items_count", cart.items.length);
span.setAttribute("cache.hit", cacheHit);
span.setAttribute("retry.attempt", retryCount);Почему: Легче фильтровать и находить проблемные traces!
2. Используйте Span Events для Milestones
span.addEvent("cache.miss");
span.addEvent("retry.started", { attempt: 2 });
span.addEvent("circuit_breaker.opened");3. Коррелируйте Traces с Logs
const traceId = span.spanContext().traceId;
logger.error("Payment failed", {
traceId, // ⭐ Критично для корреляции!
userId,
error: err.message,
});В Grafana Loki:
{service="payment-service"} |= "traceId=abc123"→ Клик на trace ID → Opens Jaeger!
4. Мониторьте Key Metrics в Spans
span.setAttribute("db.query_time_ms", queryTime);
span.setAttribute("cache.hit_rate", hitRate);
span.setAttribute("queue.lag_seconds", queueLag);5. Используйте Jaeger Advanced Queries
# Найти все slow traces
duration>1s
# Найти traces с errors для specific user
error=true user.id=42
# Найти traces с high retry count
retry.count>3
# Комбинации
duration>500ms error=true http.status_code=503Практическое задание
Вам дан production trace с проблемой. Найдите root cause!
Scenario: API endpoint /api/dashboard медленный (P99 = 3s)
Trace structure:
GET /api/dashboard [3.2s]
├─ getUser [150ms]
│ └─ SELECT users [15ms]
├─ getUserPosts [2.8s]
│ ├─ SELECT posts [20ms]
│ └─ enrichPosts [2.7s]
│ ├─ getComments (post_id=1) [50ms]
│ ├─ getComments (post_id=2) [50ms]
│ ├─ ... [50ms × 48 more]
└─ getUserStats [200ms]Вопросы:
- Какая проблема? (N+1 query / Retry storm / Slow API / Memory leak?)
- Где bottleneck?
- Как бы вы исправили?
Ответ: N+1 query problem! 50 getComments calls вместо одного batch query. Fix: JOIN или IN query для всех post_ids сразу.
Что дальше
Поздравляем! Вы прошли весь курс по distributed tracing - от основ до production debugging! 🎉
Теперь вы можете:
- ✅ Инструментировать приложения (HTTP, gRPC, Kafka, RabbitMQ)
- ✅ Настраивать infrastructure (Jaeger, OTel Collector, Istio)
- ✅ Отлаживать production проблемы через traces
- ✅ Использовать best practices для observability
Вы теперь эксперт по distributed tracing! Применяйте эти знания для улучшения reliability и observability ваших систем.
Дополнительные материалы
🎉 Congratulations on completing the course!
Share your experience and get a promo code for free access to any premium material of your choice
Free access to any premium material of your choice
Your experience will help other students
Promo code will arrive via email within 24 hours