Fase 4: Deduplicacao de Armazenamento
2026-02-15
Quando multiplas tesseras compartilham a mesma foto, o mesmo clipe de audio ou os mesmos dados de fragmento, a camada de armazenamento antiga mantinha copias separadas de cada. Em um no armazenando milhares de tesseras para a rede, essa duplicacao se acumula rapidamente. A Fase 4 continua com deduplicacao de armazenamento: um armazenamento enderecavel por conteudo (CAS) que garante que cada dado unico seja armazenado exatamente uma vez em disco, independentemente de quantas tesseras o referenciam.
O design e simples e comprovado: hash do conteudo com BLAKE3, usar o hash como nome do arquivo e manter uma contagem de referencias no SQLite. Quando duas tesseras incluem a mesma foto de 5 MB, um arquivo existe em disco com refcount 2. Quando uma tessera e deletada, o refcount cai para 1 e o arquivo permanece. Quando a ultima referencia e liberada, uma varredura periodica limpa o orfao.
O que foi construido
Migracao do esquema CAS (tesseras-storage/migrations/004_dedup.sql) — Tres
novas tabelas:
cas_objects— rastreia cada objeto no armazenamento: hash BLAKE3 (chave primaria), tamanho em bytes, contagem de referencias e timestamp de criacaoblob_refs— mapeia identificadores logicos de blobs (hash da tessera + hash da memoria + nome do arquivo) para hashes CAS, substituindo a convencao antiga de caminhos no sistema de arquivosfragment_refs— mapeia identificadores logicos de fragmentos (hash da tessera + indice do fragmento) para hashes CAS, substituindo o antigo layout do diretoriofragments/
Indices nas colunas de hash garantem lookups O(1) durante leituras e contagem de referencias.
CasStore (tesseras-storage/src/cas.rs) — O motor central de armazenamento
enderecavel por conteudo. Arquivos sao armazenados sob um diretorio de prefixo
de dois niveis: <raiz>/<prefixo-hex-2-chars>/<hash-completo>.blob. O
armazenamento fornece cinco operacoes:
put(hash, data)— escreve dados em disco se ainda nao presente, incrementa o refcount. Retorna se ocorreu um hit de deduplicacao.get(hash)— le dados do disco pelo hashrelease(hash)— decrementa o refcount. Se chegar a zero, o arquivo em disco e deletado imediatamente.contains(hash)— verifica existencia sem lerref_count(hash)— retorna a contagem de referencias atual
Todas as operacoes sao atomicas dentro de uma unica transacao SQLite. O refcount e a fonte de verdade — se o refcount diz que o objeto existe, o arquivo deve estar em disco.
FsBlobStore com CAS (tesseras-storage/src/blob.rs) — Reescrito para
delegar todo armazenamento ao CAS. Quando um blob e escrito, seu hash BLAKE3 e
computado e passado para cas.put(). Uma linha em blob_refs mapeia o caminho
logico (tessera + memoria + arquivo) para o hash CAS. Leituras buscam o hash CAS
via blob_refs e leem de cas.get(). Deletar uma tessera libera todas as suas
referencias de blob em uma unica transacao.
FsFragmentStore com CAS (tesseras-storage/src/fragment.rs) — Mesmo padrao
para fragmentos codificados com erasure coding. O checksum BLAKE3 de cada
fragmento ja e computado durante a codificacao Reed-Solomon, entao e usado
diretamente como chave CAS. A verificacao de fragmentos agora checa o hash CAS
ao inves de recomputar do zero — se o CAS diz que os dados estao intactos,
estao.
Coletor de lixo sweep (cas.rs:sweep()) — Uma passagem periodica de GC que
trata tres casos limite que o caminho normal de refcount nao consegue:
- Arquivos orfaos — arquivos em disco sem linha correspondente em
cas_objects. Pode acontecer apos um crash durante escrita. Arquivos com menos de 1 hora sao pulados (periodo de graca para escritas em andamento); orfaos mais antigos sao deletados. - Refcounts vazados — linhas em
cas_objectscom refcount zero que nao foram limpas (ex: se o processo morreu entre decrementar e deletar). Essas linhas sao removidas. - Idempotente — executar sweep duas vezes produz o mesmo resultado.
O sweep e conectado ao loop de reparo existente em tesseras-replication, entao
roda automaticamente a cada 24 horas junto com as verificacoes de saude dos
fragmentos.
Migracao do layout antigo (tesseras-storage/src/migration.rs) — Uma
estrategia de migracao copy-first que move dados do layout antigo baseado em
diretorios (blobs/<tessera>/<memoria>/<arquivo> e
fragments/<tessera>/<indice>.shard) para o CAS. A migracao:
- Verifica a versao de armazenamento em
storage_meta(versao 1 = layout antigo, versao 2 = CAS) - Percorre os diretorios antigos
blobs/efragments/ - Computa hashes BLAKE3 e insere no CAS via
put()— duplicatas sao automaticamente deduplicadas - Cria entradas correspondentes em
blob_refs/fragment_refs - Remove diretorios antigos somente apos todos os dados estarem seguros no CAS
- Atualiza a versao de armazenamento para 2
A migracao roda na inicializacao do daemon, e idempotente (segura para re-executar) e reporta estatisticas: arquivos migrados, duplicatas encontradas, bytes economizados.
Metricas Prometheus (tesseras-storage/src/metrics.rs) — Dez novas metricas
para observabilidade:
| Metrica | Descricao |
|---|---|
cas_objects_total | Total de objetos unicos no CAS |
cas_bytes_total | Total de bytes armazenados |
cas_dedup_hits_total | Numero de escritas que encontraram um objeto existente |
cas_bytes_saved_total | Bytes economizados por deduplicacao |
cas_gc_refcount_deletions_total | Objetos deletados quando refcount chegou a zero |
cas_gc_sweep_orphans_cleaned_total | Arquivos orfaos removidos pelo sweep |
cas_gc_sweep_leaked_refs_cleaned_total | Linhas de refcount vazadas limpas |
cas_gc_sweep_skipped_young_total | Orfaos jovens pulados (periodo de graca) |
cas_gc_sweep_duration_seconds | Tempo gasto no sweep GC |
Testes baseados em propriedades — Dois testes proptest verificam invariantes do CAS sob entradas aleatorias:
refcount_matches_actual_refs— apos N operacoes aleatorias de put/release, o refcount sempre corresponde ao numero real de referencias pendentescas_path_is_deterministic— o mesmo hash sempre produz o mesmo caminho no sistema de arquivos
Atualizacao de testes de integracao — Todos os testes de integracao em
tesseras-core, tesseras-replication, tesseras-embedded e tesseras-cli
atualizados para os novos construtores com CAS. Testes de deteccao de
adulteracao atualizados para funcionar com o layout de diretorio CAS.
347 testes passam em todo o workspace. Clippy limpo com -D warnings.
Decisoes de arquitetura
- BLAKE3 como chave CAS: o hash de conteudo que ja computamos para
verificacao de integridade serve tambem como chave de deduplicacao. Nenhuma
etapa adicional de hashing — o hash computado durante
createoureplicatee reutilizado como endereco CAS. - Refcount SQLite ao inves de reflinks do sistema de arquivos: consideramos usar copy-on-write no nivel do sistema de arquivos (reflinks em btrfs/XFS), mas isso amarraria o Tesseras a sistemas de arquivos especificos. Refcounting em SQLite funciona em qualquer sistema de arquivos, incluindo FAT32 em pendrives baratos e ext4 em Raspberry Pis.
- Diretorios de prefixo hexadecimal de dois niveis: armazenar todos os
objetos CAS em um diretorio plano desaceleraria sistemas de arquivos com
milhoes de entradas. A divisao
<prefixo 2 chars>/limita qualquer diretorio individual a ~65k entradas antes de um segundo nivel ser necessario. Isso segue a abordagem usada pelo object store do Git. - Periodo de graca para arquivos orfaos: o sweep GC pula arquivos com menos de 1 hora para evitar deletar objetos sendo escritos por uma operacao concorrente. Esta e uma escolha pragmatica — troca uma pequena janela de potenciais orfaos por seguranca contra crashes sem exigir fsync ou commit de duas fases.
- Migracao copy-first: a migracao copia dados para o CAS antes de remover diretorios antigos. Se o processo for interrompido, os dados antigos permanecem intactos e a migracao pode ser re-executada. Isso e mais lento que mover arquivos mas garante zero perda de dados.
- Sweep no loop de reparo: ao inves de adicionar um timer separado de GC, o sweep CAS aproveita o loop de reparo existente de 24 horas. Isso mantem o daemon simples — um unico ciclo de manutencao em segundo plano cuida tanto da saude dos fragmentos quanto da limpeza de armazenamento.
O que vem a seguir
- Fase 4 continuacao — auditorias de seguranca, empacotamento para OS (Alpine, Arch, Debian, OpenBSD, FreeBSD)
- Fase 5: Exploracao e Cultura — navegador publico de tesseras por era/localizacao/tema/idioma, curadoria institucional, integracao genealogica (FamilySearch, Ancestry), exportacao para midia fisica (M-DISC, microfilme, papel livre de acido com QR), contexto assistido por IA
A deduplicacao de armazenamento completa a historia de eficiencia de armazenamento do Tesseras. Um no que armazena fragmentos para milhares de usuarios — comum para nos institucionais e nos completos sempre ligados — agora paga o custo de disco apenas por dados unicos. Combinado com codificacao de apagamento Reed-Solomon (que ja minimiza redundancia no nivel da rede), o sistema alcanca armazenamento eficiente tanto nas camadas local quanto distribuida.