Tracing Distribuido

Infrastructure & Governance Security Notes Jan 8, 2026 TYPESCRIPT
observability distributed-systems debugging performance microservices

Definition

Cuando haces clic en “Comprar Ahora” en un sitio de e-commerce, ese único clic podría disparar llamadas a docenas de servicios: carrito de compras, inventario, precios, detección de fraude, procesamiento de pagos, cálculo de envío, notificación por email. Si el checkout toma 10 segundos en lugar de 2, ¿qué servicio es el cuello de botella? Si falla, ¿qué servicio causó el fallo? Sin tracing distribuido, responder estas preguntas requiere trabajo detectivesco heroico a través de múltiples sistemas de logs.

El tracing distribuido resuelve esto siguiendo el viaje completo de una petición a través de tu sistema. Crea una traza - un árbol de spans donde cada span representa una operación (una llamada API, una consulta de base de datos, una operación de cola de mensajes). Cada span registra cuándo empezó, cuándo terminó, qué servicio lo manejó, y cualquier metadato relevante. La magia es que todos los spans en una petición comparten un trace ID común, así que puedes reconstruir el viaje entero de principio a fin.

Piénsalo como un sistema de rastreo de paquetes que muestra no solo “tu paquete llegó” sino cada almacén por el que pasó, cuánto tiempo pasó en cada uno, y qué camión lo llevó entre ubicaciones. El tracing responde “¿por qué esto está lento?” y “¿qué llamó a qué?” de maneras que logs y métricas solos no pueden. Cuando se combina con correlation IDs propagados a través de headers, las trazas se vuelven una herramienta esencial de debugging para microservicios.

Example

Debugging Checkout Lento en Shopify: Un cliente reporta que el checkout toma 8 segundos. El tracing muestra: API Gateway (50ms) → Cart Service (100ms) → Inventory Service (6000ms!) → Payment Service (500ms) → Email Service (200ms). La traza inmediatamente identifica inventario como el cuello de botella - está esperando una consulta de base de datos lenta visible como un span hijo.

Flujo de Petición en Netflix: Cuando presionas play, la traza de Netflix podría mostrar: Edge Service → API Gateway → User Service → Entitlement Service → Steering Service → CDN Selection → Playback Service. Cada span muestra timing, y la visualización revela que la selección de CDN tomó 2 segundos en una región específica debido a un health check fallando.

Petición de Viaje en Uber: Rastrear una petición de viaje muestra: App → API Gateway → Driver Matching → ETA Calculation → Pricing → Payment Authorization → Driver Notification. Si el matching es lento, la traza muestra exactamente qué etapa del algoritmo de matching es el problema y cuántos candidatos de conductor fueron evaluados.

Detección de Cold Start en AWS Lambda: Las trazas revelan que la primera invocación de una función Lambda toma 3 segundos (cold start) mientras llamadas subsecuentes toman 100ms. La traza muestra tiempo de inicialización separadamente del tiempo de ejecución, ayudando a los ingenieros a optimizar rendimiento de cold start.

Detección de Consultas N+1 en Base de Datos: Una traza para un endpoint de API muestra 100 spans hijos, cada uno una consulta de base de datos tomando 10ms. El span padre totaliza 1 segundo. La visualización de traza inmediatamente muestra el problema N+1 - 100 consultas secuenciales que deberían ser una consulta batch.

Analogy

El Sistema de Rastreo de Paquetes: Cuando ordenas de Amazon, puedes rastrear tu paquete a través de cada instalación: “Recogido en almacén de Los Angeles a las 10 AM, llegó al hub de Phoenix a las 2 PM, salió para entrega a las 8 AM.” El tracing distribuido hace esto para tu petición - puedes ver cada “parada” que hizo y cuánto tiempo pasó ahí.

El Viaje por el Aeropuerto: Tu vuelo involucra muchos pasos: mostrador de check-in (5 min), control de seguridad (15 min), caminata a la puerta (10 min), abordaje (20 min), vuelo (2 horas), desembarque (10 min), reclamo de equipaje (15 min). Si alguien pregunta “¿por qué el viaje tomó 4 horas?”, puedes señalar exactamente qué paso fue lento. Eso es lo que las trazas muestran para las peticiones.

