cdylib + backend architecture, dump mode and oplog incrementals, point-in-time replay, Ed25519-signed manifests, Prometheus exporter, and license isolation for Bacula Community 15.0.3+ and Bacularis.
Companion document to the PodHeitor MongoDB plugin page.
1. The problem: stock Bacula can’t back up MongoDB safely
The Bacula File Daemon, on its own, backs up files. MongoDB in production is not a coherent set of files at any single point in time: copying /var/lib/mongo while mongod is running yields an inconsistent snapshot that fails --repair or — worse — silently opens in a corrupt state. Traditional alternatives each have their own cost:
- Bacula Enterprise MongoDB plugin — only does full
mongodump; no oplog incrementals, no PITR, no signed manifest, no metrics exporter. - Veeam / Commvault — per-TB pricing scales poorly across large MongoDB clusters; PITR still depends on hand-rolled oplog handling.
- Home-grown
mongodumpscripts — ignore read preference, hammer the PRIMARY, fail to detect oplog rotation gaps, and the operator only finds out at restore time.
PodHeitor MongoDB closes those gaps with cross-DB and cross-collection parallelism, persistent oplog tip, PIT replay by timestamp or incremental counter, and RPM/DEB packaging with hardened systemd unit.
2. Architectural model
The plugin follows the PodHeitor cdylib + standalone backend pattern with PTCOMM (length-tagged framing over stdin/stdout):
bacula-fd (root, daemon)
│
│ dlopen
▼
/opt/bacula/plugins/podheitor-mongodb-fd.so (Rust cdylib)
│
│ fork+exec, PTCOMM stdin/stdout
▼
/opt/bacula/bin/podheitor-mongodb-backend (per-job subprocess)
├── mongodump / mongorestore
├── stats sidecar → /opt/bacula/working/podheitor-mongodb-<r>-stats.json
├── oplog tip file → ...-tip.json
└── manifest + topology + RestoreObject → Bacula catalog (FT_RESTORE_FIRST=25)
│
▼
/opt/bacula/bin/podheitor-mongodb-exporter
(HTTP :9105/metrics, bacula:bacula, hardened systemd)
The split exists for the same reasons as HPC: crash isolation (a panic in MongoDB-touching code can’t wedge bacula-fd), parallelism freedom (the backend hosts tokio + cross-DB threadpools without violating Bacula’s “one thread per bpContext” contract), and license firewall (the subprocess never touches the Bacula ABI; only the cdylib does, exclusively via the in-house bacula-fd-abi crate with independent extern "C").
3. Supported backup modes (v1.0)
| Mode | Mechanism | v1.0 status |
|---|---|---|
dump |
Logical mongodump streaming, BSON over PTCOMM |
GA — JobId 7984/7985 validated |
dump multi-DB pipelined |
N DBs in parallel, configurable cap (parallel_dbs=4) |
GA — JobId 7906 |
dump per-collection chunking |
Splits big collections by _id via $bucketAuto |
GA — JobId 7925/7927 |
Oplog incremental (level=I) |
applyOps over the wire — real incrementals, not diff-of-dump |
GA — JobId 8037-8041 |
| PIT replay | second / RFC 3339 / :inc / @inc |
GA — JobId 8065-8099 |
snapshot (LVM/ZFS) |
Code stub | Phase 3 — pending LVM/ZFS-backed lab |
hot_backup (Percona createBackup) |
Code stub | Phase 4 — pending Percona Server lab |
| Sharded cluster orchestration | Code stub | Phase 6 — pending |
replicate (DR clone) |
Code stub | Phase 7 — pending |
4. SECONDARY-aware source picker
A backup that hammers PRIMARY is a backup that costs SLA. The plugin parses the replicaSet connection string, runs rs.status(), and picks a healthy SECONDARY (readPreference=secondaryPreferred honoured). If no SECONDARY is reachable, it falls back to PRIMARY with an explicit warning in the job log — documented behaviour, never silent.
Field-validated: JobId 7728/7996.
5. Oplog incremental — the piece Bacula Enterprise lacks
Real MongoDB incrementals require the oplog. The plugin maintains a persistent oplog tip at /opt/bacula/working/podheitor-mongodb-<resource>-tip.json: each level=I job reads the previous job’s tip, opens a tailable cursor on local.oplog.rs from that timestamp, and streams every applyOps entry up to the current tip.
Rotation-gap detection is mandatory: if the oplog wrapped between two jobs, the old tip is gone. The plugin detects this by comparing the first entry’s ts against the expected tip and aborts with a clear message — the operator must run a fresh Full. Validated in JobId 8047-8050.
6. Point-in-time replay
Granular restore to an arbitrary instant without restoring the entire chain at the destination:
# By absolute timestamp (RFC 3339)
pluginoptions "podheitor-mongodb: mode=restore pit=2026-04-15T14:30:00Z dry_run=true"
# By incremental counter within the day
pluginoptions "podheitor-mongodb: mode=restore pit=:inc=12"
# By Unix second
pluginoptions "podheitor-mongodb: mode=restore pit=1713187800"
dry_run=true makes the plugin compute the chain (Full + N incrementals) and emit a preview to the job log without applying — useful for the operator to confirm before a destructive restore. Validated in JobId 8097/8099.
The per-restore sidecar JSON removes the need to restart the FD to change restore parameters — the sidecar is read each job and discarded afterward (Phase 5ε, JobId 8090/8092).
7. Signed manifest and BLAKE3 verification
Every backup emits a JSON manifest cataloging what was saved: list of DBs, collections, document counts, oplog tip, replica set topology at backup time. The manifest is BLAKE3-hashed and Ed25519-signed with a key configured via env var (PODHEITOR_MONGODB_SIGNING_KEY).
At restore the plugin verifies the signature before applying — protects against post-backup tampering (ransomware that tries to rewrite backups before detonating). The key lives outside the FD; artefacts can be audited independently via verify-artifacts.sh.
8. Packaging and Prometheus exporter
v1.0 ships RPM (EL9) + DEB (Debian 12 / Ubuntu 22.04), signed (RPM in-archive + DEB detached GPG), validated by a 5-distro smoke matrix on podman (5/5 PASS). The Prometheus exporter exposes at :9105/metrics:
podheitor_mongodb_exporter_scrapes_totalpodheitor_mongodb_backup_duration_seconds{resource}podheitor_mongodb_oplog_lag_seconds{resource}podheitor_mongodb_last_success_timestamp{resource}
The exporter runs as bacula:bacula under a hardened systemd unit (ProtectSystem=strict, NoNewPrivileges=yes, PrivateTmp=yes), reading only the stats sidecars in the working dir.
9. Sample FileSet (replica set)
FileSet {
Name = "MongoDB-RS-Dump"
Include {
Options { Signature = SHA256 }
Plugin = "podheitor-mongodb: mode=dump uri=mongodb://backup_user@rs0-1,rs0-2,rs0-3/?replicaSet=rs0&readPreference=secondaryPreferred&authSource=admin database=* parallel_dbs=4 verify=true"
}
}
Job {
Name = "MongoDB-Backup"
Type = Backup
Client = mongo-fd
FileSet = "MongoDB-RS-Dump"
Storage = File1
Pool = Default
Schedule = "WeeklyCycle"
}
10. Supported versions (v1.0)
- MongoDB Community 4.4, 5.0, 6.0, 7.0, 8.0
- Percona Server for MongoDB 6.0, 7.0, 8.0 (
hot_backupmode in Phase 4 — code-stub) - MongoDB Enterprise 7.0+ (
$backupCursor) — Phase 9 / v1.5 - Bacula Community 15.0.3+ and Bacularis
11. License posture
The plugin ships under PROPRIETARY single-license. The backend never touches the Bacula ABI.
Ready to evaluate?
30-day free trial for qualified MongoDB clusters (Community, Percona, Enterprise). We guarantee at least 50% off vs Bacula Enterprise, Veeam, or Commvault, with more features included — including PIT replay and signed manifests that no competitor delivers out-of-the-box.
Heitor Faria — Founder, PodHeitor International
✉ [email protected]
☎ +1 (789) 726-1749 · +55 (61) 98268-4220 (WhatsApp)
🔗 PodHeitor MongoDB plugin page
Disponível em:
Português (Portuguese (Brazil))
English
Español (Spanish)