Entrega E2 · Ingestão em lote e captura de mudanças
Entregue na tag e2 (commit 95a82b4, 16/10/2026). Esta página tem três partes: a arquitetura, num diagrama interativo; os entregáveis, o que foi construído e onde está; e o checklist de aceite da disciplina, item a item, com o lugar exato onde conferir.
Arquitetura
O dado sai da Imprensa Nacional e chega ao DuckDB por dois caminhos de natureza diferente, como a E2 pede: um lote mensal, idempotente, e um fluxo de mudanças que leva ao Delta o que a curadoria altera no OLTP. Clique numa caixa.
A mesma figura, estática, para imprimir ou colar numa apresentação: arquitetura-e2.svg.
Entregáveis
| O que | Onde está | Como conferir |
|---|---|---|
| Ingestão mensal da fonte pública, idempotente | make ingest-mensal MES=AAAA-MM · src/dou/ (Python) | 2ª execução: gravadas=0 inalteradas=96; banco idêntico linha a linha |
| OLTP de curadoria com versões e fila de órgãos | Postgres 16 · sql/ · ato, classificacao, orgao_alias | make test (23 testes de banco em transação desfeita) |
Tabela Delta em formato aberto (Parquet, ano=, zstd) | make lake · data/silver/atos/ · src/lake/ | 2ª execução: inseridas=0; DuckDB lê com delta_scan |
| CDC por slot lógico, sem Kafka | make cdc · src/lake/cdc.py · wal_level=logical | make cdc-demo: reclassifica, chega ao Delta, restaura |
| Semântica de entrega demonstrada | make cdc-chaos · scripts/cdc_chaos_demo.sh | queda forçada (código 42) → reexecução sem duplicar |
| Dado atrasado e janela | make ingest (janela de 7 dias) · tests/lake/test_atrasado.sh | ato de 2024 chegado hoje cai em ano=2024 |
| ADRs da E2 | 0005 ingestão · 0006 Delta/zstd · 0007 CDC | medições em docs/adr/medicoes/ |
| Diários das semanas 7 a 10 | docs/diario/ | Processo |
| Migração do ingestor Go → Python (#51–#55) | src/dou/, provada byte a byte e linha a linha contra o Go | plano · PRs #58, #60, #62, #63 |
Checklist de aceite
Cada item do checklist da E2, na ordem da página da disciplina, com o lugar exato onde conferir.
Conferir em máquina limpa
git clone https://github.com/Luidooo/conselhos-vivos.git && cd conselhos-vivos
make setup # cria o .env, sobe o Postgres, aplica as migrações e carrega a planilha
make test # inclui a idempotência da gravação e a janela de reprocessamento
make ingest-mensal MES=2026-08 # baixa agosto de 2026 dos dados abertos e grava as matérias de conselho
make ingest-mensal MES=2026-08 # de novo: 0 gravadas, nada muda no banco
make lake # OLTP → tabela Delta em data/silver/atos/; de novo: 0 inseridas
make cdc-demo # reclassifica um ato e mostra a mudança atravessando o slot até o Delta
make cdc-chaos # derruba o consumidor pós-gravação e reprocessa provando que nada duplicaPré-requisitos: Docker com Compose v2 e make. A ingestão mensal não precisa de credencial. Detalhes em Como rodar.
1. Ingestão em lote da fonte pública, idempotente e reexecutável
Fonte: o Diário Oficial da União em XML, pelos dados abertos da Imprensa Nacional: um ZIP por mês, público e sem login, com o histórico desde 2002. O INLABS, que publica o dia, continua disponível pelo make ingest, mas recusa login automático; por isso a fonte automática é a mensal (#42).
Um comando:
make ingest-mensal MES=AAAA-MMbaixa a Seção 1 do mês paradata/bronze/dados-abertos/e grava as matérias de conselho emato. Mês já em disco não é baixado de novo. Conferir: Ingerir um mês pelos dados abertos.Idempotente: cada matéria é comparada com todas as versões dela no banco; igual, nada é gravado; diferente, entra como versão nova, e as anteriores ficam. Conferir: Gravar as matérias no banco.
Medido com dado real, num banco limpo, com agosto de 2026 (8.590 matérias, 52,6 MB):
Execução gravadas inalteradas órgão desconhecido falhou 1ª 96 0 8.494 0 2ª 0 96 8.494 0 Depois da 2ª execução,
ato,orgao_aliaseclassificacaosaíram idênticos linha a linha. Conferir: diário da semana 10 e PR #43.Testado:
TestPersistTwiceLeavesTheDatabaseIdenticalgrava a mesma edição duas vezes e compara o banco linha a linha, comidecriado_em; roda nomake teste no CI.
2. Fluxo de mudanças a partir do OLTP (CDC)
- Decodificação lógica do próprio Postgres, sem Kafka nem Debezium: o banco sobe com
wal_level=logical, um slot (conselhos_lake, plugintest_decoding) guarda as mudanças declassificacao, emake cdcas aplica na tabela Delta. O slot só diz quais atos mudaram; o consumidor relê esses atos do OLTP com a mesma consulta domake lakee grava porMERGEemato_id. Conferir: Fluxo de mudanças do OLTP ao Delta e ADR 0007. - O CDC viu uma mudança de verdade:
make cdc-demoreclassifica um ato (umINSERTnovo, porqueclassificacaoé insert-only), consome o slot, mostra a linha nova no Delta e depois restaura a tipologia original pelo mesmo caminho. Testado portests/lake/test_cdc.sh, que roda nomake test. - Cobertura declarada: o fluxo cobre
INSERTemclassificacao; ato novo do DOU, versão nova e troca de nome de órgão continuam entrando pelomake lake. - Medido (ADR 0007, script em
docs/adr/medicoes/0007-cdc/): recarga completa dos 215 atos ~68 ms contra ~58 ms do incremental de um ato; o slot consumido retém 56 bytes de WAL, e uma reclassificação pendente, 512 bytes.
3. Camada de armazenamento analítico em formato aberto
- Tabela Delta sobre Parquet em
data/silver/atos/, uma tabela consolidada (ato, órgão vigente na data, tipologia vigente), particionada por ano e gravada porMERGEemato_ida partir do OLTP com a bibliotecadeltalake(Rust/Python, sem JVM). Lida pelo DuckDB comdelta_scan. Conferir: ADR 0006. - Idempotente:
make lakeduas vezes com os 311 atos (215 da planilha + 96 do DOU de agosto): 1ªinseridas=311, 2ªinseridas=0 atualizadas=0 inalteradas=311. Testado portests/lake/test_lake.sh, que compara a contagem do DuckDB com ocount(*)do banco. - Delta × Iceberg com critério explícito (catálogo,
MERGEem Python puro, leitura no DuckDB) no ADR 0006.
4. Semântica de entrega declarada e demonstrada
- Declarada: pelo menos uma vez na leitura, efetivamente uma vez no destino. O consumidor espia o slot (
pg_logical_slot_peek_changes), grava o Delta porMERGEatômico na chave primária (ato_id) e checksum de integridade, e só então consome até o último LSN (pg_logical_slot_get_changes). Se o processo cair antes de avançar o slot, o evento permanece no WAL e é entregue de novo na próxima execução (at-least-once); oMERGEidempotente detecta que a versão no Delta já coincide e absorve a repetição (inalteradas=N,inseridas=0,atualizadas=0), assegurando semântica effectively-once. Conferir: ADR 0007, decisão 3. - Demonstrada por teste automatizado:
tests/lake/test_cdc.sh(integrado aomake teste CI) simula a interrupção forçada (--falhar-apos-gravar, exit code 42), confirma que o slot permaneceu sem avançar (pendentes > 0), reexecuta o consumidor normal e afere via DuckDB que a contagem total de registros no Delta Lake permaneceu estritamente inalterada e com 0 duplicações. - Demonstrada por comando interativo:
make cdc-chaosroda o roteiro reproduzívelscripts/cdc_chaos_demo.sh: reclassifica um ato no OLTP, derruba o consumidor intencionalmente após o MERGE no Delta, inspeciona o slot e o Delta Lake durante a falha, reexecuta o consumidor e valida via DuckDB que a contagem de atos (215) se mantém idêntica, sem linhas duplicadas.
5. Dado atrasado e reprocessamento de janela
Janela de dias:
make ingestbusca e grava de novo os últimosJANELAdias (padrão 7, provisório), do mais antigo para o mais novo;make janelalista os dias sem baixar nada. Conferir: Gravar as matérias no banco, no parágrafo "Dado atrasado".O ato atrasado fica na data em que foi publicado, não na data em que chegou. Testado por
TestPersistTakesAMateriaThatArrivedLate; a conta da janela, portests/ingest/test_janela.sh.O que chega depois da janela não entra sozinho; está escrito o que fazer.
Na fonte mensal não há janela: o ZIP de um mês fechado já traz a edição extra (
DO1E) de todos os dias, e uma retificação sai como matéria nova, na data em que foi publicada.No Delta, demonstrado de ponta a ponta:
tests/lake/test_atrasado.shgrava pelopersistum ato publicado em 15/03/2024 que chega hoje, roda omake lakee confere no DuckDB que ele entrou emano=2024(inseridas=1), que nenhuma outra partição mudou e que a reexecução não duplica (inseridas=0 atualizadas=0). Roda nomake test.
6. Serialização e compressão justificadas pelo padrão de acesso
- Parquet particionado por ano, não por dia: com os 215 atos da planilha, a partição diária dá 112 arquivos de poucos KB e a anual, 12; a agregação por conselho e tipologia no DuckDB cai de 16,7 ms para 4,9 ms. Partição por órgão foi descartada porque o indicador compara os 82 conselhos de uma vez.
zstd, medido contrasnappycom os dados reais: mesmo tempo de consulta (agregação e busca no texto) e 20% a 29% menos disco. A escolha não é o padrão da ferramenta: odeltalakegravasnappyse nada for dito, e o código fixaWriterProperties(compression="ZSTD"). Script e resultados emdocs/adr/medicoes/0006-camada-analitica/; justificativa no ADR 0006.
7. ADR sobre o mecanismo de ingestão, com a opção nula considerada
- Método de Decisão com 3 alternativas: ADR 0005 compara formalmente a Opção Nula (A, recarga periódica completa agendada), o CDC lógico nativo (B, adotada) e Debezium com Kafka (C).
- Medição reproduzível no repositório: comando
docker compose run --rm lake python docs/adr/medicoes/0005-ingestao/bench.py(não altera o banco) afere os tempos de extração e MERGE no Python para as opções A e B com os dados reais do domínio (base de referência com 311 atos: 215 históricos + 96 de agosto/2026), comparando-os com as estimativas arquiteturais da opção C (Debezium + Kafka).- Containers e RAM permanente: Opção B adiciona 0 containers e 0 MB de RAM permanente (o slot é nativo no PostgreSQL), contra a estimativa de 4 containers e ~2 GB de RAM JVM permanente que uma stack Debezium + Kafka demandaria em regime ocioso.
- Tempo de processamento e parede: em CPU no Python praticamente empatam (92,5 ms vs 65,4 ms; a extração pontual é mais rápida, 1,7 ms vs 25,8 ms, mas o MERGE no Delta Lake domina o tempo), e em tempo de parede no Compose efêmero ambos levam ~7 s de container. O CDC foi escolhido porque o checklist da E2 exige fluxo de mudanças com semântica demonstrada, porque custa zero container a mais, e porque escala em O(Δ) contra O(N) da opção nula (onde hoje N = 311 atos, acumulando ~1.150/ano com HTMLs de até 42 KB).
- Reprocessamento e tolerância a falhas: entrega effectively-once comprovada sem duplicação no Delta Lake (ADR 0007).
- Por que o DOU agora entra no OLTP: o ADR 0001 previa o DOU apenas como bronze/OLAP. O ADR 0005 documenta a evolução e o que não mudou: o OLTP continua sendo a curadoria humana em 3NF (exigindo que
classificacaoaponte para uma chave estrangeira real emato(id)comorigem = DOU|PLANILHA), o espelho integral bruto segue no bronze (data/bronze/), a fila de resolução de aliases de órgãos (ADR 0004) é transacional viva, e o OLTP não espelha indiscriminadamente o DOU (em agosto/2026, 8.494 matérias alheias a conselho geraram 626 nomes novos na filaorgao_aliase apenas as 96 matérias pertinentes foram gravadas emato). - Dados Abertos vs INLABS: a ingestão automática oficial adotou os Dados Abertos mensais públicos (sem login, com histórico desde 2002 e DO1E), contornando a recusa de login de script (502 Bad Gateway) imposta pelo INLABS em 12/10 (para publicações do dia corrente, a operação é baixar o ZIP pelo navegador na interface web e rodar
make persist). - Delta × Iceberg e compressão zstd: critérios explícitos consolidados no ADR 0006.
8. Diário de bordo das semanas 7 a 10
| Semana | Diário |
|---|---|
| 7 | 25/09 e 26/09 |
| 8 | 29/09 |
| 9 | 06/10 |
| 10 | 13/10 |
Processo da squad
- Registro de uso de IA, uma entrada por uso: AI-USAGE.
- Histórico e revisões entre pares: pull requests.
- O que a E2 deixa para a E3: Jornada do dado.