La Carrera de Relevos: En una carrera de relevos, el tiempo parcial de cada corredor se registra. Puedes ver que el corredor 2 fue 3 segundos más lento de lo esperado, identificando inmediatamente dónde se perdió tiempo. Cada corredor es un span en la traza.

La Investigación de la Escena del Crimen: Los detectives rastrean los movimientos de un sospechoso a través de la ciudad: “Salió de casa a las 8 AM (grabación de cámara), llegó al banco a las 8:30 (registro de transacción), gasolinera a las 9 AM (recibo).” Están reconstruyendo un viaje desde evidencia dispersa. El tracing distribuido automáticamente recolecta esta evidencia para cada petición.

Code Example

// Configuración de tracing distribuido con OpenTelemetry
import { trace, SpanKind, SpanStatusCode, context } from '@opentelemetry/api';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { JaegerExporter } from '@opentelemetry/exporter-jaeger';
import { W3CTraceContextPropagator } from '@opentelemetry/core';

// Inicializar tracer
const provider = new NodeTracerProvider();
provider.addSpanProcessor(
  new BatchSpanProcessor(new JaegerExporter({ endpoint: 'http://jaeger:14268/api/traces' }))
);
provider.register({ propagator: new W3CTraceContextPropagator() });

const tracer = trace.getTracer('order-service', '1.0.0');

// Trazar un manejador de petición HTTP
app.post('/api/orders', async (req, res) => {
  // Iniciar un nuevo span (o continuar desde contexto de traza entrante)
  const span = tracer.startSpan('create_order', {
    kind: SpanKind.SERVER,
    attributes: {
      'http.method': 'POST',
      'http.url': req.url,
      'order.customer_id': req.body.customerId
    }
  });

  try {
    // Envolver todas las operaciones en el contexto del span
    await context.with(trace.setSpan(context.active(), span), async () => {
      // Cada llamada a servicio crea un span hijo
      const items = await validateInventory(req.body.items);
      const payment = await processPayment(req.body.payment);
      const order = await createOrder(req.body.customerId, items, payment);

      span.setAttribute('order.id', order.id);
      span.setAttribute('order.total', order.total);

      res.json(order);
    });

    span.setStatus({ code: SpanStatusCode.OK });
  } catch (error) {
    span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
    span.recordException(error);
    res.status(500).json({ error: 'Fallo la creación de orden' });
  } finally {
    span.end();
  }
});

// Span hijo para llamada a servicio downstream
async function validateInventory(items: OrderItem[]): Promise<ValidatedItem[]> {
  return tracer.startActiveSpan('validate_inventory', {
    kind: SpanKind.CLIENT,
    attributes: { 'inventory.item_count': items.length }
  }, async (span) => {
    try {
      // Propagar contexto de traza a servicio downstream
      const headers = {};
      propagator.inject(context.active(), headers);

      const response = await fetch('http://inventory-service/validate', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...headers  // Contiene header traceparent
        },
        body: JSON.stringify(items)
      });

      span.setAttribute('inventory.validated', true);
      return response.json();
    } catch (error) {
      span.setStatus({ code: SpanStatusCode.ERROR });
      span.recordException(error);
      throw error;
    } finally {
      span.end();
    }
  });
}

// Operación de base de datos con span
async function createOrder(customerId: string, items: ValidatedItem[], payment: PaymentResult) {
  return tracer.startActiveSpan('db.create_order', {
    kind: SpanKind.INTERNAL,
    attributes: {
      'db.system': 'postgresql',
      'db.operation': 'INSERT',
      'db.table': 'orders'
    }
  }, async (span) => {
    const order = await db.orders.create({
      customerId,
      items,
      paymentId: payment.id,
      status: 'confirmed'
    });

    span.setAttribute('db.rows_affected', 1);
    span.end();
    return order;
  });
}

// Ejemplo de salida de traza (conceptual):
// Trace ID: abc123
// └── create_order (server, 500ms)
//     ├── validate_inventory (client, 150ms)
//     │   └── [inventory-service] check_stock (server, 100ms)
//     │       └── db.query (internal, 50ms)
//     ├── process_payment (client, 300ms)
//     │   └── [payment-service] charge_card (server, 250ms)
//     │       ├── fraud_check (internal, 50ms)
//     │       └── gateway_call (client, 150ms)
//     └── db.create_order (internal, 50ms)

Diagram

