Перейти к содержимому

Heartbeat-уведомления (dead-man-switch)

Heartbeat — пассивный мониторинг по принципу «не позвонил — значит, что-то случилось». Ваш сервис/cron/скрипт регулярно дёргает короткий HTTP-эндпоинт Notifly, а если очередной ping не пришёл за заданное время — вы получаете push-уведомление с настраиваемым текстом.

Это особенно удобно для:

  • регулярных cron-задач (бэкапы, импорты, генераторы отчётов);
  • демонов, которые «должны быть всегда включены»;
  • IoT-устройств, которые «звонят домой»;
  • batch-пайплайнов, где важно не «успех», а «успех вовремя».
ваш cron ──ping──▶ POST /heartbeat/ping/<pingToken> (раз в N сек)
└─▶ Notifly обновляет last_ping и сдвигает next_check_at
каждую минуту: timer-trigger → notifly-heartbeat-checker
└─▶ SELECT WHERE next_check_at <= now AND status IN (ok, pending)
для каждого: создать сообщение + push в WS
  • Хранение — таблица heartbeats в YDB Serverless. Один индекс по next_check_at — checker сканирует только просроченные записи (дёшево).
  • Проверка — отдельная Cloud Function notifly-heartbeat, запускается таймер-триггером Yandex Cloud (cron * * * * ? * — раз в минуту).
  • Уведомление — обычное сообщение Notifly, отправляется через выбранное при создании heartbeat-а канал и доходит до всех ваших клиентов (web, Android, desktop), как любая другая push-нотификация.
  1. Откройте app.notifly.ruHeartbeats.
  2. Нажмите «Создать heartbeat», заполните:
    • Название — для отображения, например «Cron бэкапа базы».
    • Канал — через какой канал слать алёрт.
    • Интервал (сек) — ожидаемый период между ping-ами (минимум 30).
    • Допуск (сек) — сколько ещё ждать после интервала (защита от джиттера).
    • Текст alert — что прилетит в push, когда ping пропустит дедлайн.
    • Приоритет alert — 0–10, по умолчанию 8 (громкое уведомление).
    • Текст recovery — необязательное «всё снова в порядке», когда после алёрта приходит ping.
  3. Скопируйте Ping URL из таблицы — этим URL будет дёргать ваш скрипт.
Окно терминала
curl -X POST "$NOTIFLY_URL/heartbeat" \
-H "Content-Type: application/json" \
-H "X-Gotify-Key: <client-token>" \
-d '{
"appid": 12345,
"name": "Cron бэкапа базы",
"intervalSec": 3600,
"graceSec": 300,
"alertTitle": "Бэкап не запустился",
"alertMessage": "За последний час cron бэкапа не пришёл — проверьте сервер!",
"alertPriority": 9,
"recoveryMessage": "Бэкап снова работает."
}'

В ответе будет pingToken (начинается с H...) и итоговый URL вида https://<домен>/heartbeat/ping/<pingToken>.

Если у вас настроен MCP-сервер Notifly (см. MCP), просто попросите ассистента:

Создай heartbeat для канала «бэкапы», который ждёт ping раз в час с допуском 5 минут, и шлёт «Бэкап не запустился» приоритета 9.

MCP-инструменты: list_heartbeats, create_heartbeat, update_heartbeat, pause_heartbeat, resume_heartbeat, delete_heartbeat, ping_heartbeat.

Самый минимальный ping — обычный curl без авторизации:

Окно терминала
curl -fsS "$NOTIFLY_URL/heartbeat/ping/H<pingToken>" -o /dev/null

pingToken — это и есть аутентификация. Никаких других заголовков не нужно. Поддерживается и GET, и POST — чтобы можно было дёргать прямо из cron-job или мониторингов, которые не умеют POST.

*/15 * * * * /usr/local/bin/backup.sh && curl -fsS "$NOTIFLY_URL/heartbeat/ping/H..." -o /dev/null

Ping вызывается только при успехе скрипта (благодаря &&). Если бэкап упал — ping не пройдёт, и через intervalSec+graceSec прилетит alert.

В service-юните регулярного таймера:

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
ExecStartPost=/usr/bin/curl -fsS https://your-notifly/heartbeat/ping/H... -o /dev/null

ExecStartPost запускается только если основной ExecStart завершился успешно.

import requests, subprocess
subprocess.check_call(["/usr/local/bin/backup.sh"])
requests.post(f"{NOTIFLY_URL}/heartbeat/ping/{TOKEN}", timeout=10)
import {execSync} from 'child_process';
execSync('/usr/local/bin/backup.sh');
await fetch(`${NOTIFLY_URL}/heartbeat/ping/${TOKEN}`, {method: 'POST'});
СостояниеЧто значит
pendingСоздан, ни одного ping ещё не приходило
okПоследний ping в пределах интервала
alertingДедлайн пропущен, alert уже отправлен
pausedПроверка отключена (через UI или POST /heartbeat/:id/pause)

После отправки алёрта heartbeat не «спамит» — следующая проверка отложена ещё на intervalSec + graceSec секунд. Когда придёт первый ping после alerting — heartbeat вернётся в ok и (если задано) уйдёт recovery-уведомление.

Метод и путьАвторизацияНазначение
GET /heartbeatclient-tokenсписок heartbeat-ов
POST /heartbeatclient-tokenсоздание
PUT /heartbeat/:idclient-tokenобновление
DELETE /heartbeat/:idclient-tokenудаление
POST /heartbeat/:id/pauseclient-tokenприостановить проверки
POST /heartbeat/:id/resumeclient-tokenвозобновить проверки
GET/POST /heartbeat/ping/:tokenпубличныйping (token = аутентификация)

Архитектурно проверка «у каких heartbeat-ов next_check_at ≤ сейчас» — это индексный point-query, а не сканирование. В YDB Serverless у нас INDEX heartbeats_next_check_idx и одна короткая транзакция на минуту (несколько Request Units). В S3 пришлось бы делать LIST (1 запрос) + GET (по запросу на каждый объект), причём оплата у Object Storage идёт за каждый запрос, и таких запросов было бы много даже без алёртов. Heartbeat-ов в проекте быстро становится десятки и сотни — поэтому YDB заметно дешевле и быстрее.