Whitepaper técnico — PodHeitor Global Deduplication para Bacula

Engine de deduplicação global standalone que opera como sidecar do Storage Daemon, com chunking de tamanho variável (FastCDC), Bloom filter multicamadas, RocksDB index, deduplicação client-side via FD plugin, e métricas Prometheus — competindo diretamente com Bacula Enterprise GED e Commvault DDB.

Documento técnico complementar à página do plugin PodHeitor Global Dedup.

1. O problema: GED do Bacula Enterprise é fixed-block + TokyoCabinet

O GED (Global Endpoint Deduplication) do Bacula Enterprise usa chunking de tamanho fixo e TokyoCabinet B-tree como hash DB. Isso tem três limitações estruturais:

  • Cascade effect em modificações. Inserir 1 byte no início de um arquivo de 10 GB invalida todos os blocos subsequentes — o boundary fixed-block desalinha. Resultado: ratio de dedup colapsa em workloads de log, dump de DB, e snapshots de VM.
  • SSD bound em IOPS. Cada lookup TokyoCabinet é um seek SSD. Sem Bloom filter na frente, o caminho hot toca disco a cada bloco.
  • Sem client-side dedup. Todo byte cruza o link FD→SD antes de a dedup acontecer. Em backup remoto, isso desperdiça banda.

O PodHeitor GDD ataca os três: FastCDC variable-length, Bloom filter L1 + RocksDB L2, e fingerprint client-side via FD plugin antes de transmitir o payload.

2. Arquitetura — sidecar SD + FD plugin client-side

┌─────────────────────────────────────────────────────────────────────────────┐
│                      BACULA INFRASTRUCTURE (UNMODIFIED)                      │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌──────────┐        ┌──────────────┐        ┌──────────────────────────┐   │
│  │  Bacula  │  TCP   │  Bacula FD   │  TCP   │     Bacula SD            │   │
│  │ Director │◄──────►│ + PodHeitor  │◄──────►│ + PodHeitor SD Plugin    │   │
│  │          │        │  FD Plugin   │        │   (Rust cdylib v2)       │   │
│  └──────────┘        └──────┬───────┘        └───────────┬──────────────┘   │
│                              │ Fingerprints              │ Data stream       │
│                              ▼                            ▼                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                       PODHEITOR GDD ENGINE (RUST)                            │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌───────────────┐  ┌──────────────────────────┐           │
│  │  FastCDC    │  │ Bloom Filter  │  │   Dedup Index            │           │
│  │  Chunker    │──►│  (L1 Cache)  │──►│  (RocksDB + RAM)         │           │
│  │  (variable) │  │  FPR: 0.1%   │  │  SHA-256 → ContainerRef  │           │
│  └─────────────┘  └───────────────┘  └──────────────────────────┘           │
│         │                                        │                          │
│         ▼                                        ▼                          │
│  ┌─────────────┐                     ┌──────────────────────────┐           │
│  │  Segment    │                     │   Container Store        │           │
│  │  Locality   │────────────────────►│  (append-only files)     │           │
│  │  Tracker    │                     │  + segment grouping      │           │
│  └─────────────┘                     └──────────────────────────┘           │
└─────────────────────────────────────────────────────────────────────────────┘

O FD plugin é cdylib Rust (workspace plugin-gdd). O SD plugin é cdylib Rust + binding FFI (libgdd_ffi_sd.so) que conversa com o daemon GDD via TCP+PSK na porta 9104, com métricas Prometheus na 9421. Zero modificação ao source do Bacula.

3. FastCDC — chunking de tamanho variável

FastCDC (Fast Content-Defined Chunking) usa rolling hash baseado em gear table para encontrar boundaries determinísticas baseadas em conteúdo:

Parâmetro Default Range
Min chunk 4 KB 2–8 KB
Avg chunk 16 KB 8–64 KB
Max chunk 64 KB 32–256 KB
Hash bits 48 fixo

Por que variable-length em vez de fixed-size? Em modificações que inserem ou removem bytes, fixed-size desalinha tudo após o ponto de inserção (cascade effect). Variable-length re-encontra o boundary natural após poucos bytes — 50–70% melhor ratio em DB dumps, logs, snapshots de VM.

4. Bloom filter L1 — eliminação de I/O SSD

Antes de qualquer query ao RocksDB (custosa em SSD), o Bloom filter em RAM responde “definitivamente novo” ou “talvez existe”. Counting Bloom Filter com partitioned hashing:

Parâmetro Valor Racional
Items esperados 100M chunks ~1.6 TB unique data em 16 KB avg
FPR target 0.1% (1 em 1000) Aceitável
Bits per item 14.4 Optimal para 0.1% FPR
Memória total ~180 MB Cabe em RAM facilmente
Hash functions 10 k = ln(2) × m/n
Persistência mmap’d file Load instantâneo, crash-safe

