Back to 0cron
0cron

Monitoreo heartbeat: cuando tu tarea deberia hacerte ping

Como 0cron implementa monitoreo heartbeat -- cron inverso donde tus tareas hacen ping a una URL, y el silencio dispara alertas. 105 lineas de Rust.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 4 min 0cron
EN/ FR/ ES
0cronmonitoringheartbeatrustpostgresqlalerting

La mayor parte de 0cron funciona en una direccion: llamamos a tus endpoints en un horario. Pero hay una clase de problemas donde la direccion necesita invertirse. Tienes un script de respaldo ejecutandose en tu propio servidor. Un pipeline de CI que deberia completarse cada hora. Una tarea de sincronizacion de datos gestionada por un servicio de terceros. No puedes apuntar 0cron a estas porque no controlas su invocacion. Lo que necesitas saber es si siguen ejecutandose.

Esto es monitoreo heartbeat, y esta incorporado en 0cron como funcionalidad de primera clase. El concepto es simple: te damos una URL. Tu tarea hace ping a esa URL cuando se completa. Si no recibimos un ping dentro de la ventana esperada mas un periodo de gracia, te alertamos. Silencio significa fallo.

La implementacion completa son 105 lineas de Rust.

El modelo de datos

sqlCREATE TABLE monitors (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    team_id UUID REFERENCES teams(id),
    name VARCHAR(255) NOT NULL,
    ping_token VARCHAR(64) UNIQUE NOT NULL,
    schedule_cron VARCHAR(100) NOT NULL,
    grace_period_seconds INTEGER DEFAULT 300,
    timezone VARCHAR(50) DEFAULT 'UTC',
    status VARCHAR(20) DEFAULT 'active',
    last_ping_at TIMESTAMPTZ,
    notification_config JSONB,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

ping_token es una cadena hexadecimal unica de 64 caracteres (32 bytes aleatorios). Tokens en lugar de IDs de monitor por dos razones: los tokens son imposibles de adivinar y desacoplan el endpoint de ping del modelo de datos interno.

grace_period_seconds predeterminado en 300 (5 minutos). Una tarea que se ejecuta a las 2:00am y hace ping a las 2:04am no esta retrasada -- esta dentro de tolerancia.

last_ping_at es anulable. Un monitor nuevo nunca ha recibido un ping. No alertamos en monitores que nunca han hecho ping.

Generacion de tokens

rustfn generate_ping_token() -> String {
    use rand::Rng;
    let mut rng = rand::thread_rng();
    let bytes: Vec<u8> = (0..32).map(|_| rng.gen()).collect();
    hex::encode(bytes)
}

32 bytes aleatorios, codificados en hexadecimal a 64 caracteres. Eso son 256 bits de entropia -- mas combinaciones posibles que atomos en el universo observable.

Registro de pings

rustpub async fn record_ping(ping_token: &str, db: &PgPool) -> AppResult<()> {
    let now = Utc::now();
    let result = sqlx::query("UPDATE monitors SET last_ping_at = $1 WHERE ping_token = $2")
        .bind(now).bind(ping_token).execute(db).await?;
    if result.rows_affected() == 0 {
        return Err(AppError::NotFound(format!("Monitor with token '{ping_token}' not found")));
    }
    Ok(())
}

Una sola sentencia SQL. El endpoint de la API es GET /v1/ping/{token}. Si, GET, no POST. Las solicitudes GET son la solicitud HTTP mas simple posible. Funcionan con curl, con wget, e incluso pegando la URL en un navegador.

Deteccion de monitores vencidos

rustpub async fn check_monitors(db: &PgPool) -> AppResult<Vec<Monitor>> {
    let overdue = sqlx::query_as::<_, Monitor>(
        "SELECT * FROM monitors WHERE status = 'active'
         AND last_ping_at IS NOT NULL
         AND last_ping_at + (grace_period_seconds || ' seconds')::interval < NOW()",
    ).fetch_all(db).await?;
    Ok(overdue)
}

La expresion last_ping_at + (grace_period_seconds || ' seconds')::interval construye una marca de tiempo que representa "el ultimo ping mas el periodo de gracia." La aritmetica de intervalos de PostgreSQL es una funcionalidad de primera clase.

Casos de uso

Tareas de respaldo. Anade curl https://0cron.dev/v1/ping/TOKEN como la ultima linea de tu script. Si el respaldo falla, el curl nunca se ejecuta, y 0cron te alerta.

Pipelines CI/CD. Anade un ping al final del pipeline, establece el periodo de gracia a 1800 segundos. Si un despliegue se cuelga, la falta de ping dispara una alerta.

Salud de servicios externos. Monitoreo para un servicio que no controlas.

Dispositivos IoT. Un nodo sensor que deberia reportar cada 15 minutos. Si se calla, el patron de ping-y-silencio funciona.

105 lineas

Todo el monitoreo heartbeat -- modelo de datos, generacion de tokens, registro de pings, deteccion de vencidos -- son 105 lineas de Rust. Esto es posible porque tomamos decisiones agresivas de alcance. Una marca de tiempo en lugar de una tabla de historial. GET en lugar de POST. Periodos de gracia fijos en lugar de umbrales adaptativos.


Este es el articulo 8 de 10 en la serie "Como construimos 0cron".

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude zerosuite

Funciona, y no está terminado

El director recorrió él mismo todos los canales de senndo — cinco canales, de uno en uno y en campaña, la importación, las estadísticas, un reembolso, la API — y todo respondió. El archivo de seguimiento seguía diciendo que no, y la única línea que bloqueaba no era código: era un documento que había dejado de ser cierto en silencio. Cuatro afirmaciones ciertas al escribirse y falsas al leerse, y las guardas legibles por una máquina que ahora atrapan cada una de esas formas.

12 min Sep 14, 2026
senndocpaaslaunch-readinessdocumentation +8
Thales & Claude zerosuite

El navegador en manos de Claude: manejar el propio Chrome del CEO

Claude-in-Chrome permite que una sesión de Claude Code maneje el navegador real del director — mismo perfil, mismas sesiones abiertas. Qué hace la herramienta en realidad, por qué es mejor que pedirle a una persona que haga clic y lo cuente, y dónde el humano sigue ganando. Anclado en el día en que Claude recorrió un alta de cliente completa en la consola de producción de senndo, con mensajes facturados incluidos.

9 min Aug 18, 2026
claude-in-chromebrowser-automationclaude-codeclaude-fable-5 +9
Thales & Claude deblo

El segfault que no era nuestro: cómo lanzamos el tracking del día de lanzamiento de Déblo en la noche del despliegue — analítica condicionada por entorno, atribución nativa de las tiendas, tres bugs que el compilador no podía ver y un build sin memoria que diagnosticamos en lugar de revertir

El 1 de julio de 2026 — el día del lanzamiento — el riesgo nunca fue el texto. Era que las campañas de pago salieran a ciegas. Este es el build-log de cómo desplegamos la analítica y la atribución de instalaciones de Déblo como código en la noche del lanzamiento: etiquetas GA4, Meta y LinkedIn condicionadas por entorno que se despliegan sin riesgo antes de que existan las cuentas publicitarias; atribución enrutada por los canales nativos de las tiendas en lugar del pixel web; una auditoría adversarial que atrapó tres bugs que tanto el typechecker como el build dieron por buenos; y un despliegue en Easypanel que hizo segfault en el primer build — que demostramos que no era nuestro código antes de tocar una sola línea.

18 min Jul 1, 2026
deblolaunch-dayclaude-opus-4.8claude-code +26