flowchart TB
    subgraph TraceStructure["Estructura de Traza (Trace ID: abc-123)"]
        A[Span Raíz
API Gateway
0-500ms] B[Span Hijo
Order Service
10-400ms] C[Span Hijo
Inventory Check
50-150ms] D[Span Hijo
Payment Service
150-350ms] E[Span Hijo
DB Write
350-380ms] F[Nieto
Fraud Check
160-200ms] G[Nieto
Card Charge
200-340ms] end A --> B B --> C B --> D B --> E D --> F D --> G subgraph Propagation["Propagación de Contexto"] H[header traceparent] I[W3C Trace Context] J[Servicio A → Servicio B] end H --> I --> J style A fill:#93c5fd style D fill:#fcd34d style G fill:#f87171

Security Notes

SECURITY NOTES

CRÍTICO - …

Configuración y Validación:

  • Ten cuidado con lo que pones en atributos de span.
  • IDs de usuario, direcciones IP, y parámetros de petición podrían ser apropiados, pero contraseñas, tokens, y detalles de pago no lo son.
  • Implementa sanitización de atributos para campos sensibles.
  • Los headers de contexto de traza pueden ser falsificados.
  • Un atacante podría inyectar trace IDs falsos para contaminar tus datos de tracing o intentar correlacionar sus peticiones maliciosas con tráfico legítimo.
  • Valida y sanitiza el contexto de traza entrante.

Monitoreo y Protección:

  • Atributos de alta cardinalidad como user_id o request_id pueden hacer el tracing costoso.
  • Considera estrategias de sampling que capturen trazas completas para un porcentaje de peticiones mientras todavía detectan anomalías.
  • Los datos de tracing tienen implicaciones de retención.
  • Las trazas pueden contener información sujeta a regulaciones de privacidad (GDPR, CCPA).
  • Define políticas de retención y procedimientos de anonimización para datos de traza.
  • La propagación de traza cross-service revela arquitectura interna.
  • En sistemas multi-tenant, asegura que las trazas de diferentes tenants no filtren información entre sí..

Best Practices

  1. Instrumenta en límites de servicio - Cada petición entrante y llamada saliente debería crear un span
  2. Propaga contexto a través de headers - Usa W3C Trace Context (traceparent) para interoperabilidad
  3. Añade nombres de span significativos - “GET /api/users/:id” es mejor que “http_request”
  4. Incluye atributos relevantes - Añade contexto como user_id, order_id, pero evita PII de alta cardinalidad
  5. Usa sampling sabiamente - 100% de sampling es costoso; muestrea estratégicamente basado en error, latencia, o porcentaje
  6. Establece status de span correctamente - Marca spans como error cuando fallan, incluye detalles de excepción
  7. Crea spans hijos para operaciones significativas - Consultas de base de datos, búsquedas de cache, y llamadas externas cada una merecen spans
  8. Conecta trazas a logs - Incluye trace_id en entradas de log para correlacionar logs detallados con contexto de traza
  9. Usa eventos de span para hitos - Los eventos marcan momentos significativos dentro de un span sin crear spans hijos
  10. Monitorea completitud de trazas - Spans faltantes indican gaps de instrumentación o contexto perdido

Common Mistakes

No propagar contexto de traza: Si un servicio no reenvía el header traceparent, la traza se rompe y pierdes visibilidad.

Demasiados spans (sobre-instrumentación): Crear spans para cada llamada de función crea ruido y sobrecarga de rendimiento. Enfócate en límites de servicio y operaciones significativas.

Muy pocos spans (sub-instrumentación): Solo instrumentar el punto de entrada te deja ciego a cuellos de botella internos.

Ignorar operaciones async: Colas de mensajes, trabajos en background, y procesamiento async necesitan propagación de contexto de traza también - la traza no debería terminar cuando publicas a una cola.

Atributos de alta cardinalidad: Usar valores dinámicos como timestamps o UUIDs como nombres de span o atributos explota costos de almacenamiento.

No hacer sampling en producción: Trazar 100% de peticiones en sistemas de alto tráfico es costoso. Usa sampling inteligente (basado en error, latencia, o probabilístico).

Tratar trazas como logs: Las trazas muestran flujo de petición y timing, no logs detallados de eventos. Usa spans para estructura, logs para detalle.

Standards & RFCs