Deduplicação global em Rust memory-safe. Bloom multi-camada, chunking de bloco variável, AEAD nativo. Tipicamente 10-20× redução em datasets corporativos reais.
- Variable-block chunking com fingerprint Rabin — dedup atravessa boundaries de arquivo.
- Bloom filter multi-layer — lookup O(1) sem hit em disco para chunks já vistos.
- Segment locality cache — taxa de acerto 95%+ em padrões de backup repetitivos.
- Adaptive tuner — ajuste de chunk size por workload (VMs, DBs, files) automaticamente.
- AEAD custom — encriptação end-to-end sem comprometer dedup ratio.
Comece em 30 dias, no mínimo 50% mais barato. Trial gratuito, RPMs e DEBs assinados, suporte direto com Heitor Faria. Substitua sua licença Veeam, Commvault ou Bacula Enterprise sem quebrar a janela noturna.
Solicitar trial gratuito de 30 dias →
1. Visão Geral
PodHeitor GDD é um mecanismo de deduplicação global e inline para PodHeitor Backup, implementado em Rust. Ele substitui a deduplicação por volume por um namespace de dedup único e compartilhado entre todos os jobs de backup em um Storage Daemon, possibilitando a eliminação de dados entre jobs, clientes e pools.
Objetivos de Design
| Objetivo | Abordagem |
|---|---|
| Dedup global (entre jobs) | Índice único RocksDB, compartilhado entre todos os jobs |
| Baixa latência para dados quentes | Bloom filter multicamada → índice → container |
| Restauração com consciência de segmentos | Chunks agrupados por localidade → prefetch antecipado de leitura |
| Seguro contra falhas | RocksDB WAL + containers append-only com CRC-32 |
| Simplicidade operacional | Métricas Prometheus, configuração TOML, comandos vacuum e scrub |
2. Arquitetura
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 Principais
3.1 Chunker de Comprimento Variável FastCDC
Content-defined chunking (CDC) usando o algoritmo FastCDC. Os limites de chunk são determinados por tabelas de gear hash com rolling hash, tornando-os estáveis diante de inserções/deleções de bytes — essencial para uma deduplicação eficaz.
Parâmetros padrão (ajustados pelo AdaptiveTuner):
| Perfil | Mín | Méd | 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 Multicamada
Bloom filter de duas camadas (quente + fria) elimina I/O no RocksDB para chunks já vistos. Em caso de miss, o mecanismo passa para a consulta do índice (sem falsos negativos no caminho de armazenamento).
- Camada quente: chunks ativos recentemente (capacidade configurável)
- Camada fria: todos os chunks históricos
- Taxa de falsos positivos: configurável (padrão: 0,1%)
- Caminho de persistência:
/opt/podheitor-gdd/bloom/— sobrevive a reinicializações
⚠️ Perda do Bloom: Se os arquivos bloom forem excluídos, o mecanismo reverte para consultas completas no índice (mais lento, porém correto). A integridade dos dados não é afetada.
3.3 Índice RocksDB
Mapeia SHA-256(chunk) → uma referência de localização no Container Store, incluindo ID do container, offset em bytes, comprimento, contador de referências e grupo de localidade para leitura antecipada. Armazenado em tamanho fixo no RocksDB para consulta e iteração eficientes. Utiliza compressão LZ4 (arquivos SST) e Zstd no nível mais baixo.
Segurança contra falhas: O RocksDB WAL garante durabilidade após flush(). Todas as entradas do índice sobrevivem a falhas de processo, desde que engine.flush() tenha sido chamado antes de uma finalização pelo SO ou queda de energia.
3.4 Container Store
Arquivos binários append-only (container_XXXXXXXX.dat) contendo dados de chunks comprimidos. Cada chunk armazena o hash SHA-256, o comprimento dos dados, flags de compressão (LZ4 ou Zstd) e um CRC-32 para detecção de corrupção silenciosa. Na leitura, o CRC-32 é sempre verificado — qualquer divergência retorna erro imediatamente, nunca corrupção silenciosa.
3.5 Rastreador de Segmentos e Cache de Leitura Antecipada
Os chunks são agrupados em segmentos (padrão: 64 chunks/segmento) por job de backup. Cada ContainerRef carrega um segment_id. Durante a restauração:
recall_block(hash)verifica primeiro o cache de leitura antecipada (O(1)).- Em caso de miss: busca o chunk e recupera seu
Segmentdo
SegmentTracker.
- Realiza prefetch de todos os chunks irmãos para o cache (limitado por
read_ahead_cache_size, padrão: 512 chunks ≈ 8 MB com média de 16 KB).
- Chamadas subsequentes de
recall_blockpara o mesmo segmento retornam instantaneamente.
Isso espelha o padrão de “localidade de container dedup” do Commvault.
3.6 Ajustador Adaptativo
Amostra continuamente a taxa de deduplicação em uma janela deslizante e ajusta o dimensionamento dos chunks:
- Alta deduplicação (>80%): chunks menores → granularidade mais fina
- Baixa deduplicação (<20%): chunks maiores → menor sobrecarga de índice
- Balanceado / Desconhecido: parâmetros padrão
3.7 Vacuum / GC
Coleta de lixo por marcação e varredura. Identifica arquivos de container sem referências de índice ativas (ref_count = 0) e os remove. Pode ser cancelado. Executa offline ou agendado (padrão: diariamente às 02:00).
3.8 Scrub / Verificação de Integridade
Varredura completa de integridade dos dados. Itera cada entrada do índice, lê e verifica o CRC de cada chunk. Retorna um relatório de scrub com:
- Contadores: chunks verificados, íntegros, corrompidos e ausentes
- Lista de chunks defeituosos com status para reparo direcionado
ChunkStatus::Corrupted = divergência de CRC/cabeçalho (inversão de bit, erro silencioso de disco). ChunkStatus::Missing = arquivo de container não encontrado (exclusão acidental, etc.).
4. Desempenho
4.1 Resultados de Benchmark
Medido em hardware de produção (servidor: 192.168.15.105). Dados: blocos 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álise de gargalo: Em disco real, o
storeé limitado pelo RocksDB WAL e pela adição ao arquivo de container. O caminho de deduplicação (bloom hit → index hit → sem escrita) é apenas marginalmente mais rápido porque a consulta ao índice ainda acessa o RocksDB.Recall a 265 µs para 4 MB = ~15 GB/s efetivos (cache de leitura antecipada servindo chunks subsequentes após o primeiro miss; o recall em disco frio seria limitado por I/O a ~128 MB/s).
4.2 Métricas de Produção ao Vivo
Do endpoint Prometheus (http://server:9420/metrics, abril de 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. Referência de Configuração
Caminho: /etc/podheitor-gdd.toml (ou via 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. Guia de Operações
6.1 Iniciar / Parar
# Systemd
systemctl start podheitor-gdd
systemctl stop podheitor-gdd
# Direto (foreground)
podheitor-gdd --config /etc/podheitor-gdd.toml
6.2 Monitoramento
# 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 (Coleta de Lixo)
O vacuum remove arquivos de container sem referências de chunk ativas. Execute-o após excluir jobs antigos do Bacula:
# Via gdd-client
gdd-client vacuum
# Estimated safe schedule: daily at 02:00 (see [vacuum] config)
6.4 Scrub (Integridade dos Dados)
O scrub lê e verifica o CRC de cada chunk. Execute após erros de disco ou antes de testes de recuperação de desastre:
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 Recuperação de Falhas
O GDD utiliza o RocksDB WAL para durabilidade do índice. Após encerramento abrupto do processo:
- O RocksDB reproduz o WAL na próxima abertura — nenhuma recuperação manual é necessária.
- Os arquivos de container são append-only com CRC — escritas parciais são detectáveis.
- O Bloom filter é opcional: se perdido, as taxas de deduplicação podem cair temporariamente
(falsos negativos → novos chunks tratados como únicos), mas a integridade dos dados é preservada pelo índice.
6.6 Considerações de Escalabilidade
| Recurso | Recomendação |
|---|---|
| RAM | ≥ 4 GB para cache RocksDB (cache_size_mb) + 2 GB SO |
| Armazenamento | SSD para o caminho do índice; HDD aceitável para dados do container |
| CPU | 4+ núcleos; dedup sem bloqueio em nível de chunk |
max_concurrent_jobs |
Comece com 8; aumente se a CPU permitir |
6.7 Hardening Futuro (Direção Gen2)
A validação em campo demonstrou que o scrub periódico é útil, mas não é a experiência ideal em regime estacionário para operadores. Uma direção de design mais robusta é reduzir a necessidade de scrub completo tornando a consistência dos metadados auto-recuperável e reproduzível na inicialização.
Próximos passos recomendados:
- Journal de commit de container — persistir marcadores de commit ou
manifestos por container, para que o mecanismo possa identificar o último offset durável de chunk sem uma varredura completa.
- Reprodução na inicialização / fsck de cauda — verificar apenas a cauda recente dos containers
mais o estado do journal durante a inicialização do daemon, em vez de exigir scrub global periódico para confiança em consistência.
- Checkpointing atômico de metadados — gravar metadados de chunk em sequência reproduzível
(append -> fsync -> index insert -> checkpoint) para que referências de índice pendentes não sobrevivam a falhas.
- Reparo guiado por manifesto — reconstruir ou remover referências de índice inválidas a partir de
manifestos de container diretamente, evitando uma varredura completa do índice nos caminhos de recuperação mais comuns.
6.8 Redução Adicional de Latência do Índice
O design atual já reduz o I/O do índice via Bloom filter em camadas e grandes caches do RocksDB. Melhorias adicionais que valem a pena implementar:
- Cache RAM de fingerprints quentes à frente do RocksDB para chunks ativos recentemente.
- Consultas/escritas em lote no índice para melhor compactação e localidade de syscall.
- Partições de índice fragmentadas por prefixo para reduzir contenção e melhorar
localidade de cache.
- Mídia rápida dedicada para WAL / L0, com níveis SST mais frios em
armazenamento mais barato quando necessário.
- Cache de resultado negativo para misses repetidos durante ingestão de dados únicos.
- Manifestos locais de container para ignorar acessos ao índice global em alguns caminhos de
restauração e reparo.
7. Considerações de Segurança
O PodHeitor GDD é implementado em Rust, proporcionando segurança de memória por design — ausência de buffer overflows, use-after-free e condições de corrida que afetam implementações C/C++ equivalentes.
- Sem exposição de rede por padrão — endpoint Prometheus é apenas LAN.
- Sem armazenamento de segredos — a configuração contém apenas caminhos e tamanhos.
- Arquivos de container são binários; dados de chunk são comprimidos, não criptografados.
Adicione criptografia de disco completo na camada do SO, se necessário.
- As impressões digitais SHA-256 são resistentes a colisões para fins de deduplicação, mas não
constituem um mecanismo de autenticação criptográfica.
8. Limitações Conhecidas e Roadmap
| Item | Status |
|---|---|
| Cache de leitura antecipada (localidade de segmento) | ✅ Implementado |
| Recuperação de falhas (RocksDB WAL + CRC) | ✅ Testado |
| Scrub / verificação de integridade | ✅ Implementado |
| Benchmarks de desempenho | ✅ Baseline medido |
| Re-semeadura do Bloom filter após perda | 🔄 Automático (bloom vazio → fallback para índice) |
| Leitura antecipada de restauração para segmentos entre sessões | 🔄 Apenas na sessão (cache de segmento em RAM) |
| Scrub paralelo (multithreaded) | 📋 Planejado |
| Suporte a container criptografado | 📋 Planejado |
| Interface Plugin Bacula (integração em produção) | 🔄 Em desenvolvimento |
PodHeitor GDD é desenvolvido como parte do ecossistema PodHeitor Bacula. Entre em contato para licenciamento comercial.
PodHeitor GDD V2 — Whitepaper de Desempenho
Público-alvo: arquitetos de armazenamento, operadores Bacula, tomadores de decisão avaliando deduplicação no lado da fonte para ambientes de backup conectados por WAN. Versão: V2 (F4 entregue, 2026-04-24).
1. Resumo executivo
PodHeitor GDD V2 reduz armazenamento de backup Bacula e largura de banda de rede via deduplicação baseada em conteúdo com dois modos de implantação:
- Modo Storage (F3, entregue): o host do Storage Daemon intercepta registros de dados
de arquivo recebidos, fragmenta-os com FastCDC, substitui conteúdo correspondente por referências de 32 bytes (VREFs) e armazena chunks únicos em um repositório local endereçável por conteúdo. Medido em /usr/bin (408,8 MiB): arquivo de volume em disco reduzido para 4,45 MiB — 1,09% dos bytes do cliente, compressão de 91×.
- Modo Bothsides (F4, entregue): o plugin do File Daemon fragmenta o conteúdo no
cliente, troca hashes com um daemon remoto (HKDF-SHA256 + ChaCha20-Poly1305 AEAD sobre TCP) e emite apenas as referências de hash no fluxo Bacula — transferindo os bytes reais apenas para chunks que o daemon ainda não possui.
Ambos os modos utilizam o mesmo formato de container em disco (VOLUME_FORMAT_V2). O Modo A (somente storage) compartilha seu índice de deduplicação com o caminho de restauração e está pronto para produção: roundtrip byte a byte validado em um backup de 414 MB em /usr/bin (job 3597 → job 3604 em 2026-04-24). O Modo B (bothsides) atingiu 99,63% de economia medida de largura de banda com cache quente (420 MB → 1,55 MB), mas seu caminho de restauração atualmente não consegue remontar os bytes originais porque os repositórios RocksDB do FD e do SD são instâncias separadas — use o Modo B para benchmarking de economia de largura de banda, não em produção, até que a unificação de repositórios (F4.8) seja lançada.
2. A história de desempenho por trás da nossa escolha de PSK
2.1 Por que não X.509 mTLS
O padrão natural para um listener de servidor em produção é TLS 1.3 com autenticação mútua por certificado. Avaliamos e rejeitamos por:
- Custo de handshake: TLS 1.3 baseado em certificado executa validação da cadeia de certificados + verificação de assinatura RSA/ECDSA
+ OCSP opcional em cada nova conexão. Em hardware de servidor comum, isso representa ~3–5 ms por handshake. Multiplicado por milhares de clientes de backup abrindo uma sessão por job, em um cron noturno, a sobrecarga fixa domina o throughput de jobs pequenos.
- Peso operacional: bootstrap de CA, renovação de certificados, gerenciamento da cadeia de confiança
e distribuição de certificados por host são tarefas administrativas recorrentes — o tipo que se deteriora silenciosamente por e-mails de “expirando em 30 dias” e eventualmente causa interrupções em produção.
- Risco de canal lateral: assinatura RSA vaza temporização micro-arquitetural; o
abuso de nonce no ECDSA é um problema bem documentado. Nenhum se aplica ao nosso caso de uso.
2.2 Por que TLS 1.3 PSK foi insuficiente
TLS 1.3 suporta Pre-Shared Keys externas (RFC 8446 §4.2.11), o que ignora a validação da cadeia de certificados e reduz o custo de handshake para ~1–2 ms. Porém, o suporte a PSK externo na versão estável é experimental, requer contornos não seguros e adiciona peso binário significativo em cada host FD para uma única suíte de cifras que usaríamos exclusivamente.
2.3 Nosso framing AEAD personalizado
Mantivemos os primitivos criptográficos que o TLS 1.3 usa — HKDF-SHA256 para derivação de chaves, ChaCha20-Poly1305 para AEAD — e ignoramos completamente a máquina de estados TLS. O resultado:
| Camada | TLS 1.3 baseado em cert | TLS 1.3 + PSK | PodHeitor GDD V2 PSK |
|---|---|---|---|
| Round-trips de handshake | 1 | 1 (0-RTT possível) | 1 |
| CPU de handshake | cadeia de cert + verificação de assinatura | HKDF + configuração AEAD | HKDF expand (×2 direções) |
| Handshake (tempo real) | ~3–5 ms | ~1–2 ms | ~0,1–0,3 ms |
| AEAD por frame | ChaCha20-Poly1305 | ChaCha20-Poly1305 | ChaCha20-Poly1305 |
| Throughput em regime permanente | idêntico | idêntico | idêntico |
| Tamanho binário adicionado | ~200 KB (TLS completo) | ~150 KB (TLS PSK) | ~45 KB (chacha20poly1305 + hkdf) |
| Arquivos de configuração por host | cert CA + cert cliente + chave cliente + cadeia de confiança | arquivo PSK | arquivo PSK |
| Artefatos com expiração | certs (renovação anual) | PSK (definido pelo usuário) | PSK (definido pelo usuário) |
Conclusão: confidencialidade + integridade de rede idênticas, 5–10× menor CPU de handshake que TLS 1.3 PSK e 10–50× menor que TLS baseado em certificado, e a menor superfície operacional possível (um arquivo de 32 bytes por site).
2.4 Equivalência do modelo de ameaça
Ambas as abordagens protegem contra:
- Escuta passiva: criptografia AEAD com nonces novos.
- Injeção/replay ativo de MitM: nonce monotônico estrito + tag Poly1305.
- Frame adulterado/chave errada: falha silenciosa, conexão encerrada.
Nenhuma das abordagens protege contra:
- Chave privada/PSK comprometida: catastrófico em ambos os modelos. TLS baseado em cert
pode revogar via CRL/OCSP (com janela de propagação); nosso PSK é rotacionado com recarga SIGHUP com janela de carência.
- Adversário quântico contra a chave compartilhada: fora do escopo para ambos.
Diferenças:
- Sigilo futuro perfeito (PFS): nossa derivação PSK é determinística dado os
nonces do handshake. Um atacante que posteriormente recuperar o PSK e tiver capturado ambos os hellos pode descriptografar o tráfego gravado. TLS completo com psk_dhe_ke adiciona ECDH efêmero e fornece PFS. Se seu modelo de ameaça requer PFS contra adversários estatais realizando captura de longa duração, coloque um túnel SSH ou stunnel com suítes de cifras com sigilo futuro na frente do listener — o protocolo V2 é agnóstico em relação ao transporte.
2.5 Como as economias de desempenho se acumulam
Uma implantação Bacula de médio porte: 200 clientes, 50 jobs/cliente/mês, 1 sessão/job = 10.000 sessões/mês. Economia de handshake por sessão: ~4 ms (vs TLS baseado em cert). CPU agregada economizada: 10.000 × 4 ms = 40 segundos de CPU por mês.
Por si só, irrelevante. Mas multiplicado por:
- Tempo de operador economizado: sem CA para gerenciar = 1–2 incidentes de suporte a
menos por trimestre em implantações típicas.
- Orçamento de latência recuperado: uma economia de 4 ms no handshake é suficiente para fazer a
diferença entre “rápido o suficiente para não ser notado” e “rápido o suficiente para não provocar timeout e retry” em links WAN instáveis.
- Economia no tamanho binário: 150 KB a menos por host FD significa implantações mais rápidas em
espelhos de pacotes lentos e menos pressão de memória em clientes embarcados.
A vitória real é o efeito de segunda ordem: porque o custo operacional é um único arquivo a distribuir, os sites implantam o PodHeitor GDD. Eles não o descartam como “muita burocracia de certificados”. O argumento de desempenho é honesto, mas a história de adoção é o destaque.
3. Resultados medidos
3.1 Compressão em modo Storage (F3)
Carga de trabalho: /usr/bin em uma imagem padrão Oracle Linux 9.6, 1847 arquivos, 408.877.776 bytes.
- Arquivo de volume em disco: 4.667.193 bytes → 1,09% dos bytes do cliente, redução de 91×
- Container store /gdd: ~110 MB (chunks + índice + manifestos)
- Recall verificado: reconstrução byte a byte dos arquivos originais.
O arquivo de volume diminui porque cada registro de dados de arquivo em um bloco Bacula é reescrito de (hash, raw-bytes...) para (hash, VREF: 32 bytes). O índice de deduplicação e o repositório de chunks endereçável por conteúdo ficam separadamente em /gdd, com tamanho proporcional ao conteúdo único visto em todos os jobs.
3.2 Economia de largura de banda em modo Bothsides (F4, medido em 2026-04-24)
Backup completo de ponta a ponta com interceptação VREF ativada (GDD_VREF_ENABLE=1) foi validado em /usr/bin (1478 arquivos regulares / 1 diretório, histórico de teste acumulado em ~15 sessões de backup anteriores no lab) em uma execução de segunda passagem com cache quente:
| Métrica | Valor |
|---|---|
| Arquivos interceptados | 1.326 |
| Arquivos em passthrough (muito pequenos) | 153 |
| Bytes originais dos arquivos | 420.411.433 B |
| Bytes na rede (registros VREF) | 1.552.160 B |
| Economia de largura de banda | 99,63% |
| Total de chunks no daemon | 38.141 |
| Chunks novos (armazenados pela primeira vez) | 0 |
| Chunks duplicados (hash encontrado) | 38.141 |
| Taxa de deduplicação do daemon | 1,0000 |
O percentual de economia de largura de banda é impulsionado pelo cache quente: o daemon já possuía todo hash de chunk de execuções anteriores, portanto as respostas QUERY do plugin FD marcaram cada chunk como KNOWN, e o caminho STORE_REF (somente hash) dominou o tráfego de rede. Com cache frio, o tráfego de rede são os próprios bytes dos chunks (igual ao Modo A).
A correção que desbloqueou esses números foi um bug de higiene de alocador no código-fonte do plugin. A afirmação de “bug pré-existente do Bacula” referenciada na versão anterior desta seção estava errada; não houve bug no upstream.
3.2.1 Lacuna de restauração (conhecida; rastreada como F4.8)
A restauração a partir de backups em modo bothsides F4 atualmente retorna bytes VREF brutos em vez de expandir para o conteúdo original. Causa raiz: o caminho VREF do FD grava chunks no repositório RocksDB /gdd-bothsides/ do daemon; o transform_read do driver SD no caminho de restauração resolve hashes de chunks contra o próprio repositório /gdd/ do driver SD. Duas instâncias RocksDB distintas, sem consulta cruzada. A unificação dos repositórios é o trabalho restante.
Enquanto isso, o Modo A (sem diretiva Plugin, dedup puramente do lado SD) é o caminho pronto para produção: backup job 3597 / restauração job 3604, 1.859 arquivos / 414.246.812 bytes, md5sum no /usr/bin/ls restaurado é byte a byte idêntico ao original.
3.3 Tamanhos-chave e sobrecarga
- Chunker: FastCDC com min/méd/máx = 4 KiB / 16 KiB / 64 KiB. Cargas de trabalho
típicas estilo /usr/share fragmentam em blocos de ~16 KiB em média.
- Registro VREF: 20 bytes de cabeçalho + 40 bytes por referência de chunk. Um arquivo de 1 MiB
fragmentado em ~64 chunks produz um VREF de ~2,6 KB — ~0,25% do original.
- Entrada do índice de chunks no RocksDB: 32 bytes de hash + 24 bytes de struct de referência.
100 M de chunks únicos = ~5,3 GB de índice (respaldado por bloom filter, 99,9% das consultas atendidas em memória).
4. Princípios de design
- Zero configuração por padrão. Na primeira inicialização, o PSK é gerado automaticamente. Operadores copiam
um arquivo de 32 bytes para os hosts clientes. Sem bootstrap de CA, sem renovação de certificados, sem configuração com estado.
- Falhar aberto, registrar alto. Daemon inacessível → plugin degrada para
backup sem deduplicação com um M_WARNING no log do job. O Bacula nunca falha um job por problemas de infraestrutura do nosso plugin.
- Reutilização de primitivos > criptografia customizada. Cada primitivo criptográfico que usamos
é um padrão bem analisado. HKDF-SHA256 + ChaCha20-Poly1305 correspondem aos padrões do TLS 1.3. A novidade é o framing — deliberadamente mais simples que TLS — não a criptografia.
- Fonte única de verdade. O protocolo de rede, o layout VREF e a camada PSK são implementados uma única vez e reutilizados por todos os componentes — sem lógica duplicada entre o plugin FD e o daemon.
5. Footprint de implantação
Artefatos 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)
Artefatos por host daemon:
/usr/local/bin/podheitor-gdd(~11 MB, binário estático)/etc/podheitor-gdd.toml(~2 KB)/etc/podheitor/psk.key(32 B; gerado automaticamente)/etc/systemd/system/podheitor-gdd.service(~500 B)/gdd/ou/gdd-bothsides/(repositório de conteúdo; proporcional a dados únicos)
6. Limitações e problemas conhecidos
- Plugin em modo fonte: apenas arquivos regulares na v2.0 (diretórios + symlinks
necessitam do bracketing FT_DIRBEGIN/FT_DIREND do Bacula). Aceitável para um primeiro corte focado em dedup de conteúdo; refinamento rastreado como F4.7-dir.
- Contenção de bloqueio RocksDB daemon/SD: quando o daemon e o driver SD
estão no mesmo host, eles não podem compartilhar um único repositório /gdd. Solução: repositórios separados (/gdd para o lado SD, /gdd-bothsides para o daemon). Correção completa (daemon como único proprietário do repositório + driver SD via UDS) rastreada como F4.8.
- Sem sigilo futuro perfeito por padrão — ver §2.4. Use
stunnel se seu modelo de ameaça exigir.
- Restauração em modo Bothsides (diretiva Plugin +
GDD_VREF_ENABLE=1)
atualmente não remonta os bytes originais do arquivo — grava payloads VREF brutos nos arquivos restaurados. A divisão de dois repositórios entre o daemon e o driver SD é a causa (ver §3.2.1 + F4.8). Para proteção de dados em produção, use o Modo A (sem diretiva Plugin).
- F5.A
gdd-fscké a ferramenta de recuperação suportada para corrupção ESM
(corrupt ESM — run gdd-fsck --rebuild-esm). Consulte a documentação de operação para o procedimento completo.
7. Referências
- Rust (linguagem): https://www.rust-lang.org — memory-safe, sem GC, zero overhead de runtime
- RocksDB: https://rocksdb.org — motor de índice de alto desempenho
- FastCDC: artigo 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 padrão
Licenciamento
O PodHeitor GDD é distribuído sob licença comercial proprietária. Cada contrato inclui:
- Licença de uso em produção para o número acordado de hosts Storage Daemon
- Acesso a todas as atualizações da versão principal contratada
- Suporte técnico por e-mail com tempo de resposta garantido
- Serviços profissionais opcionais: instalação, integração com ambiente Bacula existente, validação de desempenho e treinamento da equipe de operações
Para organizações que buscam reduzir custos de armazenamento e largura de banda de backup, o PodHeitor GDD oferece deduplicação global entre jobs, clientes e pools — com retorno sobre investimento mensurável desde o primeiro ciclo de backup.
Pronto para avaliar?
Entre em contato para iniciar uma prova de conceito em seu ambiente:
- WhatsApp / Telefone: +1 786 726-1749
- E-mail: heitor@opentechs.lat
- Consultoria gratuita: https://podheitor.com/consultoria-gratis/
Disponível em:
Português
English (Inglês)
Español (Espanhol)