Cinco modos de backup (dump / parallel_dump / pitr / pitr_block / cdp), captura de replication slots e subscriptions, restore automatizado em 6 fases, e PG17 block-level via pg_basebackup --incremental.
Documento técnico complementar à página do plugin PodHeitor PostgreSQL.
1. Problema: Bacula stock + PostgreSQL é frágil
Backup de PostgreSQL com Bacula stock se reduz tipicamente a uma das três opções:
- Filesystem-level backup do
$PGDATAsem coordenação — captura arquivos torn-write, restore é WAL-replay loteria. pg_dumpem RunBeforeJob — staging duplicado em disco; sem PITR; sem incremental real.- Bacula Enterprise PostgreSQL plugin — wrappers Perl em volta de
pg_dump/pg_basebackup, sem PITR_BLOCK, sem CDP, sem captura de replication state.
O PodHeitor PostgreSQL Plugin entrega cinco modos de backup nativos, captura de replication slots/subscriptions, e restore automatizado em 6 fases — todos selecionáveis pelo Bacula Director sem scripts externos.
2. Modelo arquitetural
Bacula metaplugin que fala PTCOMM em stdin/stdout entre o cdylib FD (Rust .so) e o backend Rust. O backend orquestra cada passo PostgreSQL-aware: pg_dump, pg_backup_start/pg_backup_stop, gestão de WAL archive, enumeração per-file de $PGDATA, walks de tablespace, captura de logical replication slots, escrita automatizada de recovery configuration, e verificação pós-restore.
v2.0.0 (abril 2026) — o cdylib é construído a partir do crate plugin-postgresql no workspace PodHeitor Rust cdylib. Nenhum source AGPLv3 do Bacula é vinculado estaticamente. O shim C++ legacy que linkava pluginlib/metaplugin.o foi removido.
3. Cinco modos de backup
| Mode | Função | Namespace de saída |
|---|---|---|
dump (default) |
pg_dump lógico per-database |
@postgresql/dump/<db>.dump |
parallel_dump |
Dump multi-DB com 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 via pg_basebackup --incremental + pg_combinebackup |
@postgresql/pitr_block/ |
cdp |
Streaming contínuo de WAL (continuous data protection) | @postgresql/cdp/wal/ |
3.1 VLDB split — chunking COPY-range
Tabelas com PK numérica acima de large_table_threshold (default 10G) são auto-split em ranges de PK e processadas por workers paralelos do parallel_dump mode. Em DBs com tabelas-monstro single-threaded em pg_dump, isso transforma um job de 8 horas em um de ~1 hora com parallel_tables=8.
4. Captura de replication state
Com track_replication_state=true, o plugin captura no _manifest.json per-job:
- Physical + logical replication slots de
pg_replication_slots(name, type, plugin, active, restart_lsn, confirmed_flush_lsn). - Subscriptions de
pg_subscriptioncom DDLCREATE SUBSCRIPTION ... WITH (slot_name=..., create_slot=false, enabled=...)sintetizada.LEFT JOIN pg_databasegarante que cada subscription apareça uma vez com sua DB target real (fix v1.3.0 dedup). - Source role —
primaryoustandby, derivado viapg_is_in_recovery(). Acessores primary-only vs standby-only protegidos comCASEpara a mesma query funcionar em ambos.
No restore, com restore_replication_state=true (default), o plugin parseia o manifesto e recria slots ausentes via pg_create_{physical,logical}_replication_slot() contra o cluster target. Subscriptions que estavam disabled são recriadas com enabled=false por default — restore nunca acidentalmente liga replicação que estava off no backup.
5. Restore automatizado em 6 fases
O fluxo de restore roda seis fases idempotentes que honram dry_run_restore=true:
- Pre-flight — autodetecta unidade systemd, para o PG, opcional
mv PGDATA → PGDATA.old.<UTC-ts>(rollback de 5 segundos), cria$PGDATA+wal_restore_dircom ownership correto. - Per-file receipt — dispatcha FNAME entrando por prefixo de vpath: PGDATA files, tablespace entries, WAL segments,
backup_label,tablespace_map,_manifest.json. - Recovery config — anexa um bloco PodHeitor-managed claramente delimitado em
$PGDATA/postgresql.auto.confcomrestore_command+recovery_target_{time,lsn,xid,name}fornecidos pelo operador +recovery_target_action(promote/pause/shutdown). Tocarecovery.signal. - Start + monitor — opcional
systemctl start; polllapg_is_in_recovery()+pg_last_wal_replay_lsn()até a target action convergir ourestore_timeoutdisparar. - Verify — sanity checks SQL pós-recovery; opcional
pg_checksums --check; opcional script SQL fornecido pelo operador. - Cleanup — purge de
PGDATA.old.<ts>mais antigos quepgdata_backup_retain_days; opcionalmente removewal_restore_dir.
6. Backup-from-standby + multi-cluster
| Parâmetro | Default | Função |
|---|---|---|
backup_from_standby |
false | Conecta a um standby read-only via PGTARGETSESSIONATTRS=read-only; offload completo do primary |
cluster_id |
(vazio) | Prefixo de namespace multi-cluster: @postgresql/cluster_<id>/... |
track_replication_state |
false | Captura slots + subscriptions no manifest |
compress |
zstd | zstd, lz4, gzip, none |
archive_dir |
auto | Auto-derivado de SHOW archive_command (PITR only) |
7. Compatibilidade
| Componente | Versões suportadas |
|---|---|
| PostgreSQL | 12, 13, 14, 15, 16, 17 (incremental & VLDB requerem 17) |
| Bacula Community | 15.0.3+ (testado em Oracle Linux 9.6) |
| Bacula Enterprise | 18.0+ (com patches open_bpipe + FD_PLUGIN_INTERFACE_VERSION) |
| OS (host plugin) | glibc 2.17+ ou musl-static: CentOS 7, Rocky/Alma/OL 8/9, RHEL 8/9, Debian 11+, Ubuntu 20.04+ |
| Arch | x86_64 |
Binário backend é musl static-pie ~500 KB — portável entre distros sem dependência de glibc específica.
8. Comparação 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 | ✅ | ✅ |
| Automated 6-phase restore | ⚠️ (manual) | ✅ (com 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
- Não rode Differential ou Incremental sem
Accurate = yesno Job. Bacula precisa do walker Accurate para filtrar arquivos não modificados; sem isso, “incremental” envia tudo. - Não use
start_postgresql_after_restore=trueem prod accidental. Default éfalseexatamente para evitar auto-start de cluster restaurado em ambiente errado. - Não confunda
archive_dircomwal_restore_dir. O primeiro é onde o source escreveu WALs; o segundo é onde o restore os põe para replay. - Não desabilite
wipe_pgdatasafety rename. O defaultfalseé proteção; mudar paratruesem entender omv PGDATA → PGDATA.old.<ts>de 5s pode levar a perda de dados.
10. License posture
Single-license proprietary, AGPL-clean desde v2.0.0. Ambos os binários (backend + cdylib FD) são proprietary. Nenhum source AGPLv3 do Bacula é vinculado estaticamente. Releases ≤ v1.3.0 distribuíram um shim C++ que linkava objetos do Bacula Community — esse shim foi removido em v2.0.0.
Pronto para avaliar?
Trial gratuito de 30 dias para frotas PostgreSQL em produção (incluindo PG17 com PITR_BLOCK e VLDB split). Garantimos no mínimo 50% de desconto vs Bacula Enterprise, Veeam ou Commvault, com mais funcionalidades inclusas.
Heitor Faria — Fundador, PodHeitor International
✉ [email protected]
☎ +1 (789) 726-1749 · +55 (61) 98268-4220 (WhatsApp)
🔗 Página do plugin PodHeitor PostgreSQL
Disponível em:
Português
English (Inglês)
Español (Espanhol)