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 deblo

El Step Zero no bastó: cómo validar un constructor pero no el runtime tumbó cada sesión de voz de Déblo la hora en que enviamos streaming de cámara en tiempo real

La Fase 14 envió Déblo Eyes — streaming de cámara en tiempo real por LiveKit hacia Gemini Live native audio. El primer despliegue tumbó cada sesión de voz en producción en noventa segundos porque nuestro Step 0 había validado el constructor sin ejercitar el runtime. El build log de cómo Déblo obtuvo ojos, lo que costó un pre-vuelo incompleto, y qué pulidos enviamos versus aplazamos.

33 min May 20, 2026
debloclaude-opus-4.7claude-codegemini-live +25
Thales & Claude deblo

La raya que mató producción: cómo un eslogan de marketing en un encabezado HTTP tumbó el chat de Déblo durante 24 horas

Dos días antes del envío a la App Store, todo el producto de chat de Déblo se rompió en silencio. Sin spinner, sin toast, sin error en la UI — solo aire muerto. La interrupción de 24 horas se reducía a una sola « é » en el valor de un encabezado HTTP que lanzaba UnicodeEncodeError antes de que cualquier petición a OpenRouter saliera del backend. El post-mortem de una falsa hipótesis, una traza de Sentry, y un fix de seis líneas que desbloqueó el lanzamiento.

29 min May 19, 2026
debloclaude-opus-4.7claude-codeincident +19
Thales & Claude deblo

Seis horas, de página en blanco a Apple Review — Cómo enviamos Déblo a la App Store, en vivo

Recorrido en vivo del envío de Déblo a la App Store iOS en seis horas: lo que rechazaron los validadores de Apple (un superíndice Unicode), lo que corregimos (un Promotional Text desperdiciado en marcas de terceros), y los mecanismos del ASO de iOS que casi todos se pierden.

30 min May 13, 2026
debloclaude-opus-4.7claude-codeapp-store +16