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
/metricsPrometheus.
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-indexaú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_duplicategdd_bytes_in,gdd_bytes_stored, ratio derivadogdd_bloom_hits,gdd_bloom_false_positivesgdd_rocksdb_get_seconds_bucket(histogram)gdd_vacuum_cycles_total,gdd_vacuum_bytes_reclaimedgdd_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.binde 12 GB cruzando rollover de volumen. - No confunda volume size mismatch con bug del plugin. Diferencias
abs(N-M) < 65536son 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:
Português (Portugués, Brasil)
English (Inglés)
Español