🐾

NutriPaw — Trazado Distribuido

OpenTelemetry · Jaeger · Grafana Tempo — namespace: nutripaw-prod

✓ Instrumentado Jaeger 1.62 OTel SDK 1.26
🔍 Hallazgo: Cuello de botella identificado
🚨
nutrition-engine — P99 latencia: 8.4s
El span generate_nutrition_plan acumula el 73% del tiempo total de la request. Causa: llamada bloqueante a modelo ML sin caché + N+1 queries a PostgreSQL (se detectaron 47 SELECT individuales por llamada).
⚠️
pet-profile-service — cache miss rate: 94%
Redis configurado con TTL de 30s. Los perfiles de mascotas raramente se actualizan pero se reconstruyen en cada request. Aumentar TTL a 24h reduciría latencia en ~600ms.
auth-service y api-gateway — operando correctamente
Latencias de 12ms y 28ms respectivamente. No requieren acción inmediata.
Latencia P99 (antes)
8.4s
Traza ID: abc123ef — nutrition-engine
Trazas muestreadas/min
312
5% sampling · prod · Jaeger + Tempo S3
Error rate (trazas con error)
0.8%
SLO < 1% — cumplido ✓
📊 Traza Jaeger — Request "Obtener Plan Nutricional"
🔗

Trace ID: abc123ef9d2e4f1a · Duración total: 9,241ms · 8 spans

2026-06-16 14:23:07 UTC
0ms 1s 2s 3s 4s 5s 6s 7s 8s 9.2s
Servicio / Operación Timeline Duración Estado
frontend-nextjs
GET /plan
9,241ms
LENTO
api-gateway
route_request
9,192ms
LENTO
auth-service
verify_jwt
12ms
OK
pet-profile-service
get_pet_profile
693ms
CACHÉ
redis-cache / MISS
GET pet:442
42ms
MISS
postgres-db / pets
SELECT pets WHERE id=442
630ms
OK
nutrition-engine 🔥
generate_nutrition_plan — N+1 queries + ML blocking
8,220ms
CRÍTICO
notification-service
send_ready_push
89ms
OK
🗺️

Mapa de servicios — Dependencias detectadas

frontend124 req/min
api-gateway124 req/min
auth-svc12ms avg
pet-profile693ms avg
nutrition-engine 🔥8,220ms avg
notif-svc89ms avg
redis94% miss
postgres47 queries/req

Grafo generado automáticamente por Jaeger a partir de las trazas. Las líneas rojas indican el critical path.

🛠️

Plan de acción — Optimizaciones detectadas

PrioridadServicioFixImpacto estimado
P0 nutrition-engine Batch SQL (47 → 1 query), async ML −6.8s
P0 nutrition-engine Cache resultados ML (Redis TTL 1h) −5.2s (hit)
P1 pet-profile Aumentar TTL Redis: 30s → 24h −630ms
P1 postgres Índice en pets.owner_id + EXPLAIN −200ms
P2 api-gateway Connection pooling (asyncpg) −28ms
Latencia proyectada tras fixes P0+P1:
~410ms (vs 9,241ms actual)
Mejora del 95.6% en P99 · Dentro de SLO <500ms
💻 Instrumentación OpenTelemetry — nutrition-engine (Python)
🐍

nutrition_engine/tracing.py — Implementación con spans y atributos semánticos

python
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.trace.sampling import ParentBased, TraceIdRatioBased
import asyncio

# ── Inicializar TracerProvider con 5% sampling (prod) ──
resource = Resource(attributes={SERVICE_NAME: "nutrition-engine"})
sampler  = ParentBased(root=TraceIdRatioBased(0.05))  # 5% prod
provider = TracerProvider(resource=resource, sampler=sampler)
exporter = OTLPSpanExporter(endpoint="http://tempo:4317")   # → Tempo/Jaeger
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("nutrition-engine")

