Saltar al contenido

Depuración de incidentes en producción: un enfoque sistemático

Un marco probado para diagnosticar y resolver incidentes en producción rápido, desde la primera alerta hasta la causa raíz y la prevención.

4 min de lectura
Flujo de respuesta a incidentes desde la supervisión y la alerta, pasando por la investigación y la mitigación, hasta una post-mortem, a través de líneas de registro y servidores en rack.

Cuando se rompe la producción

Los incidentes en producción son situaciones de alta presión y poco tiempo que ponen a prueba cuán preparados están tu equipo y tus sistemas. La diferencia entre una recuperación de 5 minutos y una caída de 3 horas suele ser la preparación y el proceso, no el talento individual.

Este es el marco que uso.

El ciclo OODA para incidentes

El ciclo OODA del estratega militar John Boyd se adapta bien a la respuesta de incidentes:

Observar → Orientarse → Decidir → Actuar → repetir

Nunca te saltes pasos. El error más común en los incidentes es saltar a Actuar sin Observar: hacer cambios basados en suposiciones en lugar de evidencia, lo que puede empeorar las cosas.

Fase 1: Observar — Recopilar señal

Primeros 5 minutos: entender el alcance y el impacto.

shbash
# What is actually broken?
# Check error rates across services
curl "https://metrics.internal/api/query?q=rate(http_errors_total[5m])"
 
# Which users are affected?
# Check if it's a subset (region, plan, feature flag cohort)
SELECT
  COUNT(*) as affected_users,
  COUNT(CASE WHEN region = 'us-east' THEN 1 END) as us_east,
  COUNT(CASE WHEN plan = 'free' THEN 1 END) as free_tier
FROM error_events
WHERE created_at > NOW() - INTERVAL '10 minutes';
 
# When did it start?
# Compare current error rate to baseline
SELECT
  date_trunc('minute', created_at) as minute,
  COUNT(*) as errors
FROM error_events
WHERE created_at > NOW() - INTERVAL '30 minutes'
GROUP BY 1
ORDER BY 1;

Preguntas clave:

  • ¿Cuál es la tasa de error? (3% vs 100% cambia la respuesta)
  • ¿Qué servicio/endpoint se ve afectado?
  • ¿Cuándo empezó?
  • ¿Se desplegó algo alrededor de esa hora?
  • ¿Está mejorando o empeorando?

Fase 2: Orientarse — Formar hipótesis

Correlaciona tus observaciones con cambios recientes. La causa más probable de un incidente en producción es un cambio reciente.

shbash
# Check recent deployments
git log --oneline --since="2 hours ago" --all
# → b43224f feat: add fetchDataVersion function  [30 min ago]
 
# Check infrastructure changes
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=UpdateFunctionCode \
  --start-time "2 hours ago"
 
# Check database query performance
SELECT
  query,
  mean_exec_time,
  calls,
  total_exec_time
FROM pg_stat_statements
WHERE mean_exec_time > 1000  -- Queries slower than 1 second
ORDER BY mean_exec_time DESC
LIMIT 10;

Forma una lista ordenada de hipótesis:

  1. Lo más probable: el despliegue de hace 30 minutos
  2. Posible: consulta a la base de datos repentinamente lenta
  3. Menos probable: degradación de API de terceros

Fase 3: Decidir — Mitigar vs corregir

Elige la acción correcta según la situación:

Rollback — el más rápido cuando la causa es un despliegue reciente y el rollback es seguro. Preferir esto cuando los usuarios están impactados activamente.

Feature flag off — para incidentes causados por una funcionalidad específica. Instantáneo y sin necesidad de despliegue.

Scale up — para incidentes relacionados con capacidad (alta carga, presión de memoria).

Fix forward — solo cuando el rollback no es posible y entiendes la causa raíz.

shbash
# Rollback a Kubernetes deployment
kubectl rollout undo deployment/api-server
 
