Cinco modos de backup (dump / parallel_dump / pitr / pitr_block / cdp), captura de replication slots y subscriptions, restore automatizado en 6 fases, y PG17 block-level vía pg_basebackup --incremental.
Documento técnico complementario a la página del plugin PodHeitor PostgreSQL.
1. Problema: Bacula stock + PostgreSQL es frágil
El backup de PostgreSQL con Bacula stock se reduce típicamente a una de tres opciones:
- Backup filesystem-level del
$PGDATAsin coordinación — captura archivos torn-write, el restore es lotería de WAL-replay. pg_dumpen RunBeforeJob — staging duplicado en disco; sin PITR; sin incremental real.- Plugin Bacula Enterprise PostgreSQL — wrappers Perl alrededor de
pg_dump/pg_basebackup, sin PITR_BLOCK, sin CDP, sin captura de replication state.
El PodHeitor PostgreSQL Plugin entrega cinco modos de backup nativos, captura de replication slots/subscriptions, y restore automatizado en 6 fases — todos seleccionables desde el Bacula Director sin scripts externos.
2. Modelo arquitectural
Metaplugin Bacula que habla PTCOMM sobre stdin/stdout entre el cdylib FD (Rust .so) y el backend Rust. El backend orquesta cada paso PostgreSQL-aware: pg_dump, pg_backup_start/pg_backup_stop, gestión de WAL archive, enumeración per-file de $PGDATA, walks de tablespace, captura de logical replication slots, escritura automatizada de recovery configuration y verificación post-restore.
v2.0.0 (abril 2026) — el cdylib se construye desde el crate plugin-postgresql en el workspace PodHeitor Rust cdylib. Ningún source AGPLv3 de Bacula se vincula estáticamente. El shim C++ legacy que linkeaba pluginlib/metaplugin.o fue removido.
3. Cinco modos de backup
| Mode | Función | Namespace de salida |
|---|---|---|
dump (default) |
pg_dump lógico per-database |
@postgresql/dump/<db>.dump |
parallel_dump |
Dump multi-DB con worker pool | @postgresql/dump/<db>.dump |
pitr |
Físico per-file: pg_backup_start/stop + walk de $PGDATA + WAL window |
@postgresql/pitr/{pgdata/, tblspc/, wal/, backup_label, tablespace_map, _manifest.json} |
pitr_block |
PG17+ block-level vía pg_basebackup --incremental + pg_combinebackup |
@postgresql/pitr_block/ |
cdp |
Streaming continuo de WAL (continuous data protection) | @postgresql/cdp/wal/ |
3.1 VLDB split — chunking COPY-range
Las tablas con PK numérica por encima de large_table_threshold (default 10G) se auto-splittean en rangos de PK y son procesadas por workers paralelos en modo parallel_dump. En DBs con tablas-monstruo single-threaded en pg_dump, esto convierte un job de 8 horas en ~1 hora con parallel_tables=8.
4. Captura de replication state
Con track_replication_state=true, el plugin captura en el _manifest.json per-job:
- Physical + logical replication slots de
pg_replication_slots(name, type, plugin, active, restart_lsn, confirmed_flush_lsn). - Subscriptions de
pg_subscriptioncon DDLCREATE SUBSCRIPTION ... WITH (slot_name=..., create_slot=false, enabled=...)sintetizada.LEFT JOIN pg_databasegarantiza que cada subscription aparezca una vez con su DB target real (fix v1.3.0 dedup). - Source role —
primaryostandby, derivado víapg_is_in_recovery(). Accesores primary-only vs standby-only protegidos conCASEpara que la misma query funcione en ambos.
En el restore, con restore_replication_state=true (default), el plugin parsea el manifest y recrea slots ausentes vía pg_create_{physical,logical}_replication_slot() contra el cluster target. Las subscriptions que estaban disabled se recrean con enabled=false por default — el restore nunca activa accidentalmente replicación que estaba off en backup time.
5. Restore automatizado en 6 fases
El flujo de restore corre seis fases idempotentes que honran dry_run_restore=true:
- Pre-flight — autodetecta unidad systemd, para PG, opcional
mv PGDATA → PGDATA.old.<UTC-ts>(rollback de 5 segundos), crea$PGDATA+wal_restore_dircon ownership correcto. - Per-file receipt — dispatcha FNAME entrante por prefijo de vpath: PGDATA files, tablespace entries, WAL segments,
backup_label,tablespace_map,_manifest.json. - Recovery config — anexa un bloque PodHeitor-managed claramente delimitado en
$PGDATA/postgresql.auto.confconrestore_command+recovery_target_{time,lsn,xid,name}suministrados por el operador +recovery_target_action(promote/pause/shutdown). Tocarecovery.signal. - Start + monitor — opcional
systemctl start; polleapg_is_in_recovery()+pg_last_wal_replay_lsn()hasta que la target action converja orestore_timeoutdispare. - Verify — sanity checks SQL post-recovery; opcional
pg_checksums --check; opcional script SQL suministrado por el operador. - Cleanup — purge de
PGDATA.old.<ts>más antiguos quepgdata_backup_retain_days; opcionalmente remuevewal_restore_dir.
6. Backup-from-standby + multi-cluster
| Parámetro | Default | Función |
|---|---|---|
backup_from_standby |
false | Conecta a un standby read-only vía PGTARGETSESSIONATTRS=read-only; offload completo del primary |
cluster_id |
(vacío) | Prefijo de namespace multi-cluster: @postgresql/cluster_<id>/... |
track_replication_state |
false | Captura slots + subscriptions en manifest |
compress |
zstd | zstd, lz4, gzip, none |
archive_dir |
auto | Auto-derivado de SHOW archive_command (PITR only) |
7. Compatibilidad
| Componente | Versiones soportadas |
|---|---|
| PostgreSQL | 12, 13, 14, 15, 16, 17 (incremental & VLDB requieren 17) |
| Bacula Community | 15.0.3+ (probado en Oracle Linux 9.6) |
| Bacula Enterprise | 18.0+ (con patches open_bpipe + FD_PLUGIN_INTERFACE_VERSION) |
| OS (host del plugin) | glibc 2.17+ o musl-static: CentOS 7, Rocky/Alma/OL 8/9, RHEL 8/9, Debian 11+, Ubuntu 20.04+ |
| Arch | x86_64 |
El binario backend es musl static-pie ~500 KB — portable entre distros sin dependencia de glibc específica.
8. Comparación Bacula Enterprise vs PodHeitor
| Feature | Bacula Enterprise PostgreSQL | PodHeitor v2.0.0 |
|---|---|---|
DUMP mode (pg_dump) |
✅ | ✅ |
| Parallel DUMP multi-DB | — | ✅ |
| VLDB split COPY-range | — | ✅ |
| PITR Full + Diff + Inc (WAL-only) | ✅ | ✅ |
| PITR_BLOCK (PG17+) | — | ✅ |
| CDP (continuous WAL) | — | ✅ |
| Backup from standby | ✅ | ✅ |
| Replication slot capture | ✅ | ✅ |
| Subscription DDL capture | ✅ | ✅ |
| Restore automatizado 6 fases | ⚠️ (manual) | ✅ (con dry_run_restore) |
Recovery-target params (time/lsn/xid/name) |
— | ✅ |
| Tablespace remap | ⚠️ | ✅ (OID:/path) |
Rollback safety (mv antes de wipe) |
— | ✅ |
| Prometheus metrics | — | ✅ |
| Multi-cluster namespace | — | ✅ |
9. Anti-patterns documentados
- No corras Differential o Incremental sin
Accurate = yesen el Job. Bacula necesita el walker Accurate para filtrar archivos no modificados; sin esto, «incremental» envía todo. - No uses
start_postgresql_after_restore=truepor accidente en prod. Default esfalseprecisamente para evitar auto-start de cluster restaurado en el ambiente equivocado. - No confundas
archive_dirconwal_restore_dir. El primero es donde el source escribió WALs; el segundo es donde el restore los pone para replay. - No deshabilites el safety rename de
wipe_pgdata. Defaultfalsees la protección; voltearlo atruesin entender el rollback de 5smv PGDATA → PGDATA.old.<ts>puede llevar a pérdida de datos.
10. Postura de licencia
Single-license proprietary, AGPL-clean desde v2.0.0. Ambos binarios (backend + FD cdylib) son proprietary. Ningún source AGPLv3 de Bacula se vincula estáticamente. Los releases ≤ v1.3.0 distribuyeron un shim C++ que linkeaba objetos del Bacula Community — ese shim fue removido en v2.0.0.
¿Listo para evaluar?
Trial gratuito de 30 días para flotas PostgreSQL en producción (incluyendo PG17 con PITR_BLOCK y VLDB split). Garantizamos al menos 50% de descuento vs Bacula Enterprise, Veeam o Commvault, con más funcionalidades incluidas.
Heitor Faria — Fundador, PodHeitor International
✉ [email protected]
☎ +1 (789) 726-1749 · +55 (61) 98268-4220 (WhatsApp)
🔗 Página del plugin PodHeitor PostgreSQL
Disponível em:
Português (Portugués, Brasil)
English (Inglés)
Español