async def generate_nutrition_plan(pet_id: int, owner_id: int) -> dict:
    with tracer.start_as_current_span("generate_nutrition_plan") as span:
        # Tags semánticos para filtrado en Jaeger UI
        span.set_attribute("pet.id",    pet_id)
        span.set_attribute("owner.id",  owner_id)
        span.set_attribute("model.version", "nutripaw-ml-v2.3")

        # FIX P0: batch query en lugar de N+1
        with tracer.start_as_current_span("db.fetch_pet_data_batch") as db_span:
            db_span.set_attribute("db.system",    "postgresql")
            db_span.set_attribute("db.statement", "SELECT * FROM pets JOIN nutrition_history ... WHERE pet_id=$1")
            pet_data = await db_fetch_batch(pet_id)   # 1 query, no 47

        # FIX P0: ML async, no bloqueante
        with tracer.start_as_current_span("ml.infer_nutrition_plan") as ml_span:
            ml_span.set_attribute("ml.framework", "torch")
            ml_span.set_attribute("ml.cached",    "false")
            plan = await asyncio.to_thread(ml_model_infer, pet_data)
            ml_span.set_attribute("ml.tokens_used", plan["tokens"])

        span.add_event("plan_generated", {"calories": plan["kcal"]})
        return plan
🐳

docker-compose.observability.yml — Stack local

yaml
version: "3.8"
services:
  jaeger:
    image: jaegertracing/all-in-one:1.62
    ports: ["16686:16686", "4317:4317"]
    environment:
      COLLECTOR_OTLP_ENABLED: "true"

  tempo:
    image: grafana/tempo:2.7
    ports: ["3200:3200", "4317:4317"]
    volumes: ["./tempo.yaml:/etc/tempo/tempo.yaml"]
    command: "-config.file=/etc/tempo/tempo.yaml"

  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.113
    volumes: ["./otel-config.yaml:/etc/otel/config.yaml"]
    command: "--config=/etc/otel/config.yaml"
    depends_on: [jaeger, tempo]
☸️

Kubernetes — Jaeger + sampling (nutripaw-prod)

yaml
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: nutripaw-jaeger
  namespace: nutripaw-prod
spec:
  strategy: production
  sampling:
    options:
      default_strategy:
        type: probabilistic
        param: 0.05   # 5% — restr. costes S3
      service_strategies:
        - service: nutrition-engine
          type: probabilistic
          param: 1.0    # 100% — debug activo
  storage:
    type: elasticsearch
    options:
      es.server-urls: http://elasticsearch:9200
🚀 Plan de implementación — NutriPaw

Desplegar Jaeger Operator en GKE (namespace nutripaw-prod)

Instalar el operador v1.51 con la configuración de sampling: 5% global, 100% para nutrition-engine durante la fase de debug. Conectar Elasticsearch para persistencia de trazas 30 días.

Instrumentar nutrition-engine con OTel SDK (Python 3.11)

Añadir opentelemetry-sdk, opentelemetry-exporter-otlp y el código de tracing.py. Envolver las funciones críticas con spans: generate_nutrition_plan, db_fetch_batch, ml_model_infer.

Propagación de contexto HTTP entre servicios

Inyectar cabeceras W3C TraceContext (traceparent) en todas las llamadas entre api-gateway → pet-profile → nutrition-engine → notification-service usando los propagadores de OTel.

Configurar Grafana Tempo como backend de largo plazo (S3)

Tempo escribe a bucket GCS nutripaw-traces con retención de 90 días. Jaeger envía trazas a Tempo via OTLP gRPC. Grafana 10 datasource apunta a Tempo para correlación con métricas Prometheus.

Alertas basadas en trazas (SLO latencia)

Configurar alerta en Grafana: si el span generate_nutrition_plan supera 500ms en P95 durante 5 minutos → PagerDuty. Segunda alerta para error rate en trazas > 1%.

Atributos semánticos usados en spans NutriPaw:
pet.id owner.id model.version db.system db.statement http.status_code error ml.framework ml.cached ml.tokens_used cache.hit request.id