Monitoring¶
MongoRescue reports failed backups itself, through notifications. It cannot report its own failure: when the process crashes, the host is down, the scheduler hangs or the disk is full, the process that would send the alert is the one that stopped, and a silent failure looks like "no news". These mechanisms cover that gap, and they work best together:
| Mechanism | Catches | Needs |
|---|---|---|
| Global heartbeat | The process or host is down, the scheduler hangs | An external dead-man's-switch service |
| Job heartbeats | A job stops running, runs too long or fails | The same service, one check per job |
| Health check | A hung scheduler, for Docker, load balancers and uptime monitors | Nothing |
| Prometheus alert rules | Down instances, failures, missed RPOs, integrity problems | Prometheus and Alertmanager |
Global heartbeat¶
A dead-man's switch alerts when an expected ping does not arrive. Set Settings → Monitoring → Heartbeat URL (monitoring.heartbeat_url) to the ping URL of a check, and Ping every (monitoring.heartbeat_interval, 1 to 60 minutes, default 5) to how often it is pinged. MongoRescue sends GET <url> at start and then every interval, but only while the scheduler is healthy (its last tick is at most 90 seconds old): a hung scheduler stops the pings, so the monitor alerts even though the process still answers.
Configure the check at the external service with the same period and a grace time of a few minutes (for example period 5 minutes, grace 5 minutes). When the process is killed or the host goes down, the monitor alerts within one interval plus the grace time.
Send test ping (at most 5 per minute) sends one ping to the URL in the field (or, when the field shows the stored, masked URL, to the stored one) and reports whether the monitor answered with a 2xx status. The same is POST /api/v1/settings/monitoring/test (API).
Job heartbeats¶
Every job can have a heartbeat URL of its own (the Heartbeat URL field of the job form, heartbeat_url in the API). It follows the healthchecks.io protocol:
| Event | Ping |
|---|---|
| A run starts (scheduled or on demand, all its databases) | <url>/start |
The run succeeded (ok) |
<url> |
The run failed or was partial (also a run that could not start) |
<url>/fail |
| The run was cancelled by a user, or interrupted by a shutdown | nothing |
A cancellation is a deliberate action, so it pages nobody: the check stays "started" and the monitor reports it as late when no successful run follows within its period and grace time, which is the right signal for a backup that did not happen.
The suffix is appended to the URL's path; a query string is kept (https://push.example.com/api/push/abc?status=up becomes .../abc/fail?status=up). With a /start ping the service also measures how long each run takes and alerts when a run does not finish. Give the check the job's schedule (healthchecks.io accepts cron expressions) and a grace time longer than a run takes.
How pings are sent¶
- Pings never block or fail a backup: they are sent in the background, at most 32 at a time (queued or in flight; further ones are dropped, logged and counted in
mongorescue_heartbeat_dropped_total), every attempt has a 10 second timeout, and a network error,408,429or5xxis retried twice (after 2 and 4 seconds). Other4xxanswers are not retried. The pings of one job are sent in order, so a/startnever arrives after the outcome. - Requests go through the same HTTP client as notifications: redirects are not followed, link-local and cloud metadata addresses are refused (also after DNS resolution), and
HTTPS_PROXY/HTTP_PROXY/NO_PROXYapply. - Heartbeat URLs carry the check's token, so they are secrets: they are stored encrypted (
secret.key), shown only up to their host (https://hc-ping.com/******) by the dashboard, the API and MCP, and never logged; failures are logged with the host and a fixed reason only (connection refused,timeout,TLS error,DNS error), never the resolved address. A stored URL that can no longer be decrypted is dropped: the job keeps running without its heartbeat and the URL is reported among the unreadable records until it is entered again. Sending the masked value back keeps the stored URL; a masked value for another host is refused, so a URL is never kept for a host it was not entered for. - At shutdown, pings already queued are still sent, for up to 15 seconds; runs the shutdown interrupts send nothing after their
/start.
Health check¶
GET /api/v1/health (no authentication) answers 503 with "scheduler": "stale" when the scheduler's last liveness tick is older than 90 seconds (three ticks), and 200 with "scheduler": "ok" and scheduler_last_tick otherwise; status keeps its value, so checks must read the HTTP status or scheduler (see api.md). The Docker image's HEALTHCHECK uses it, so a container with a hung scheduler is reported unhealthy; point load balancer and uptime checks at it too. In active-passive HA mode it adds role (active or standby) and ha (cluster, epoch, holder, lease health); a standby answers 200 with "scheduler": "not_started".
Clock steps¶
The liveness tick runs every 30 seconds on a monotonic timer of its own, not on the cron that starts jobs, and its age is measured on the monotonic clock. A wall clock step (an NTP correction, a restored VM snapshot, a manual change) therefore never delays the tick nor makes the scheduler stale: the health check stays 200, the heartbeat keeps pinging and MongoRescueSchedulerStale does not fire. Only a scheduler that cannot take its own lock (a deadlock) or a stopped one stops ticking.
Scheduled jobs follow the wall clock, since cron expressions name wall-clock times:
- Forward step. Every job whose next run the clock jumped over runs once, late, when the scheduler next wakes up (within about a minute: its own checks run every minute), and then on its schedule from the new time. Missed runs are not caught up one by one.
- Backward step. Jobs wait until the clock reaches their next run again, so their runs are delayed by the size of the step. A step never repeats a run by itself. A job whose schedule is rebuilt after the step (it is edited or re-enabled, or MongoRescue restarts) could be due again at a time that already ran; MongoRescue skips such a trigger when the job's previous scheduled run started less than half its interval earlier (measured on the monotonic clock, or after a restart on the start time of the newest scheduled run on record), and logs
skipping a repeated scheduled run. After a restart at most one trigger is skipped this way.
Prometheus alert rules¶
deploy/prometheus/alerts.yml holds alerting rules over the metrics, unit-tested with promtool test rules in CI (alerts_test.yml; run them with make test-prometheus-rules):
| Alert | Severity | Fires when |
|---|---|---|
MongoRescueDown |
critical | up == 0 for 5 minutes on a target that exposed mongorescue_build_info in the last 7 days |
MongoRescueSchedulerStale |
critical | The last scheduler tick is older than 90 seconds, for 2 minutes |
MongoRescueBackupFailed |
warning | A backup of a job (or a manual backup, job="manual") failed in the last hour, or a run failed before any backup started |
MongoRescueRPOMissed |
critical | mongorescue_job_rpo_met == 0 for 5 minutes: a database is past its job's recovery point objective |
MongoRescueVerificationFailed |
critical | An archive verification (after upload, integrity sweep or on demand) found a mismatch or could not read the archive in the last hour |
MongoRescueArchivesMissing |
critical | The last storage scan did not find the archives of some backup records |
MongoRescueRestoreTestFailed |
warning | An automated restore test failed or found differences in the last 24 hours |
MongoRescueMetadataBackupFailed |
warning | A metadata snapshot failed in the last 24 hours |
MongoRescueSettingsWarning |
warning | A settings warning (encryption off, recovery kit missing or outdated, ...) has been active for an hour |
MongoRescueHATakeoverFailed |
critical | A standby of an HA cluster acquired the lease in the last 15 minutes but found no usable metadata snapshot to take over from, and stayed standby |
MongoRescueHAStartedFromEmptyState |
critical | An instance of an HA cluster found no lease and no metadata snapshot on the HA target in the last 15 minutes and became active with its own database. Expected once for a new cluster; otherwise check the HA target and run a storage scan of every storage target to re-import the archives |
MongoRescueHARollbackSuspected |
critical | A standby of an HA cluster refused a takeover in the last 15 minutes because it could roll the metadata back (the lease is not authentic or missing, or the newest snapshot is older than the lease or the instance knows). No instance becomes active until an operator checks the HA target and runs mongorescue ha force-takeover (how) |
MongoRescueHASnapshotRejected |
critical | An instance of an HA cluster found a refused metadata snapshot on the HA target (it lasts until the snapshot is deleted) or refused one in the last 15 minutes: its authenticated header does not match its name or cluster (copied or renamed on the HA target), or it does not decrypt (altered). It is never restored nor deleted; delete it after the investigation (how) |
MongoRescueHANoActiveInstance |
critical | No instance of an active-passive HA cluster has reported role="active" for a minute (twice the default lease TTL), while one of them exported mongorescue_ha_role in the last 10 minutes |
Load the file in prometheus.yml. The rules work with a default scrape configuration under any scrape job name: they select no job and do not need honor_labels. By default Prometheus renames the job label of per-job series (the MongoRescue job ID) to exported_job; the rules group by job and exported_job and name the job from whichever holds it, so honor_labels: true works too.
MongoRescueDown recognises MongoRescue targets by mongorescue_build_info instead of a job name, so it needs nothing to edit; an instance that has been down for more than 7 days drops out of it (the external heartbeat keeps alerting), and a target that was never scraped successfully is not covered. Prometheus only notices that MongoRescue is down while Prometheus itself runs; the external heartbeat covers the case where both are on the same failed host.
Recommended external services¶
Any service that alerts when an HTTP GET ping stops arriving works for the global heartbeat. The job heartbeats need one that understands the /start and /fail paths:
- healthchecks.io supports both, hosted or self-hosted (it is open source). It is the reference for the protocol above.
- Other heartbeat services with a plain ping URL, such as Better Stack heartbeats, Cronitor heartbeat monitors, Uptime Kuma push monitors (self-hosted) or Dead Man's Snitch, work as the global heartbeat. Check their documentation before using them for job heartbeats: a service without the
/startand/failpaths answers them with an error, which MongoRescue logs and does not retry.
Run the monitor somewhere other than MongoRescue's host (a hosted service, or another machine), or it fails together with MongoRescue.