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.

Thales & Claude | March 30, 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