Resultado mensurado: 90%+ de redução de I/O SSD no caminho hot vs lookup direto em DB.

5. Dois modos de operação

Mode Comportamento Quando usar
Mode A SD plugin transparente; dedup acontece server-side; FD não muda Migration path / clientes legados que não podem mudar FD
Mode B FD plugin computa fingerprint client-side; só blocos novos atravessam o link Default produção — economia de 60–95% de banda

Em Mode B, o FD lê o arquivo, FastCDC chunk-a, gera SHA-256 fingerprint por chunk, envia a lista ao GDD daemon. O daemon responde “já tenho” ou “preciso”. O FD só transmite o que é novo.

6. Reliability — fail-closed obrigatório

O lab Mode B passou por sequência de bugs documentados (B3a/b/c/d, B4, B5) que ensinaram um princípio operacional: fail-closed > fail-open. Em particular, qualquer error path que deixe state->active=false com sp->no_read=false emite silenciosamente file records de 0 bytes com Termination: Backup OK — a pior falha possível em backup. As correções de produção 2026-04-29 → 2026-05-01:

Bug Causa Fix
B3c (daemon-restart mid-job) state stale após reconnect Fail-closed JOB_START + per-file passthrough fallback (com lseek(fd, 0, SEEK_SET) antes de reusar fd)
B3a (FD kill) Job ficava em status I infinito Auto-reschedule pelo Director
B3b (SD kill) Volume parcial sem detecção Fatal status; recovery via re-run
B5 (cargo 0-byte) VREF build 24.6 MB excedia STORE_NEW cap 16 MiB FDRAW passthrough em três paths de fallback
Foreign-plugin guard Plugin engatava em jobs ADABAS SELF_PREFIX gate em PluginCommand

7. Garbage collection — concurrent mark-sweep

O GDD daemon roda vacuum concorrente em background: mark-sweep sobre o index, refcount drop em containers, compaction de container holes. Ao contrário do GED do BEE (manutenção em “DDB maintenance job” agendado), o vacuum do PodHeitor:

  • Roda com priority baixa, sem janela.
  • Não bloqueia jobs ativos (read+write em containers append-only).
  • Reporta progresso via /metrics Prometheus.

8. BR/DR — chunk-store recovery

O BR_DR_RUNBOOK.md documenta cold-stop tar + LVM/ZFS snapshot + recovery soft-loss (delete bloom/esm, daemon rebuilds). Caveats:

  • gdd-fsck --rebuild-index ainda não implementado (post-GA item).
  • manifests/ empty no estado atual — write path em verificação.
  • Live-snapshot API não disponível; usar LVM/ZFS no host.

9. Métricas Prometheus

Endpoint /metrics na porta 9421 expõe:

  • gdd_chunks_total, gdd_chunks_unique, gdd_chunks_duplicate
  • gdd_bytes_in, gdd_bytes_stored, ratio derivado
  • gdd_bloom_hits, gdd_bloom_false_positives
  • gdd_rocksdb_get_seconds_bucket (histogram)
  • gdd_vacuum_cycles_total, gdd_vacuum_bytes_reclaimed
  • gdd_daemon_rss_bytes (target ≤ 200 MB sustentado)

10. Anti-patterns documentados

  • Não rode dedup em arquivo único > 134 MiB cap sem FDRAW passthrough. O VREF protocol cap é 16 MiB; arquivos grandes vão por OVERSIZE-PASSTHROUGH. O lab provou isso com randombig.bin de 12 GB cruzando rollover de volume.
  • Não confunda volume size mismatch com bug do plugin. Diferenças abs(N-M) < 65536 são self-heal do core do Bacula — runbook B1 do operator.
  • Não rode operações concorrentes sobre o mesmo container sem o daemon ativo. O daemon é a única fonte de verdade do refcount.
  • Não derrube o daemon mid-job sem aceitar fail-closed do job atual. Comportamento intencional: fail-closed por design vs silent 0-byte bug.

11. License posture

O plugin distribui sob LicenseRef-PodHeitor-Proprietary. Não vincula estaticamente nenhum source AGPLv3 do Bacula. O FD cdylib + SD cdylib + daemon são todos Rust binaries puros. A integração SD usa apenas a sd_plugins.h API pública do Bacula via extern "C".

Pronto para avaliar?

Trial gratuito de 30 dias para workloads multi-TB com dedup ratio esperado > 4×. Garantimos no mínimo 50% de desconto vs Bacula Enterprise GED, Veeam Repository ou Commvault DDB, 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 Global Dedup

Disponível em: pt-brPortuguêsenEnglish (Inglês)esEspañol (Espanhol)

Deixe um comentário