Microservicios mudos: redes Docker, RabbitMQ y el día que nadie hablaba con nadie
En arquitecturas distribuidas, el problema casi nunca es que un servicio "se cayó". Es que dos servicios sanos no se entienden. Aquí está la metodología que usamos para diagnosticar fallas de comunicación, ilustrada con un caso real de auth + billing + notification + RabbitMQ.
Una noche de abril, en una de nuestras plataformas SaaS pasaron dos cosas a la vez:
- Los emails de "restablecer contraseña" no llegaban.
- El dashboard de los usuarios cargaba eternamente.
Ningún servicio estaba "caído". Todos respondían sus health checks. Y aun así, el sistema completo estaba inservible. La causa no era que un servicio hubiera muerto — era que dos servicios vivos no estaban hablando, y otro estaba hablando consigo mismo en círculo.
Este post no es un tutorial. Es la metodología que usamos para diagnosticar este tipo de fallas en sistemas distribuidos, ilustrada con el caso real de aquella noche.
El problema arquitectónico de fondo
Las arquitecturas de microservicios cambian la naturaleza de los bugs. En un monolito, si tu sistema se cae, suele ser por una excepción no manejada, una fuga de memoria o una base de datos saturada. La causa está en un proceso.
En microservicios, los bugs más caros viven entre los procesos: en la red, en los contratos de eventos, en el orden de arranque, en la consistencia eventual. La falla no está en ningún log individual — está en el espacio entre los logs.
Por eso depurar microservicios requiere un cambio mental: no busques al culpable, busca la conversación rota.
Las cinco fallas de comunicación más comunes
Después de tres años administrando arquitecturas distribuidas en producción, casi todas las fallas que hemos visto entran en una de estas cinco categorías:
1. Red Docker mal configurada
Dos servicios que deberían poder hablar están en redes Docker distintas y nadie se acordó de unirlos. El servicio A intenta resolver el nombre del servicio B y obtiene "name not resolved" — pero en logs aparece como timeout, lo que confunde el diagnóstico.
Cómo se detecta: docker network inspect <red> muestra qué contenedores están dentro. Si te falta uno que esperabas, ese es tu problema.
2. Contrato de evento roto
El servicio A publica un evento con un campo tenant_id. El servicio B lo consume esperando un campo tenantId (camelCase). El evento llega, pero el campo es null. La acción no se ejecuta. Y nadie ve un error, porque para el sistema "el mensaje se procesó".
Cómo se detecta: logs de qué entra al consumidor vs qué sale. Si entra algo y la salida es "no hago nada porque tenantId es null", encontraste el problema.
3. Mensajes envenenados que bloquean la cola
Un mensaje malo se reintenta infinitamente. La cola se llena de reintentos del mismo mensaje y los mensajes legítimos quedan esperando atrás. La cola "no está caída" — está ocupada haciendo nada útil.
Cómo se detecta: rabbitmqctl list_queues name messages consumers muestra el tamaño de cada cola. Si una cola crece y crece sin que el consumidor la baje, hay un mensaje envenenado.
4. Concurrencia sobre recursos compartidos
Múltiples consumidores comparten la misma instancia de un componente que no es seguro de usar concurrentemente (en .NET, un DbContext singleton; en Node, un cliente con estado mutable). El sistema funciona en pruebas con un solo mensaje a la vez. Bajo carga real, falla intermitentemente con errores raros.
Cómo se detecta: errores tipo "A second operation was started on this context" o similares cuando el servicio recibe varios mensajes en paralelo.
5. Dependencia circular de arranque
El servicio A necesita al B para arrancar. El B necesita al A. Ambos están "vivos" pero ninguno avanza más allá del arranque. Suele pasar después de un reinicio total del cluster.
Cómo se detecta: logs de arranque que se quedan en "esperando a B..." sin progreso. Romper el círculo suele requerir levantar uno con configuración de fallback temporal.
La metodología: de afuera hacia adentro
Cuando pasa una falla en producción, la tentación es abrir logs y leer todo. Es ineficiente. La metodología que funciona es descartar capas, de afuera hacia adentro:
Capa 1: red
¿Pueden los contenedores resolverse y conectarse entre sí? Test rápido: entrar a un contenedor y hacer ping o curl al hostname del otro. Si esto falla, no leas más logs — arregla la red primero.
Capa 2: contrato
¿El mensaje sale como debe y llega como se espera? Test: publicar manualmente un evento de prueba y ver qué recibe el consumidor. Si la forma cambió, el problema es el contrato.
Capa 3: cola
¿Los mensajes se procesan o se acumulan? Test: ver el conteo de mensajes en cada cola. Si crece sin parar, hay un problema en el consumidor (envenenamiento, concurrencia, lentitud).
Capa 4: lógica
¿El consumidor procesa el mensaje y hace lo que debe? Test: log del flujo completo de un mensaje desde que entra al consumidor hasta que actualiza la base. Si algo se pierde en el camino, el problema es lógica.
Capa 5: persistencia
¿Lo que el consumidor escribió en BD coincide con lo que esperabas? Test: leer la base. Si el dato está, el problema está río abajo. Si no está, el problema es río arriba.
El error más común: empezar por capa 5 (la base de datos). Es lo más visible, pero suele ser síntoma, no causa.
Las dos herramientas que ahorran horas
Correlation ID
Cada petición que entra al sistema recibe un identificador único (un UUID). Ese ID viaja con cada mensaje, evento y log que esa petición genere a través de todos los servicios. En un debugging real, encuentras el correlation ID de la petición fallida y filtras todos los logs por ese ID. Tienes la conversación completa entre servicios delante de ti.
Sin correlation ID, depurar un fallo distribuido es mirar logs de servicios distintos por timestamp y rezar.
Logs estructurados, no concatenados
Logs en formato JSON con campos consistentes (service, level, tenant_id, correlation_id, event) son buscables. Logs como cadenas de texto libres son arqueología. La diferencia en una emergencia es 10 minutos vs 4 horas.
Lo que aprendimos del incidente
En el caso real de aquella noche se encontraron tres causas distintas anidadas:
- Concurrencia en la base de datos del servicio de notificaciones (un
DbContextsingleton compartido entre consumidores). - Mensajes envenenados en la cola por un evento mal formado.
- Tokens JWT sin tenantId porque una migración había dejado a varios usuarios sin rol asignado.
Cada uno por separado tenía solución conocida. Lo difícil fue identificarlos en el orden correcto: si arreglas primero la concurrencia pero no limpias los mensajes envenenados, el sistema sigue muerto.
En ServerStack Solutions
Mantener arquitecturas distribuidas en producción es un trabajo que requiere herramientas, metodología y experiencia con cómo fallan. Si tu equipo está empezando con microservicios o llevas tiempo con problemas de comunicación intermitentes que no logras explicar, esa es la conversación que vale la pena tener.
Solicita una revisión arquitectónica. Identificamos los puntos donde tu sistema es frágil y proponemos cómo blindarlo, incluyendo correlation ID, logs estructurados y health checks profundos.
Relacionado: Cuando 'healthy' miente: el container sano que no servía · Microservicios + DDD + Outbox Pattern · Docker + ECR.
Preguntas frecuentes
¿Cuándo conviene microservicios y cuándo es mejor un monolito?
Si tu equipo es de menos de 10 personas y tu producto aún busca encaje en el mercado, casi siempre es mejor un monolito modular bien estructurado. Microservicios añaden costo operacional (red, observabilidad, deploys coordinados) que solo se justifica cuando el equipo se beneficia de poder evolucionar partes del sistema independientemente. Romper un monolito por moda y no por necesidad es la fuente de muchos de los problemas descritos en este post.
¿Qué es exactamente un correlation ID?
Un identificador único que se genera al inicio de cada petición o evento, y que se propaga por todos los servicios que esa petición toca. Por convención se transporta en un header HTTP (X-Correlation-ID) y se incluye en cada log y cada mensaje publicado. En depuración, te permite filtrar millones de logs y ver solo los relacionados con la petición que estás investigando — la diferencia entre encontrar el bug en 10 minutos o en 4 horas.
¿RabbitMQ o Kafka?
Para la mayoría de SaaS multi-tenant de tamaño medio, RabbitMQ es más sencillo y suficiente. Kafka brilla cuando tienes volúmenes muy altos (millones de eventos por minuto) o cuando necesitas reproducir el histórico de eventos para reconstruir estado. RabbitMQ es más fácil de operar, monitorear y depurar. Empieza con RabbitMQ; migra a Kafka cuando el volumen lo justifique de verdad.