Cronradar · arquitectura
¿Quién vigila al vigilante?
Construir un monitor de cron que tiene que sobrevivir a su propia caída — y por qué borré la mitad de la infraestructura para lograrlo.
Cronradar →El problema
Cronradar existe para avisarte en el momento en que un job programado falla en silencio. Eso invierte la pregunta habitual de fiabilidad: el producto solo sirve cuando algo más está caído, y nunca debe ser lo que se queda en silencio sin que nadie lo note. No puedes alertar sobre un error que recibiste — no hay error. Alertas sobre un ping que nunca llegó, lo que significa que todo el sistema está construido para detectar una ausencia contra un plazo.
Cada monitor lleva una hora esperada para el próximo ping y un período de gracia. Un barrido en segundo plano busca los monitores cuyo plazo ya pasó, los mueve a Advertencia y luego a Crítico, y escala. Un segundo barrido atiende al monitor que se creó pero nunca hizo ping — un job que debía empezar a reportar y nunca lo hizo. La ausencia tiene dos formas, y ambas hay que atraparlas contra el reloj.
Autodescubrimiento y el problema de la deriva
La integración de cinco líneas no solo envía pings — los SDK de framework (Hangfire, Quartz) reconcilian en monitores los jobs que tu app realmente declara. No registras nada a mano; el horario en tu código es la fuente de verdad.
Eso crea un fallo más sutil que un ping perdido: un job que se elimina por completo del horario. Nunca volverá a hacer ping, así que un monitor ingenuo alertaría para siempre. El barrido de deriva registra cuándo se declaró cada job por última vez, y pausa automáticamente los monitores cuyos jobs desaparecieron del horario — deliberadamente tras un flag desactivado por defecto, porque pausar un monitor en silencio es justo la clase de comportamiento «servicial» que puede ocultar una caída real si te equivocas sobre la intención.
Por qué borré Redis, Typesense y la base de datos empaquetada
El backend es .NET (orquestado con Aspire) sobre Postgres, con Hangfire corriendo el cálculo de horarios y los barridos. No empezó así de ligero. Al principio también corría Redis, Typesense, nginx y un Postgres empaquetado. En una sola pasada arranqué todo eso y enruté todo por Traefik contra un único Postgres compartido y gestionado.
El razonamiento es toda la tesis del producto: cada pieza móvil adicional es otra cosa que puede fallar en el único servicio cuyo trabajo entero es seguir funcionando cuando otras cosas fallan. Una caché que tengo que mantener coherente, el índice de un buscador que tengo que mantener en sincronía, una base de datos que tengo que operar yo mismo — cada una es un nuevo modo de fallo que estaría metiendo en una herramienta de fiabilidad. Postgres ya maneja bien el estado duradero y transaccional; el estado de un monitor de cron es pequeño y relacional. Así que la respuesta a «¿por qué no una cola / una caché / un clúster de búsqueda?» es que las tuve, y luego elegí tener menos cosas que se puedan romper.
Vigilar al vigilante
La ingesta de pings corre como su propio servicio, separado del panel. Esa separación es el punto: el plano que recibe los latidos de tus jobs no comparte destino con el plano que dibuja gráficos o procesa la facturación, así que un problema del panel no puede hacerte perder un ping. Las rutas de alertado, ingesta de pings y mutación del panel se endurecieron para ejecución duradera, de modo que una alerta en vuelo sobrevive a un reinicio en vez de perderse.
cron.life es el endpoint de latido que los jobs golpean al terminar con éxito; crontab.space es el constructor de horarios en lenguaje llano — compañeros que además permiten ejercitar la propia salud de Cronradar igual que el job de un cliente.
Lo que de verdad dicen los números
Medido en el borde (logs de acceso de Traefik) durante unas tres semanas: respuesta HTTP mediana de 14 ms, p95 alrededor de 97 ms, p99 alrededor de 658 ms entre la API, la ingesta de latidos y los endpoints del panel. Eso es latencia de respuesta del endpoint medida en el proxy — lo rápido que Cronradar contesta una petición.
Deliberadamente no es el número que el marketing de un monitor de cron querría citar, porque no es el tiempo de entrega de la alerta. Una alerta es un job en segundo plano que nunca atraviesa el proxy; medir «qué tan rápido nos damos cuenta y avisamos» significa cronometrar de ping-recibido a notificación-enviada, que es otra medición que reportaría por separado en vez de dejar que un tiempo de respuesta rápido la sustituya.
Qué haría distinto
La brecha honesta hoy es la cobertura de pruebas: los SDK están probados, pero la lógica de barrido del servidor — el código exacto que decide si tu job va tarde — se apoya en la confianza manual más de lo que me gustaría para el componente que más importa. Para un producto cuya promesa es «nos daremos cuenta», la ruta de detección de fallos es justo donde querría pruebas basadas en propiedades sobre los bordes del reloj, los límites del período de gracia y los cambios de horario de verano antes de darla por terminada.