# Disable a feature flag (example with Vercel Edge Config)
curl -X PATCH https://api.vercel.com/v1/edge-config/{id}/items \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"items": [{"operation": "update", "key": "features.new-checkout", "value": {"enabled": false}}]}'
 
# Scale up if the issue is capacity
kubectl scale deployment/api-server --replicas=10

El sesgo hacia la acción: durante un incidente activo, una mitigación imperfecta ahora supera una corrección perfecta en 30 minutos. Primero detén la hemorragia.

Fase 4: Verificar y monitorear

Después de aplicar la mitigación, confirma la recuperación antes de dar por terminado.

tstypescript
// Monitor key metrics for 10-15 minutes after mitigation
const metricsToWatch = [
  "http.error_rate", // Should return to baseline
  "db.query_duration_p95", // Should drop if DB was the cause
  "api.response_time_p99", // Should recover
  "active_user_sessions", // Should stop declining
];
 
// Set up a dashboard view filtered to last 30 minutes
// Watch for the inflection point where the incident started to resolve

No declares la victoria demasiado pronto. Algunos problemas tardan 5-10 minutos en propagarse por el sistema después de una corrección.

Post-incidente: La post-mortem sin culpas

Dentro de las 48 horas, escribe una post-mortem. El objetivo no es asignar culpas, sino prevenir que se repita.

markdownmarkdown
## Incident: API 500 Errors — 2026-03-15 14:30 UTC
 
### Summary
 
For 23 minutes, 8% of API requests returned 500 errors, affecting ~1,200 users.
The cause was a missing database index added in deployment v2.4.1, causing
query timeouts under normal load.
 
### Timeline
 
- 14:30 — Deployment v2.4.1 rolled out to production
- 14:38 — Alert: error rate exceeded 5% threshold
- 14:41 — On-call acknowledged, began investigation
- 14:47 — Identified slow query in pg_stat_statements
- 14:52 — Added missing index with CREATE INDEX CONCURRENTLY
- 14:53 — Error rate returned to baseline
 
### Root Cause
 
The migration added a `user_id` filter to a query that previously had no
user scope. Without an index on `user_id`, this query did a full table scan
on 8M rows under load.
 
### Why It Reached Production
 
- The migration was tested on a dev database with ~500 rows
- Query performance wasn't tested under load
- No slow query monitoring was in place for new migrations
 
### Action Items
 
- [ ] Add EXPLAIN ANALYZE to migration PR checklist
- [ ] Set up pg_stat_statements alerts for new slow queries
- [ ] Create load test suite that runs against staging with production-scale data

Los mejores equipos tratan las post-mortems como herramientas de aprendizaje, no como castigo. "¿Cómo permitió nuestro sistema esto?" en lugar de "¿quién causó esto?"

Observabilidad: Investiga antes de necesitarla

El peor momento para configurar logging y monitoreo es durante un incidente.

tstypescript
// Structured logging — make logs queryable
import pino from "pino";
 
const logger = pino({
  level: process.env.LOG_LEVEL ?? "info",
  formatters: {
    level: (label) => ({ level: label }),
  },
});
 
// Every important operation logs with context
async function processOrder(orderId: string, userId: string) {
  const start = Date.now();
 
  logger.info({ orderId, userId, event: "order.process.start" });
 
  try {
    const result = await fulfillOrder(orderId);
    logger.info({
      orderId,
      userId,
      event: "order.process.success",
      durationMs: Date.now() - start,
    });
    return result;
  } catch (error) {
    logger.error({
      orderId,
      userId,
      event: "order.process.error",
      error: error.message,
      durationMs: Date.now() - start,
    });
    throw error;
  }
}

Si no puedes responder "¿qué estaba haciendo el sistema 30 segundos antes del incidente?" a partir de tus logs, necesitas mejor observabilidad.

La producción siempre te sorprenderá. La pregunta es si serás capaz de entender qué pasó.

Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX