Whitepaper técnico — PodHeitor Global Deduplication para Bacula

Engine de deduplicación global standalone que opera como sidecar del Storage Daemon, con chunking de tamaño variable (FastCDC), Bloom filter multicapa, RocksDB index, deduplicación client-side vía FD plugin, y métricas Prometheus — compitiendo directamente con Bacula Enterprise GED y Commvault DDB.

Documento técnico complementario a la página del plugin PodHeitor Global Dedup.

1. El problema: GED de Bacula Enterprise es fixed-block + TokyoCabinet

El GED (Global Endpoint Deduplication) de Bacula Enterprise usa chunking de tamaño fijo y TokyoCabinet B-tree como hash DB. Eso tiene tres limitaciones estructurales:

  • Cascade effect en modificaciones. Insertar 1 byte al inicio de un archivo de 10 GB invalida todos los bloques siguientes — los boundaries fixed-block se desalinean. Resultado: el ratio de dedup colapsa en workloads de log, dump de DB y snapshots de VM.
  • SSD bound en IOPS. Cada lookup TokyoCabinet es un seek SSD. Sin Bloom filter al frente, el camino caliente toca disco por bloque.
  • Sin client-side dedup. Cada byte cruza el link FD→SD antes de que la dedup ocurra. En backup remoto, eso desperdicia ancho de banda.

PodHeitor GDD ataca los tres: FastCDC variable-length, Bloom L1 + RocksDB L2, y fingerprint client-side vía FD plugin antes de transmitir el payload.

2. Arquitectura — 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      │           │
│  └─────────────┘                     └──────────────────────────┘           │
└─────────────────────────────────────────────────────────────────────────────┘

El FD plugin es cdylib Rust (workspace plugin-gdd). El SD plugin es cdylib Rust + binding FFI (libgdd_ffi_sd.so) que conversa con el daemon GDD vía TCP+PSK en el puerto 9104, con métricas Prometheus en 9421. Cero modificación al source de Bacula.

3. FastCDC — chunking de tamaño variable

FastCDC (Fast Content-Defined Chunking) usa rolling hash basado en gear table para encontrar boundaries determinísticos basados en contenido:

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 fijo

¿Por qué variable-length en vez de fixed-size? En modificaciones que insertan o remueven bytes, fixed-size desalinea todo después del punto de inserción (cascade effect). Variable-length re-encuentra el boundary natural tras pocos bytes — 50–70% mejor ratio en DB dumps, logs, snapshots de VM.

4. Bloom L1 — eliminación de I/O SSD

Antes de cualquier query a RocksDB (costosa en SSD), el Bloom filter en RAM responde «definitivamente nuevo» o «talvez existe». Counting Bloom Filter con partitioned hashing:

Parámetro Valor Racional
Items esperados 100M chunks ~1.6 TB unique data en 16 KB avg
FPR target 0.1% (1 en 1000) Aceptable
Bits per item 14.4 Optimal para 0.1% FPR
Memoria total ~180 MB Cabe en RAM fácilmente
Hash functions 10 k = ln(2) × m/n
Persistencia mmap’d file Load instantáneo, crash-safe

Resultado mensurado: 90%+ de reducción de I/O SSD en el camino caliente vs lookup directo en DB.

5. Dos modos de operación

Mode Comportamiento Cuándo usar
Mode A SD plugin transparente; dedup ocurre server-side; FD no cambia Migration path / clientes legacy que no pueden cambiar FD
Mode B FD plugin computa fingerprint client-side; solo bloques nuevos cruzan el link Default producción — ahorro de 60–95% de banda

En Mode B, el FD lee el archivo, FastCDC lo chunkea, genera SHA-256 fingerprint por chunk, envía la lista al GDD daemon. El daemon responde «ya tengo» o «necesito». El FD solo transmite lo nuevo.

6. Reliability — fail-closed obligatorio

El lab Mode B pasó por una secuencia de bugs documentados (B3a/b/c/d, B4, B5) que enseñaron un principio operacional: fail-closed > fail-open. En particular, cualquier error path que deje state->active=false con sp->no_read=false emite silenciosamente file records de 0 bytes con Termination: Backup OK — la peor falla posible en backup. Las correcciones de producción 2026-04-29 → 2026-05-01:

Bug Causa Fix
B3c (daemon-restart mid-job) state stale tras reconnect Fail-closed JOB_START + per-file passthrough fallback (con lseek(fd, 0, SEEK_SET) antes de reusar fd)
B3a (FD kill) Job quedaba en status I infinito Auto-reschedule por el Director
B3b (SD kill) Volumen parcial sin detección Fatal status; recovery vía re-run
B5 (cargo 0-byte) VREF build 24.6 MB excedía cap STORE_NEW 16 MiB FDRAW passthrough en tres paths de fallback
Foreign-plugin guard Plugin enganchaba en jobs ADABAS SELF_PREFIX gate en PluginCommand

7. Garbage collection — concurrent mark-sweep

El GDD daemon corre vacuum concurrente en background: mark-sweep sobre el index, refcount drop en containers, compaction de container holes. A diferencia del GED de BEE (mantenimiento en «DDB maintenance job» agendado), el vacuum de PodHeitor:

  • Corre con prioridad baja, sin ventana.
  • No bloquea jobs activos (read+write en containers append-only).
  • Reporta progreso vía /metrics Prometheus.

8. BR/DR — chunk-store recovery

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

  • gdd-fsck --rebuild-index aún no implementado (post-GA item).
  • manifests/ empty en el estado actual — write path en verificación.
  • Live-snapshot API no disponible; usar LVM/ZFS en el host.

9. Métricas Prometheus

El endpoint /metrics en el puerto 9421 expone:

  • 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 sostenido)

10. Anti-patterns documentados

  • No corra dedup en archivo único > cap 134 MiB sin FDRAW passthrough. El cap del VREF protocol es 16 MiB; archivos grandes van por OVERSIZE-PASSTHROUGH. El lab probó esto con randombig.bin de 12 GB cruzando rollover de volumen.
  • No confunda volume size mismatch con bug del plugin. Diferencias abs(N-M) < 65536 son self-heal del core de Bacula — runbook B1 del operator.
  • No corra ops concurrentes sobre el mismo container sin el daemon activo. El daemon es la única fuente de verdad del refcount.
  • No mate el daemon mid-job sin aceptar fail-closed del job actual. Comportamiento intencional: fail-closed por diseño vs silent 0-byte bug.

11. License posture

El plugin se distribuye bajo LicenseRef-PodHeitor-Proprietary. No vincula estáticamente ningún source AGPLv3 de Bacula. El FD cdylib + SD cdylib + daemon son todos binarios Rust puros. La integración SD usa solo la sd_plugins.h API pública de Bacula vía extern "C".

¿Listo para evaluar?

Trial gratuito de 30 días para workloads multi-TB con dedup ratio esperado > 4×. Garantizamos al menos 50% de descuento vs Bacula Enterprise GED, Veeam Repository o Commvault DDB, 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 Global Dedup

Disponível em: pt-brPortuguês (Portugués, Brasil)enEnglish (Inglés)esEspañol

Deja una respuesta