PermafrostCatalog — Índice Centralizado (DuckDB)
Com dezenas de arquivos .permafrost, você precisa saber: "quais têm dados de 2022?",
"qual tem a coluna total?", "quanto estou gastando?". O Catalog resolve isso.
Criar e registrar
import permafrost as pf
cat = pf.PermafrostCatalog(".permafrost_catalog.db")
# Registrar um arquivo (lê apenas header + footer — zero decompressão)
cat.register("vendas_2022.permafrost")
cat.register("vendas_2023.permafrost", tags=["producao", "fiscal"])
# Registrar diretório inteiro
cat.register_dir("/dados/cold/", tags=["producao"], recursive=True)
O registro é idempotente — chamar register() duas vezes no mesmo arquivo
não duplica o registro.
Buscar datasets
# Busca por nome (substring)
df = cat.search(name="vendas")
# Filtrar por codec
df = cat.search(codec="lzma2")
# Filtrar por chave de partição
df = cat.search(partition_key="2023")
# Filtrar por coluna presente
df = cat.search(columns_contain="total")
# Apenas lossless
df = cat.search(lossless_only=True)
# Combinar filtros
df = cat.search(
name="vendas",
codec="lzma2",
partition_key="2023",
min_rows=50_000,
lossless_only=True,
)
print(df[["name", "codec", "rows", "mb", "partition_col"]])
Thaw via catalog
# O catalog encontra o arquivo e roteia o thaw automaticamente
df = cat.unfreeze("vendas_2023", filter={"ano": 2023})
Inspecionar chunks
# Chunks de um dataset com filtro por partition key
df_chunks = cat.search_chunks("vendas_2023", part_key="2023")
print(df_chunks[["chunk_id", "row_start", "row_end", "part_key", "byte_offset", "kb"]])
Relatório de custo
# Custo estimado por dataset
cr = cat.cost_report("glacier_deep") # ou "s3_standard", "s3_ia", "glacier"
print(cr[["name", "size_mb", "cost_monthly_usd", "cost_annual_usd", "cost_3yr_usd"]])
# Total
print(f"Total mensal: ${cr['cost_monthly_usd'].sum():.4f}")
print(f"Total 3 anos: ${cr['cost_3yr_usd'].sum():.2f}")
Verificação de integridade
# Verifica SHA-256 de todos os chunks de todos os datasets
# Não descomprime — apenas confere os hashes
ic = cat.integrity_check()
print(ic[["name", "status", "chunks_ok", "chunks_fail"]])
# name status chunks_ok chunks_fail
# vendas_2022 OK 16 0
# vendas_2023 OK 18 0
# clientes_v1 OK 4 0
SQL direto (DuckDB)
# Qualquer query DuckDB nas tabelas internas
df = cat.sql("""
SELECT
codec,
COUNT(*) as datasets,
SUM(orig_rows) as total_linhas,
ROUND(SUM(file_size_mb), 2) as total_mb,
ROUND(AVG(file_size_mb / orig_rows * 1000), 4) as mb_per_1k_rows
FROM datasets
GROUP BY codec
ORDER BY total_mb DESC
""")
# JOIN com chunks para análise granular
df = cat.sql("""
SELECT d.name, c.part_key, c.row_start, c.row_end,
ROUND(c.byte_len / 1024.0, 1) as kb
FROM datasets d
JOIN chunks c ON c.dataset_id = d.id
WHERE d.name LIKE '%vendas%'
ORDER BY c.chunk_id
""")
Schema do banco DuckDB
Tabela datasets
| Coluna | Tipo | Descrição |
|---|---|---|
| id | INTEGER PK | |
| name | VARCHAR | Nome derivado do filename |
| path | VARCHAR UNIQUE | Caminho absoluto |
| freeze_date | TIMESTAMP | |
| codec | VARCHAR | lzma2 / zstd |
| quant_level | INTEGER | 0=lossless, 1=high, 2=medium |
| orig_rows | BIGINT | |
| n_chunks | INTEGER | |
| file_size_mb | DOUBLE | |
| partition_col | VARCHAR | |
| partition_keys | VARCHAR | JSON array |
| columns | VARCHAR | JSON array |
| tags | VARCHAR | JSON array |
| schema_hash | VARCHAR | SHA-256 dos nomes das colunas |
| verified_ok | BOOLEAN | Última verificação de integridade |
Tabela chunks
| Coluna | Tipo | Descrição |
|---|---|---|
| id | INTEGER PK | |
| dataset_id | INTEGER FK → datasets | |
| chunk_id | INTEGER | Índice do chunk |
| row_start | BIGINT | |
| row_end | BIGINT | |
| part_key | VARCHAR | Valor da partition key |
| byte_offset | BIGINT | Posição no arquivo para seek |
| byte_len | BIGINT | |
| sha256 | VARCHAR |