Deduplicación global en Rust memory-safe. Bloom multi-capa, chunking de bloque variable, AEAD nativo. Típicamente 10-20× de reducción en datasets corporativos reales.
- Variable-block chunking con fingerprint Rabin — dedup atraviesa boundaries de archivo.
- Bloom filter multi-capa — lookup O(1) sin hit en disco para chunks ya vistos.
- Segment locality cache — tasa de acierto 95%+ en patrones de respaldo repetitivos.
- Adaptive tuner — ajuste de chunk size por workload (VMs, DBs, files) automático.
- AEAD custom — encriptación end-to-end sin comprometer dedup ratio.
Producción en 30 días, al menos 50% más barato. Trial gratuito, RPMs y DEBs firmados, soporte directo con Heitor Faria. Reemplace su licencia Veeam, Commvault o Bacula Enterprise sin romper la ventana nocturna.
Solicitar trial gratuito de 30 días →
1. Descripción General
PodHeitor GDD es un motor de deduplicación global en línea para PodHeitor Backup, implementado en Rust. Reemplaza la deduplicación por volumen con un espacio de nombres de dedup único y compartido entre todos los jobs de backup en un Storage Daemon, permitiendo la eliminación de datos entre jobs, clientes y pools.
Objetivos de Diseño
| Objetivo | Enfoque |
|---|---|
| Dedup global (entre jobs) | Índice único RocksDB, compartido entre todos los jobs |
| Baja latencia para datos calientes | Bloom filter multicapa → índice → container |
| Restauración con consciencia de segmentos | Chunks agrupados por localidad → prefetch anticipado de lectura |
| Seguro ante fallos | RocksDB WAL + containers append-only con CRC-32 |
| Simplicidad operacional | Métricas Prometheus, configuración TOML, comandos vacuum y scrub |
2. Arquitectura
Bacula SD (write path)
│
▼
┌─────────────┐ ┌────────────────────┐
│ FastCDC │ │ Adaptive Tuner │
│ Chunker │◄────│ (workload profile) │
└──────┬──────┘ └────────────────────┘
│ chunk stream
▼
┌─────────────┐ miss
│ Bloom Filter│──────────────────────────────┐
│ (2-layer) │ hit │
└──────┬──────┘ │
│ probable-duplicate │
▼ │
┌─────────────┐ not found (false positive) │
│ RocksDB │──────────────────────────────►│
│ Index │ found → increment ref_count │
└──────┬──────┘ │
│ new chunk path ◄──────────────────────┘
▼
┌─────────────┐
│ Container │ append-only, LZ4/ZSTD compressed
│ Store │ CRC-32 per chunk header
└─────────────┘
Bacula SD (read path)
│
▼
┌─────────────┐ hit
│ Read-Ahead │──────────────────────► data (from cache)
│ Cache │ miss
└──────┬──────┘
│
▼
┌─────────────┐
│ Index lookup│ hash → ContainerRef (container_id, offset, length, segment_id)
└──────┬──────┘
│
▼
┌─────────────┐
│ Container │ seek + read + CRC verify
│ Store │
└──────┬──────┘
│ prefetch segment siblings into Read-Ahead Cache
▼
data
3. Componentes Principales
3.1 Chunker de Longitud Variable FastCDC
Content-defined chunking (CDC) mediante el algoritmo FastCDC. Los límites de chunk se determinan por tablas de gear hash con rolling hash, lo que los hace estables ante inserciones y eliminaciones de bytes — fundamental para una deduplicación efectiva.
Parámetros predeterminados (ajustados por AdaptiveTuner):
| Perfil | Mín | Prom | Máx |
|---|---|---|---|
HighDedup |
2 KB | 8 KB | 32 KB |
Balanced / Unknown |
4 KB | 16 KB | 64 KB |
LowDedup |
8 KB | 32 KB | 128 KB |
3.2 Bloom Filter Multicapa
Bloom filter de dos capas (caliente + fría) elimina I/O en RocksDB para chunks ya vistos. Ante un miss, el motor pasa a la consulta del índice (sin falsos negativos en la ruta de almacenamiento).
- Capa caliente: chunks activos recientemente (capacidad configurable)
- Capa fría: todos los chunks históricos
- Tasa de falsos positivos: configurable (predeterminado: 0,1%)
- Ruta de persistencia:
/opt/podheitor-gdd/bloom/— sobrevive a reinicios
⚠️ Pérdida del Bloom: Si los archivos bloom son eliminados, el motor regresa a consultas completas en el índice (más lento, pero correcto). La integridad de los datos no se ve afectada.
3.3 Índice RocksDB
Mapea SHA-256(chunk) → una referencia de ubicación en el Container Store, con ID de container, desplazamiento en bytes, longitud de datos, contador de referencias para garbage collection y grupo de localidad para lectura anticipada. Almacenado como valor de tamaño fijo en RocksDB para consulta e iteración eficientes. Usa compresión LZ4 (archivos SST) y Zstd en el nivel más bajo.
Seguridad ante fallos: El RocksDB WAL garantiza durabilidad tras flush(). Todas las entradas del índice sobreviven a fallos del proceso, siempre que engine.flush() haya sido llamado antes de una terminación por el SO o corte de energía.
3.4 Container Store
Archivos binarios append-only (container_XXXXXXXX.dat) que contienen datos de chunks comprimidos. Cada chunk almacena el hash SHA-256, la longitud de los datos, flags de compresión (LZ4 o Zstd) y un CRC-32 para detección de corrupción silenciosa. En cada lectura el CRC-32 es verificado — cualquier discrepancia retorna error de inmediato, nunca corrupción silenciosa.
3.5 Rastreador de Segmentos y Caché de Lectura Anticipada
Los chunks se agrupan en segmentos (predeterminado: 64 chunks/segmento) por job de backup. Cada ContainerRef lleva un segment_id. Durante la restauración:
recall_block(hash)verifica primero la caché de lectura anticipada (O(1)).- Ante un miss: obtiene el chunk y recupera su
Segmentdel
SegmentTracker.
- Realiza prefetch de todos los chunks hermanos hacia la caché (limitado por
read_ahead_cache_size, predeterminado: 512 chunks ≈ 8 MB con promedio de 16 KB).
- Las llamadas subsiguientes a
recall_blockpara el mismo segmento retornan de inmediato.
Esto refleja el patrón de «localidad de container dedup» de Commvault.
3.6 Ajustador Adaptativo
Muestrea continuamente la tasa de deduplicación en una ventana deslizante y ajusta el dimensionamiento de los chunks:
- Alta deduplicación (>80%): chunks más pequeños → mayor granularidad
- Baja deduplicación (<20%): chunks más grandes → menor sobrecarga de índice
- Balanceado / Desconocido: parámetros predeterminados
3.7 Vacuum / GC
Recolección de basura por marcado y barrido. Identifica archivos de container sin referencias de índice activas (ref_count = 0) y los elimina. Cancelable. Se ejecuta fuera de línea o de forma programada (predeterminado: diariamente a las 02:00).
3.8 Scrub / Verificación de Integridad
Análisis completo de integridad de datos. Itera cada entrada del índice, lee y verifica el CRC de cada chunk. Devuelve un informe de scrub con:
- Contadores: chunks verificados, correctos, corruptos y ausentes
- Lista de chunks defectuosos con estado para reparación dirigida
Los chunks corruptos presentan discrepancia de CRC/encabezado (inversión de bit, error silencioso de disco). Los chunks ausentes indican que el archivo de container no fue encontrado (eliminación accidental, etc.).
4. Rendimiento
4.1 Resultados de Benchmark
Medido en hardware de producción (servidor: 192.168.15.105). Datos: bloques de 4 MB, tmpfs (local) vs disco real (servidor).
| Benchmark | Local (tmpfs) | Servidor (disco real) |
|---|---|---|
store/unique_4mb |
~1,18 GB/s | ~128 MB/s |
store/dedup_4mb |
~1,22 GB/s | ~125 MB/s |
recall/sequential_4mb |
36 µs | 265 µs |
scrub/full_scan (16 MB) |
9,8 µs | 65 µs |
Análisis de cuello de botella: En disco real, el
storeestá limitado por el RocksDB WAL y la adición al archivo de container. La ruta de deduplicación (bloom hit → index hit → sin escritura) es solo marginalmente más rápida porque la consulta al índice aún accede a RocksDB.Recall a 265 µs para 4 MB = ~15 GB/s efectivos (caché de lectura anticipada sirviendo chunks subsiguientes tras el primer miss; el recall en disco frío estaría limitado por I/O a ~128 MB/s).
4.2 Métricas de Producción en Vivo
Desde el endpoint Prometheus (http://server:9420/metrics, abril 2026):
| Métrica | Valor |
|---|---|
gdd_bytes_ingested |
57,7 MB |
gdd_bytes_stored |
30,4 MB |
gdd_bytes_deduplicated |
27,4 MB |
gdd_chunks_total |
3.726 |
gdd_chunks_new |
1.821 |
gdd_chunks_duplicate |
1.905 |
gdd_dedup_ratio |
47,4% |
gdd_savings_factor |
1,9× |
5. Referencia de Configuración
Ruta: /etc/podheitor-gdd.toml (o mediante GddConfig::load(path))
[engine]
data_dir = "/opt/podheitor-gdd/data"
max_container_size_mb = 64 # max .dat file size before rotation
hash_algorithm = "sha256"
max_concurrent_jobs = 8
[bloom]
expected_items = 100_000_000 # estimated total unique chunks
false_positive_rate = 0.001 # 0.1% FP rate
hot_layer_items = 10_000_000 # recently-active chunk window
persist_path = "/opt/podheitor-gdd/bloom"
[index]
db_path = "/opt/podheitor-gdd/index"
cache_size_mb = 4096 # RocksDB block cache (RAM)
write_buffer_mb = 256 # RocksDB memtable size
[chunking]
min_size = 4096 # 4 KB minimum chunk
avg_size = 16384 # 16 KB target average
max_size = 65536 # 64 KB maximum chunk
algorithm = "fastcdc"
[segment]
chunks_per_segment = 64 # chunks grouped per backup job segment
cache_size = 10000 # segment LRU cache (for read-ahead lookup)
read_ahead_cache_size = 512 # max chunks in read-ahead cache (≈8 MB)
[adaptive]
enabled = true
sample_window = 1000 # dedup ratio sampling window
low_dedup_threshold = 0.20 # → LowDedup profile
high_dedup_threshold = 0.80 # → HighDedup profile
[metrics]
enabled = true
bind = "0.0.0.0:9420" # Prometheus scrape endpoint
[vacuum]
schedule = "daily"
time = "02:00"
max_duration_hours = 4
6. Guía de Operaciones
6.1 Iniciar / Detener
# Systemd
systemctl start podheitor-gdd
systemctl stop podheitor-gdd
# Directo (foreground)
podheitor-gdd --config /etc/podheitor-gdd.toml
6.2 Monitoreo
# Prometheus metrics
curl http://localhost:9420/metrics | grep ^gdd_
# Key metrics to watch
gdd_dedup_ratio # target > 0.30 for typical backup data
gdd_savings_factor # target > 1.5×
gdd_chunks_duplicate # growing = dedup working
gdd_jobs_active # sanity check: no stuck jobs
6.3 Vacuum (Recolección de Basura)
Vacuum elimina los archivos de container que no tienen referencias de chunk activas. Ejecútelo después de eliminar jobs antiguos de Bacula:
# Via gdd-client
gdd-client vacuum
# Estimated safe schedule: daily at 02:00 (see [vacuum] config)
6.4 Scrub (Integridad de Datos)
Scrub lee y verifica el CRC de cada chunk. Ejecútelo tras errores de disco o antes de pruebas de recuperación ante desastres:
gdd-client scrub
# Expected output (healthy)
# Checked: 1821 | OK: 1821 | Corrupted: 0 | Missing: 0 ✓
# On corruption
# Corrupted: 3 → see bad_chunks list for hashes to restore from tape
6.5 Recuperación ante Fallos
GDD usa RocksDB WAL para la durabilidad del índice. Tras una terminación abrupta del proceso:
- RocksDB reproduce el WAL en la siguiente apertura — no se requiere recuperación manual.
- Los archivos de container son append-only con CRC — las escrituras parciales son detectables.
- El Bloom filter es opcional: si se pierde, las tasas de deduplicación pueden caer temporalmente
(falsos negativos → nuevos chunks tratados como únicos), pero la integridad de los datos se preserva a través del índice.
6.6 Consideraciones de Escalabilidad
| Recurso | Recomendación |
|---|---|
| RAM | ≥ 4 GB para caché RocksDB (cache_size_mb) + 2 GB SO |
| Almacenamiento | SSD para la ruta del índice; HDD aceptable para datos del container |
| CPU | 4+ núcleos; dedup sin bloqueo a nivel de chunk |
max_concurrent_jobs |
Comience con 8; aumente si la CPU lo permite |
6.7 Hardening Futuro (Dirección Gen2)
La validación en campo demostró que el scrub periódico es útil, pero no la experiencia óptima en estado estacionario para los operadores. Una dirección de diseño más sólida es reducir la necesidad de scrub completo haciendo que la consistencia de metadatos sea auto-reparable y reproducible en el arranque.
Próximos pasos recomendados:
- Journal de commit de container — persistir marcadores de commit o
manifiestos por container, para que el motor pueda identificar el último offset durable de chunk sin un análisis completo.
- Reproducción en arranque / fsck de cola — verificar solo la cola reciente de los containers
más el estado del journal durante el inicio del daemon, en lugar de requerir un scrub global periódico para tener confianza en la consistencia.
- Checkpointing atómico de metadatos — escribir metadatos de chunk en una secuencia reproducible
(append -> fsync -> index insert -> checkpoint) para que las referencias de índice pendientes no sobrevivan a fallos.
- Reparación guiada por manifiesto — reconstruir o eliminar referencias de índice inválidas desde
manifiestos de container directamente, evitando un recorrido completo del índice en los caminos de recuperación más comunes.
6.8 Reducción Adicional de Latencia del Índice
El diseño actual ya reduce el I/O del índice mediante un Bloom filter en capas y grandes cachés de RocksDB. Mejoras adicionales que vale la pena implementar:
- Caché RAM de huellas digitales calientes delante de RocksDB para chunks activos recientemente.
- Consultas/escrituras en lote en el índice para mejor compactación y localidad de syscall.
- Particiones de índice fragmentadas por prefijo para reducir la contención y mejorar
la localidad de caché.
- Medio rápido dedicado para WAL / L0, con niveles SST más fríos en
almacenamiento más económico cuando sea necesario.
- Caché de resultado negativo para misses repetidos durante la ingesta de datos únicos.
- Manifiestos locales de container para omitir accesos al índice global en algunas rutas de
restauración y reparación.
7. Consideraciones de Seguridad
PodHeitor GDD está implementado en Rust, lo que proporciona seguridad de memoria por diseño — sin buffer overflows, use-after-free ni data races que afectan implementaciones equivalentes en C/C++.
- Sin exposición de red por defecto — el endpoint Prometheus es solo de LAN.
- Sin almacenamiento de secretos — la configuración contiene solo rutas y tamaños.
- Los archivos de container son binarios; los datos de chunk están comprimidos, no cifrados.
Agregue cifrado de disco completo a nivel del SO si es necesario.
- Las huellas digitales SHA-256 son resistentes a colisiones para fines de deduplicación, pero no
constituyen un mecanismo de autenticación criptográfica.
8. Limitaciones Conocidas y Hoja de Ruta
| Elemento | Estado |
|---|---|
| Caché de lectura anticipada (localidad de segmento) | ✅ Implementado |
| Recuperación ante fallos (RocksDB WAL + CRC) | ✅ Probado |
| Scrub / verificación de integridad | ✅ Implementado |
| Benchmarks de rendimiento | ✅ Baseline medido |
| Re-siembra del Bloom filter tras pérdida | 🔄 Automático (bloom vacío → fallback al índice) |
| Lectura anticipada de restauración para segmentos entre sesiones | 🔄 Solo en sesión (caché de segmento en RAM) |
| Scrub paralelo (multithreaded) | 📋 Planificado |
| Soportado: container cifrado | 📋 Planificado |
| Interfaz Plugin Bacula (integración en producción) | 🔄 En desarrollo |
PodHeitor GDD se desarrolla como parte del ecosistema PodHeitor Bacula. Contáctenos para licenciamiento comercial.
PodHeitor GDD V2 — Whitepaper de Rendimiento
Audiencia: arquitectos de almacenamiento, operadores Bacula, tomadores de decisiones que evalúan la deduplicación del lado de la fuente para entornos de backup conectados por WAN. Versión: V2 (F4 entregado, 2026-04-24).
1. Resumen ejecutivo
PodHeitor GDD V2 reduce el almacenamiento de backup Bacula y el ancho de banda de red mediante deduplicación basada en contenido con dos modos de despliegue:
- Modo Storage (F3, entregado): el host del Storage Daemon intercepta registros de datos
de archivo entrantes, los fragmenta con FastCDC, reemplaza el contenido coincidente con referencias de 32 bytes (VREFs) y almacena chunks únicos en un repositorio local direccionable por contenido. Medido en /usr/bin (408,8 MiB): archivo de volumen en disco reducido a 4,45 MiB — 1,09% de los bytes del cliente, compresión de 91×.
- Modo Bothsides (F4, entregado): el plugin del File Daemon fragmenta el contenido en
el cliente, intercambia hashes con un daemon remoto (HKDF-SHA256 + ChaCha20-Poly1305 AEAD sobre TCP) y emite solo las referencias de hash en el flujo Bacula — transfiriendo los bytes reales únicamente para los chunks que el daemon aún no tiene.
Ambos modos utilizan el mismo formato de container en disco (VOLUME_FORMAT_V2). El Modo A (solo storage) comparte su índice de deduplicación con la ruta de restauración y está listo para producción: roundtrip byte a byte validado en un backup de 414 MB en /usr/bin (job 3597 → job 3604 el 2026-04-24). El Modo B (bothsides) alcanzó 99,63% de ahorro medido de ancho de banda con caché caliente (420 MB → 1,55 MB), pero su ruta de restauración actualmente no puede rearmar los bytes originales porque los repositorios RocksDB del FD y del SD son instancias separadas — use el Modo B para benchmarking de ahorro de ancho de banda, no en producción, hasta que la unificación de repositorios (F4.8) sea lanzada.
2. La historia de rendimiento detrás de nuestra elección de PSK
2.1 Por qué no X.509 mTLS
El estándar natural para un listener de servidor en producción es TLS 1.3 con autenticación mutua por certificado. Lo evaluamos y rechazamos por:
- Costo de handshake: TLS 1.3 basado en certificado ejecuta validación de la cadena de certificados + verificación de firma RSA/ECDSA
+ OCSP opcional en cada nueva conexión. En hardware de servidor común, esto supone ~3–5 ms por handshake. Multiplicado por miles de clientes de backup abriendo una sesión por job, en un cron nocturno, la sobrecarga fija domina el throughput de jobs pequeños.
- Peso operacional: bootstrap de CA, renovación de certificados, gestión de la cadena de confianza
y distribución de certificados por host son tareas administrativas recurrentes — del tipo que se deterioran silenciosamente con correos de «expira en 30 días» y eventualmente causan interrupciones en producción.
- Riesgo de canal lateral: la firma RSA filtra temporización microarquitectural; el
abuso de nonce en ECDSA es un problema bien documentado. Ninguno aplica a nuestro caso de uso.
2.2 Por qué TLS 1.3 PSK fue insuficiente
TLS 1.3 soporta Pre-Shared Keys externas (RFC 8446 §4.2.11), lo que omite la validación de la cadena de certificados y reduce el costo del handshake a ~1–2 ms. Sin embargo, el soporte de PSK externo en la versión estable es experimental, requiere escapes inseguros y agrega peso binario significativo en cada host FD para una única suite de cifrado que usaríamos exclusivamente.
2.3 Nuestro framing AEAD personalizado
Conservamos los primitivos criptográficos que usa TLS 1.3 — HKDF-SHA256 para derivación de claves, ChaCha20-Poly1305 para AEAD — y omitimos por completo la máquina de estados TLS. El resultado:
| Capa | TLS 1.3 basado en cert | TLS 1.3 + PSK | PodHeitor GDD V2 PSK |
|---|---|---|---|
| Round-trips de handshake | 1 | 1 (0-RTT posible) | 1 |
| CPU de handshake | cadena de cert + verificación de firma | HKDF + configuración AEAD | HKDF expand (×2 direcciones) |
| Handshake (tiempo real) | ~3–5 ms | ~1–2 ms | ~0,1–0,3 ms |
| AEAD por frame | ChaCha20-Poly1305 | ChaCha20-Poly1305 | ChaCha20-Poly1305 |
| Throughput en estado estacionario | idéntico | idéntico | idéntico |
| Tamaño binario agregado | ~200 KB (TLS completo) | ~150 KB (TLS PSK) | ~45 KB (chacha20poly1305 + hkdf) |
| Archivos de configuración por host | cert CA + cert cliente + clave cliente + cadena de confianza | archivo PSK | archivo PSK |
| Artefactos con expiración | certs (renovación anual) | PSK (definido por el usuario) | PSK (definido por el usuario) |
Conclusión: confidencialidad + integridad de red idénticas, 5–10× menor CPU de handshake que TLS 1.3 PSK y 10–50× menor que TLS basado en certificado, con la menor superficie operacional posible (un archivo de 32 bytes por sitio).
2.4 Equivalencia del modelo de amenaza
Ambos enfoques protegen contra:
- Escucha pasiva: cifrado AEAD con nonces frescos.
- Inyección/replay activo de MitM: nonce monotónico estricto + tag Poly1305.
- Frame adulterado/clave incorrecta: fallo silencioso, conexión terminada.
Ningún enfoque protege contra:
- Clave privada/PSK comprometida: catastrófico en ambos modelos. TLS basado en cert
puede revocar vía CRL/OCSP (con ventana de propagación); nuestro PSK se rota con recarga SIGHUP con ventana de gracia.
- Adversario cuántico contra la clave compartida: fuera del alcance para ambos.
Diferencias:
- Secreto futuro perfecto (PFS): nuestra derivación PSK es determinista dado los
nonces del handshake. Un atacante que posteriormente recupere el PSK y haya capturado ambos hellos puede descifrar el tráfico grabado. TLS completo con psk_dhe_ke agrega ECDH efímero y provee PFS. Si su modelo de amenaza requiere PFS contra adversarios estatales con captura de largo plazo, coloque un túnel SSH o stunnel con suites de cifrado con secreto futuro frente al listener — el protocolo V2 es agnóstico al transporte.
2.5 Cómo se acumulan los ahorros de rendimiento
Un despliegue Bacula de tamaño mediano: 200 clientes, 50 jobs/cliente/mes, 1 sesión/job = 10.000 sesiones/mes. Ahorro de handshake por sesión: ~4 ms (vs TLS basado en cert). CPU agregada ahorrada: 10.000 × 4 ms = 40 segundos de CPU por mes.
Por sí solo, insignificante. Pero multiplicado por:
- Tiempo de operador ahorrado: sin CA que gestionar = 1–2 incidentes de soporte menos
por trimestre en despliegues típicos.
- Presupuesto de latencia recuperado: un ahorro de 4 ms en el handshake es suficiente para marcar
la diferencia entre «suficientemente rápido para no notarse» y «suficientemente rápido para no provocar timeout y retry» en enlaces WAN inestables.
- Ahorro en tamaño binario: 150 KB menos por host FD significa despliegues más rápidos en
espejos de paquetes lentos y menos presión de memoria en clientes embebidos.
La victoria real es el efecto de segundo orden: porque el costo operacional es un solo archivo a distribuir, los sitios despliegan PodHeitor GDD. No lo descartan como «demasiada burocracia de certificados». El argumento de rendimiento es honesto, pero la historia de adopción es el titular.
3. Resultados medidos
3.1 Compresión en modo Storage (F3)
Carga de trabajo: /usr/bin en una imagen estándar Oracle Linux 9.6, 1847 archivos, 408.877.776 bytes.
- Archivo de volumen en disco: 4.667.193 bytes → 1,09% de los bytes del cliente, reducción de 91×
- Container store /gdd: ~110 MB (chunks + índice + manifiestos)
- Recall verificado: reconstrucción byte a byte de los archivos originales.
El archivo de volumen se reduce porque cada registro de datos de archivo en un bloque Bacula se reescribe de (hash, raw-bytes...) a (hash, VREF: 32 bytes). El índice de deduplicación y el repositorio de chunks direccionable por contenido residen separadamente en /gdd, con tamaño proporcional al contenido único visto en todos los jobs.
3.2 Ahorro de ancho de banda en modo Bothsides (F4, medido el 2026-04-24)
Backup completo de extremo a extremo con interceptación VREF activada (GDD_VREF_ENABLE=1) fue validado en /usr/bin (1478 archivos regulares / 1 directorio, historial de pruebas acumulado en ~15 sesiones de backup previas en el lab) en una ejecución de segunda pasada con caché caliente:
| Métrica | Valor |
|---|---|
| Archivos interceptados | 1.326 |
| Archivos en passthrough (demasiado pequeños) | 153 |
| Bytes originales de los archivos | 420.411.433 B |
| Bytes en la red (registros VREF) | 1.552.160 B |
| Ahorro de ancho de banda | 99,63% |
| Total de chunks en el daemon | 38.141 |
| Chunks nuevos (almacenados por primera vez) | 0 |
| Chunks duplicados (hash encontrado) | 38.141 |
| Tasa de deduplicación del daemon | 1,0000 |
El porcentaje de ahorro de ancho de banda está impulsado por la caché caliente: el daemon ya tenía todos los hashes de chunks de ejecuciones anteriores, por lo que las respuestas QUERY del plugin FD marcaron cada chunk como KNOWN, y la ruta STORE_REF (solo hash) dominó el tráfico de red. Con caché fría, el tráfico de red son los propios bytes de los chunks (igual que el Modo A).
La corrección que desbloqueó estos números fue un bug de higiene de asignador en el código fuente del plugin. La afirmación de «bug preexistente de Bacula» referenciada en la versión anterior de esta sección era incorrecta; no hubo bug en el upstream.
3.2.1 Brecha de restauración (conocida; rastreada como F4.8)
La restauración desde backups en modo bothsides F4 actualmente devuelve bytes VREF crudos en lugar de expandir al contenido original. Causa raíz: la ruta VREF del FD escribe chunks en el repositorio RocksDB /gdd-bothsides/ del daemon; el transform_read del driver SD en la ruta de restauración resuelve hashes de chunks contra el propio repositorio /gdd/ del driver SD. Dos instancias RocksDB distintas, sin consulta cruzada. La unificación de repositorios es el trabajo pendiente.
Mientras tanto, el Modo A (sin directiva Plugin, dedup puramente del lado SD) es la ruta lista para producción: backup job 3597 / restauración job 3604, 1.859 archivos / 414.246.812 bytes, md5sum en el /usr/bin/ls restaurado es byte a byte idéntico al original.
3.3 Tamaños clave y sobrecarga
- Chunker: FastCDC con min/prom/máx = 4 KiB / 16 KiB / 64 KiB. Cargas de trabajo
típicas estilo /usr/share fragmentan en bloques de ~16 KiB en promedio.
- Registro VREF: 20 bytes de encabezado + 40 bytes por referencia de chunk. Un archivo de 1 MiB
fragmentado en ~64 chunks produce un VREF de ~2,6 KB — ~0,25% del original.
- Entrada del índice de chunks en RocksDB: 32 bytes de hash + 24 bytes de struct de referencia.
100 M de chunks únicos = ~5,3 GB de índice (respaldado por bloom filter, 99,9% de las consultas atendidas en memoria).
4. Principios de diseño
- Sin configuración por defecto. En el primer arranque se genera el PSK. Los operadores copian
un archivo de 32 bytes a los hosts clientes. Sin bootstrap de CA, sin renovación de certificados, sin configuración con estado.
- Fallar abierto, registrar alto. Daemon inaccesible → el plugin degrada a
backup sin deduplicación con un M_WARNING en el log del job. Bacula nunca falla un job por problemas de infraestructura de nuestro plugin.
- Reutilización de primitivos > criptografía personalizada. Cada primitivo criptográfico que usamos
es un estándar bien analizado. HKDF-SHA256 + ChaCha20-Poly1305 coinciden con los valores predeterminados de TLS 1.3. La novedad es el framing — deliberadamente más simple que TLS — no la criptografía.
- Fuente única de verdad. El protocolo de red, el diseño VREF y la capa PSK están implementados una sola vez y reutilizados por todos los componentes — sin lógica duplicada entre el plugin FD y el daemon.
5. Huella de despliegue
Artefactos por host FD:
/usr/local/lib64/libgdd_ffi_fd.so(~4,5 MB)/opt/bacula/plugins/podheitor-fd-dedup-fd.so(~23 KB)/etc/podheitor/psk.key(32 B)/etc/systemd/system/bacula-fd.service.d/gdd-env.conf(~150 B)
Artefactos por host daemon:
/usr/local/bin/podheitor-gdd(~11 MB, binario estático)/etc/podheitor-gdd.toml(~2 KB)/etc/podheitor/psk.key(32 B; generado automáticamente)/etc/systemd/system/podheitor-gdd.service(~500 B)/gdd/o/gdd-bothsides/(repositorio de contenido; proporcional a datos únicos)
6. Limitaciones y problemas conocidos
- Plugin en modo fuente: solo archivos regulares en v2.0 (directorios + symlinks
requieren el bracketing FT_DIRBEGIN/FT_DIREND de Bacula). Aceptable para un primer corte enfocado en dedup de contenido; refinamiento rastreado como F4.7-dir.
- Contención de bloqueo RocksDB daemon/SD: cuando el daemon y el driver SD
están en el mismo host, no pueden compartir un único repositorio /gdd. Solución: repositorios separados (/gdd para el lado SD, /gdd-bothsides para el daemon). Corrección completa (daemon como único propietario del repositorio + driver SD vía UDS) rastreada como F4.8.
- Sin secreto futuro perfecto por defecto — ver §2.4. Use
stunnel si su modelo de amenaza lo requiere.
- Restauración en modo Bothsides (directiva Plugin +
GDD_VREF_ENABLE=1)
actualmente no rearma los bytes originales del archivo — escribe payloads VREF crudos en los archivos restaurados. La división de dos repositorios entre el daemon y el driver SD es la causa (ver §3.2.1 + F4.8). Para protección de datos en producción, use el Modo A (sin directiva Plugin).
- F5.A
gdd-fsckes la herramienta de recuperación soportada para corrupción ESM
(corrupt ESM — run gdd-fsck --rebuild-esm). Consulte la guía de operaciones para el procedimiento completo de recuperación.
7. Referencias
- Rust (lenguaje): https://www.rust-lang.org — memory-safe, sin GC, cero overhead de runtime
- RocksDB: https://rocksdb.org — motor de índice de alto rendimiento
- FastCDC: artículo original — Wen Xia et al., «FastCDC: a Fast and Efficient Content-Defined Chunking Approach for Data Deduplication»
- ChaCha20-Poly1305 / HKDF-SHA256: RFC 8439 / RFC 5869 — primitivos criptográficos estándar
Licenciamiento
PodHeitor GDD se distribuye bajo licencia comercial propietaria. Cada contrato incluye:
- Licencia de uso en producción para el número acordado de hosts Storage Daemon
- Acceso a todas las actualizaciones de la versión principal contratada
- Soporte técnico por correo con tiempo de respuesta garantizado
- Servicios profesionales opcionales: instalación, integración con entorno Bacula existente, validación de rendimiento y capacitación del equipo de operaciones
Para organizaciones que buscan reducir costos de almacenamiento y ancho de banda de backup, PodHeitor GDD ofrece deduplicación global entre jobs, clientes y pools — con retorno de inversión medible desde el primer ciclo de backup.
¿Listo para evaluar?
Contáctenos para iniciar una prueba de concepto en su entorno:
- WhatsApp / Teléfono: +1 786 726-1749
- Correo: heitor@opentechs.lat
- Consulta gratuita: https://podheitor.com/consultoria-gratis/?lang=es
Disponível em:
Português (Portugués, Brasil)
English (Inglés)
Español