API 200 y servicio lento: trazas con OpenTelemetry
Una API puede devolver HTTP 200 en 2.000 ms y aun así el usuario sentir que el sitio está roto. El código de estado confirma que la petición terminó sin error; no dice nada sobre dónde se consumió el tiempo. En una arquitectura con gateway, varios servicios, base de datos y APIs externas, la respuesta correcta llega tarde y el problema queda invisible para los logs de la aplicación y para el monitor de CPU del servidor.
En este artículo:
- ¿Por qué una API puede devolver 200 y aun así el servicio ir lento?
- ¿Qué es la trazabilidad distribuida y por qué importa en microservicios?
- Logs, métricas y trazas: tres preguntas distintas
- Cómo funcionan Trace, Span y la propagación de contexto
- Cómo se instrumenta una API en Python
- Por qué el Collector va en el medio
- ¿Cómo evitar que el almacenamiento de trazas salga carísimo?
- ¿Qué datos sensibles no conviene guardar en los spans?
- Un criterio para verificar antes de rediseñar
¿Por qué una API puede devolver 200 y aun así el servicio ir lento?
Porque el tiempo total es la suma de tramos que se ejecutan en sistemas distintos, y el código de estado sólo describe el resultado final. Un caso ilustrativo del artículo que originó esta nota: el gateway tarda 20 ms, el servicio de búsqueda 40 ms, la consulta a la base 80 ms, una API externa 1.800 ms y el formateo de la respuesta 60 ms. El cliente ve “200, 2.000 ms” y nada más.
| Etapa | Tiempo |
|---|---|
| API Gateway | 20 ms |
| Procesamiento interno del servicio | 40 ms |
| Consulta a base de datos | 80 ms |
| Llamada a API externa | 1.800 ms |
| Formateo de resultados | 60 ms |
La fuente aclara que esos tramos se suponen secuenciales y sin solapamiento, y que el ejemplo es hipotético. Aun así, el punto se sostiene: logs sin errores, CPU normal y conexión a base de datos estable son compatibles con una latencia que el usuario percibe como caída. Cuando la causa está tres saltos más allá del servicio que alertó, el monitoreo por servidor no alcanza.
¿Qué es la trazabilidad distribuida y por qué importa en microservicios?
La trazabilidad distribuida es la técnica que reconstruye el recorrido completo de una petición a través de todos los procesos que toca. En lugar de mirar cada servidor por separado, asigna un identificador común a la petición y registra cada operación con su inicio, su fin y su relación con la anterior. Así se ve en qué salto se fue el tiempo, incluso entre servicios escritos en lenguajes distintos y operados por equipos distintos. Te puede servir nuestra guía de observabilidad en FastAPI.
Importa en microservicios porque ahí el trabajo se reparte: un checkout puede involucrar quince procesos y el lento estar lejos del que disparó la alerta. Sin un identificador compartido, esa reconstrucción se hace a mano, copiando timestamps entre pestañas.
Logs, métricas y trazas: tres preguntas distintas
Los tres pilares de la observabilidad no compiten entre sí; responden cosas diferentes. La documentación de OpenTelemetry describe las trazas como el camino de una petición por la aplicación, y la fuente original lo resume en una tabla que conviene retener:
| Señal | Qué responde | Qué aporta |
|---|---|---|
| Logs | ¿Qué pasó en este proceso? | Mensajes de error, resultados de una operación |
| Métricas | ¿Cómo está el sistema, en números? | Cantidad de requests, tasa de error, latencia P95 |
| Trazas | ¿Por dónde pasó esta petición y cuánto tardó cada paso? | Orden de llamadas entre servicios y duración por salto |
El valor aparece al correlacionarlos. Una métrica dice que el P95 subió; una traza muestra qué llamada lo causó; el log asociado a ese trace ID agrega el detalle que la traza no captura.
Cómo funcionan Trace, Span y la propagación de contexto
Un trace es el flujo completo de una petición y se identifica con un trace ID que comparten todos los servicios involucrados. Un span es una operación individual dentro de ese flujo, con nombre, timestamps de inicio y fin, un span ID propio y, salvo el span raíz, un identificador del span padre. Los spans se anidan y forman un árbol: ahí está la cascada que se ve en las herramientas de tracing. Esto se conecta con lo que analizamos en nuestra guía de monitoreo en Kubernetes con OpenTelemetry.
Los attributes agregan metadatos al span, como el método HTTP, la ruta o el código de respuesta. La documentación oficial recomienda usar convenciones semánticas de atributos para estandarizar los metadatos comunes entre sistemas.
Lo que hace posible el tracing entre procesos es la propagación de contexto. OpenTelemetry usa el estándar W3C Trace Context, que viaja en el header traceparent con este formato:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01Los campos son la versión, el trace ID de 16 bytes, el span ID del llamador de 8 bytes y las flags. Un detalle que suele pasarse por alto: en el header viajan sólo el contexto (trace ID, span ID, flags y estado), no los attributes ni los eventos. Si un servicio externo no está instrumentado, no hay forma de ver su trabajo interno; lo único observable es el span del cliente que lo llamó, que igual sirve para medir cuánto tardó esa llamada.
Cómo se instrumenta una API en Python
Con el SDK de OpenTelemetry y la instrumentación automática de FastAPI, un ejemplo mínimo queda así (adaptado de la fuente, que simula las demoras con time.sleep para el ejemplo). También analizamos este escenario en nuestro artículo sobre Bloomberg.
resource = Resource.create({"service.name": "search-api"})
provider = TracerProvider(resource=resource)
provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
FastAPIInstrumentor.instrument_app(app)
@app.get("/search")
def search():
with tracer.start_as_current_span("database.query"):
time.sleep(0.08)
with tracer.start_as_current_span("external_api.call"):
time.sleep(1.8)
return {"status": "success", "results": []}En producción se cambia el exportador de consola por uno que envíe los datos a un Collector, y se agrega auto-instrumentación para las librerías de HTTP y de base de datos en lugar de abrir spans a mano.
Por qué el Collector va en el medio
Un servicio puede exportar sus trazas directamente al backend, pero cuando hay decenas de servicios, cada uno termina gestionando envío, filtrado y reintentos. El OpenTelemetry Collector centraliza esa tarea con cuatro piezas: receivers que reciben, processors que filtran o transforman, exporters que entregan y un pipeline que los conecta. La contrapartida es operativa: hay que monitorear su disponibilidad, su capacidad de procesamiento y el backpressure.
¿Cómo evitar que el almacenamiento de trazas salga carísimo?
Con sampling: guardar una fracción de las trazas en lugar de todas. El head sampling decide al inicio de la petición, cuando todavía no se sabe si va a fallar o a tardar. El tail sampling decide al final, con los spans ya recolectados, y por eso puede retener el 100% de los requests con error y de los que superaron un umbral de latencia. A cambio, consume más memoria y requiere que los spans de una misma traza lleguen juntos al punto de decisión cuando hay varios colectores.
| Situación | Qué conviene evaluar |
|---|---|
| Servicio chico, poco tráfico | Recolección total o sampling simple |
| Tráfico alto, costo sensible | Head sampling |
| Foco en errores y requests lentos | Tail sampling |
| Requisitos mixtos | Política jerárquica combinada |
La fuente original es explícita: lo importante no es el porcentaje, sino definir qué requests necesitás poder analizar y construir la política a partir de eso.
¿Qué datos sensibles no conviene guardar en los spans?
Tokens de acceso, API keys, contraseñas, identificadores de sesión, datos personales y cuerpos de request sensibles. Todo eso puede terminar en un attribute sin que nadie lo note, y el sistema de observabilidad concentra información de producción con accesos amplios. La recomendación operativa es no recolectar por defecto, registrar sólo los attributes necesarios, filtrar o enmascarar en la recolección y limitar el acceso y la retención.
El caso de Baggage merece atención aparte: es el mecanismo para pasar información entre servicios a través de headers, de modo que puede salir hacia sistemas externos. No conviene poner secretos ahí, y cualquier valor recibido desde afuera debe tratarse como input no confiable.
Un criterio para verificar antes de rediseñar
Propuesta editorial, no un resultado de la fuente: antes de instrumentar todo, tomá un endpoint con latencia alta y hacé dos comprobaciones. Primero, compará la métrica de P95 con la cascada de una traza lenta del mismo intervalo; si el span más costoso es una llamada saliente a un servicio externo, el problema está en el proveedor o en la red, no en tu código. Segundo, repetí la medición con la llamada externa deshabilitada o apuntando a un stub local. Si el tiempo total cae a la suma de los tramos internos, confirmaste la hipótesis; si no, el cuello está en otro lado y conviene revisar base de datos y conexiones. Es una prueba acotada y no concluyente sobre la causa raíz, pero alcanza para decidir dónde mirar primero.
Las fuentes consultadas
- API 응답은 정상인데 왜 서비스가 느릴까? OpenTelemetry로 분산 추적 시스템 설계하기 (dev.to, 12 de noviembre de 2026)
- Traces, documentación oficial de OpenTelemetry
- W3C Trace Context, especificación del W3C
- Distributed Tracing End-to-End: Context Propagation, Tempo, and Correlating Traces with Metrics and Logs (Kloudvin, 4 de junio de 2026)






