Durante semanas, una tarea cron en Elevare Digital se activaba según lo programado, revisaba su cola y registraba un éxito sin errores. Aprobaba exactamente cero borradores. Diecinueve piezas de contenido permanecían esperando. El equipo solo se enteró más tarde, después de que el vacío silencioso hubiera pasado de ser una anomalía a convertirse en un pequeño retraso. Nada se había caído. No se activaron alertas de paginación. El sistema estaba técnicamente sano y funcionalmente muerto.
Este es el horror silencioso de los pipelines autónomos. Cuando eliminas al humano del proceso, también eliminas a la persona que nota que no está pasando nada.
El pipeline que se ejecutaba solo
Elevare Digital gestiona un flujo de trabajo de contenido totalmente automatizado. Agentes de software generan borradores. Una tarea cron de aprobación programada actúa como guardián, revisando esos borradores y enviando los elementos aprobados directamente a la publicación. Ningún humano abre un panel de control para dar el visto bueno a cada lote. El objetivo principal es que la máquina se encargue de las tareas tediosas mientras el equipo se dedica a otros problemas.
Bajo este modelo, la confianza se convierte en tu interfaz principal. Confías en que el programador se ejecute. Confías en que el trabajo se realice. Confías en el código de salida. Cuando los registros muestran un latido constante de respuestas 200 OK, asumes que el trabajo está avanzando. Durante semanas, ese latido fue perfecto. La cron se ejecutaba a tiempo, siempre. Simplemente, nunca realizó el trabajo real.
Diecinueve borradores y ninguna alarma
El descubrimiento fue accidental. Alguien acabó notando que la cola de publicación se había quedado en silencio, o quizás revisaron una métrica descendente y vieron una línea plana. Lo que encontraron fue un montón de diecinueve borradores que permanecían completamente intactos. El aprobador se había estado ejecutando diligentmente, registrando éxitos cada día, y no había procesado ninguno de ellos.
En un flujo de trabajo manual, un revisor humano habría notado una bandeja de entrada vacía o una acumulación de elementos pendientes desde el primer día. En la versión automatizada, la ausencia de actividad parecía exactamente la ausencia de trabajo. La cron no tenía un gerente a quien decepcionar. Simplemente seguía fichando y marchándose temprano a casa.
Dos errores, un resultado vacío
El fallo tuvo dos causas. Ninguna fue un error de sintaxis, un tiempo de espera agotado o una caída de dependencias. Ambas fueron errores semánticos que redujeron diecinueve filas válidas a la nada a ojos del motor de consultas.
Primero, un desajuste de tipos. El agente que generaba los borradores escribía registros etiquetados como article. La cron de aprobación consultaba específicamente tipos thread. Este es el tipo de deriva que ocurre cuando los productores y los consumidores evolucionan en vías paralelas. Un equipo —o un agente— decidió que la salida era un artículo. Otro escribió el consumidor asumiendo que ingeriría hilos (threads). Ningún sistema de tipos lanzó un error de compilación porque probablemente se trataba de etiquetas de cadena sueltas, tal vez campos JSON o valores varchar sin restricciones. La base de datos simplemente no encontró coincidencias y devolvió un conjunto vacío. Para el motor, eso no es una condición de error. Es una respuesta correcta a una pregunta incorrecta.
Segundo, un inner join en la consulta del aprobador se tragó silenciosamente las filas por completo. Si la consulta unía la tabla de borradores con otra tabla —quizás una búsqueda de metadatos, indicadores de estado o reglas de enrutamiento— y la condición de unión fallaba, el inner join se comportaba exactamente como fue diseñado. Excluía las filas que no coincidían. No aparecieron filas huérfanas en el conjunto de resultados. Ningún valor nulo señaló un problema. Los diecinueve borradores pasaron a través de la consulta como el agua a través de un tamiz, y la capa de aplicación recibió una lista impecable y vacía.
Como la consulta no devolvió filas, la función terminó limpiamente. No se propagaron excepciones. La respuesta HTTP fue 200 OK. La cron registró el éxito y volvió a dormirse.
La trampa del "procesado cero"
Aquí está el núcleo del problema. En un sistema basado en colas, un consumidor encuentra con frecuencia cero filas para procesar. La cola se vacía. El trabajador termina rápido. El registro dice processed: 0 y el equipo lo interpreta como una buena noticia: estamos cumpliendo con la demanda. Ese es un estado saludable.
Pero processed: 0 codifica dos realidades completamente diferentes:
- Estado saludable: Cero procesados porque hay cero pendientes. La cola está vacía. El sistema está inactivo por diseño.
- Estado roto: Cero procesados porque el consumidor no puede ver el trabajo. La cola tiene diecinueve filas. El sistema está ciego, no inactivo.
Sin una comprobación independiente de la profundidad de la cola, estos dos estados emiten una telemetría idéntica. Se ven igual en los paneles de control, huelen igual en los agregadores de registros y provocan el mismo silencio en PagerDuty. Has construido una estrategia de monitoreo que detecta cuando el trabajador grita, no cuando susurra pasando junto a una pila de trabajo real.
Cerrando la brecha
Elevare Digital solucionó el problema cambiando lo que monitorea. Dejaron de depender únicamente de las tasas de error y los estados de éxito. En su lugar, comenzaron a emitir alertas sobre la brecha entre el trabajo disponible y el trabajo completado.
Después de cada lote, ahora ejecutan una simple comprobación de invariantes:
- Si processed es 0 y las filas pendientes son mayores que 0, activar una alerta de alta severidad.
Esta regla es deliberadamente agnóstica respecto a la causa. No le importa si el fallo se debió a un filtro incorrecto, un join roto o una cadena de enum mal escrita. Solo le importa que existe trabajo y que no se realizó ninguno. Esto cambia el enfoque del monitoreo de "¿Se quejó el proceso?" a "¿Se movió el trabajo?".
Para respaldar esto, tratan la profundidad de la cola como una métrica de primer nivel, rastreada a lo largo del tiempo y no solo como una comprobación puntual. Si el productor sigue añadiendo filas mientras el consumidor reporta éxito continuamente, la tendencia de la profundidad se convierte en una prueba irrefutable. Una instantánea estática puede mentir, pero un backlog creciente nunca lo hace.
Lecciones para sistemas autónomos
El incidente de Elevare contiene un puñado de reglas prácticas para cualquiera que gestione pipelines automatizados.
Registra las filas escaneadas por separado de las filas procesadas. El consumidor podría ejecutar una consulta que afecte a cuarenta filas, filtrarlas todas mediante criterios incorrectos y reportar processed: 0. Si solo registras el recuento final, te perderás la interacción fantasma. Una métrica de filas escaneadas revela que el trabajador se presentó, miró el trabajo y se fue confundido. Esa brecha entre lo escaneado y lo procesado es, a menudo, tu señal más temprana.
Rastrea la profundidad de la cola como una serie temporal. Una cola que está temporalmente vacía está bien. Una cola que crece de forma monotónica mientras los trabajadores se mantienen en verde, no. Grafica la profundidad frente al rendimiento del consumidor. Cuando ambos diverjan, investiga de inmediato, incluso si todas las comprobaciones de salud pasan correctamente.
Prueba los consumidores con la salida real del productor, no solo con mocks. Las pruebas unitarias con datos simulados (mocks) conllevan las suposiciones del evaluador. Si la fábrica de mocks produce tipos thread y el consumidor espera tipos thread, tus pruebas pasarán mientras que la producción fallará. Ejecuta pruebas de integración que extraigan registros reales de la salida del productor. Asegúrate de que el consumidor pueda ver realmente lo que el productor escribe.
Trata los tipos de datos y los valores de enum como contratos. Las etiquetas de cadena sueltas en blobs JSON son convenientes hasta que se convierten en puntos de fallo invisibles. Define esquemas explícitamente. Comparte constantes. Valida los payloads en la unión entre el productor y el consumidor. Si el contrato se rompe, el sistema debe fallar de forma evidente en el límite, no silenciosamente dentro de una cláusula WHERE.
La verdadera conclusión
Los sistemas autónomos no fallan como los humanos. No llaman para decir que están enfermos, no lanzan excepciones cada vez ni dejan volcados de memoria (crash dumps) obvios. Devuelven un 200 OK y dejan que el inventario se pudra. Si tus alertas solo escuchan gritos, te perderás los fallos más costosos: aquellos en los que todo parece estar bien y no se hace nada.
Diseña tu observabilidad para vigilar la brecha. Mide el trabajo que entra frente al trabajo que sale. Cuando ambos dejen de coincidir, asume que la máquina te está mintiendo. Porque, a veces, un registro de éxito perfecto es el único síntoma de un sistema que se ha quedado completamente ciego.
