Whitepaper técnico — PodHeitor PostgreSQL para Bacula

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 $PGDATA sem coordenação — captura arquivos torn-write, restore é WAL-replay loteria.
  • pg_dump em 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_subscription com DDL CREATE SUBSCRIPTION ... WITH (slot_name=..., create_slot=false, enabled=...) sintetizada. LEFT JOIN pg_database garante que cada subscription apareça uma vez com sua DB target real (fix v1.3.0 dedup).
  • Source roleprimary ou standby, derivado via pg_is_in_recovery(). Acessores primary-only vs standby-only protegidos com CASE para 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:

  1. Pre-flight — autodetecta unidade systemd, para o PG, opcional mv PGDATA → PGDATA.old.<UTC-ts> (rollback de 5 segundos), cria $PGDATA + wal_restore_dir com ownership correto.
  2. Per-file receipt — dispatcha FNAME entrando por prefixo de vpath: PGDATA files, tablespace entries, WAL segments, backup_label, tablespace_map, _manifest.json.
  3. Recovery config — anexa um bloco PodHeitor-managed claramente delimitado em $PGDATA/postgresql.auto.conf com restore_command + recovery_target_{time,lsn,xid,name} fornecidos pelo operador + recovery_target_action (promote/pause/shutdown). Toca recovery.signal.
  4. Start + monitor — opcional systemctl start; pollla pg_is_in_recovery() + pg_last_wal_replay_lsn() até a target action convergir ou restore_timeout disparar.
  5. Verify — sanity checks SQL pós-recovery; opcional pg_checksums --check; opcional script SQL fornecido pelo operador.
  6. Cleanup — purge de PGDATA.old.<ts> mais antigos que pgdata_backup_retain_days; opcionalmente remove wal_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 = yes no Job. Bacula precisa do walker Accurate para filtrar arquivos não modificados; sem isso, “incremental” envia tudo.
  • Não use start_postgresql_after_restore=true em prod accidental. Default é false exatamente para evitar auto-start de cluster restaurado em ambiente errado.
  • Não confunda archive_dir com wal_restore_dir. O primeiro é onde o source escreveu WALs; o segundo é onde o restore os põe para replay.
  • Não desabilite wipe_pgdata safety rename. O default false é proteção; mudar para true sem entender o mv 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: pt-brPortuguêsenEnglish (Inglês)esEspañol (Espanhol)

Deixe um comentário