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 criacao
+blob_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 arquivos
+fragment_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 hash
+release(hash)— decrementa o refcount. Se chegar a zero, o arquivo em disco +e deletado imediatamente.
+contains(hash)— verifica existencia sem ler
+ref_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 pendentes
+cas_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
createoureplicate+e 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.
+ +