Skip to content

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.

Postgres 16 · OLTPfetch-mensalZIP do mêspersistsó conselhosmake lakerelê tudodelta_scanlê o DeltaDados abertosImprensa Nacional1 ZIP/mês, sem login8.590 matérias · ago/26Bronzedata/bronze/ZIP como veio52,6 MB, byte a byteDelta Lakedata/silver/atos/Parquet · ano= · zstdMERGE por ato_id311 linhas · −29% discoDuckDBconsultaanalítica~0,85 msato96 atos de conselho · versõesclassificacaoinsert-only · humano venceslot lógicotest_decoding · 512 B por mudançaWAL (wal_level=logical)Curadoriareclassifica um atoINSERT, nunca UPDATEmake cdcsó o que mudoulote: a fonte pública chega ao OLTP e ao Deltamudanças: o que a curadoria altera chega ao Delta (CDC)clique numa caixa para os detalhes

A mesma figura, estática, para imprimir ou colar numa apresentação: arquitetura-e2.svg.

Entregáveis ​

O queOnde estáComo conferir
Ingestão mensal da fonte pública, idempotentemake 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ãosPostgres 16 · sql/ · ato, classificacao, orgao_aliasmake 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 Kafkamake cdc · src/lake/cdc.py · wal_level=logicalmake cdc-demo: reclassifica, chega ao Delta, restaura
Semântica de entrega demonstradamake cdc-chaos · scripts/cdc_chaos_demo.shqueda forçada (código 42) → reexecução sem duplicar
Dado atrasado e janelamake ingest (janela de 7 dias) · tests/lake/test_atrasado.shato de 2024 chegado hoje cai em ano=2024
ADRs da E20005 ingestão · 0006 Delta/zstd · 0007 CDCmedições em docs/adr/medicoes/
Diários das semanas 7 a 10docs/diario/Processo
Migração do ingestor Go → Python (#51–#55)src/dou/, provada byte a byte e linha a linha contra o Goplano · 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

bash
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 duplica

Pré-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-MM baixa a Seção 1 do mês para data/bronze/dados-abertos/ e grava as matérias de conselho em ato. 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çãogravadasinalteradasórgão desconhecidofalhou
    1ª9608.4940
    2ª0968.4940

    Depois da 2ª execução, ato, orgao_alias e classificacao saíram idênticos linha a linha. Conferir: diário da semana 10 e PR #43.

  • Testado: TestPersistTwiceLeavesTheDatabaseIdentical grava a mesma edição duas vezes e compara o banco linha a linha, com id e criado_em; roda no make test e 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, plugin test_decoding) guarda as mudanças de classificacao, e make cdc as aplica na tabela Delta. O slot só diz quais atos mudaram; o consumidor relê esses atos do OLTP com a mesma consulta do make lake e grava por MERGE em ato_id. Conferir: Fluxo de mudanças do OLTP ao Delta e ADR 0007.
  • O CDC viu uma mudança de verdade: make cdc-demo reclassifica um ato (um INSERT novo, porque classificacao é insert-only), consome o slot, mostra a linha nova no Delta e depois restaura a tipologia original pelo mesmo caminho. Testado por tests/lake/test_cdc.sh, que roda no make test.
  • Cobertura declarada: o fluxo cobre INSERT em classificacao; ato novo do DOU, versão nova e troca de nome de órgão continuam entrando pelo make 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 por MERGE em ato_id a partir do OLTP com a biblioteca deltalake (Rust/Python, sem JVM). Lida pelo DuckDB com delta_scan. Conferir: ADR 0006.
  • Idempotente: make lake duas vezes com os 311 atos (215 da planilha + 96 do DOU de agosto): 1ª inseridas=311, 2ª inseridas=0 atualizadas=0 inalteradas=311. Testado por tests/lake/test_lake.sh, que compara a contagem do DuckDB com o count(*) do banco.
  • Delta × Iceberg com critério explícito (catálogo, MERGE em 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 por MERGE atô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); o MERGE idempotente 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 ao make test e 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-chaos roda o roteiro reproduzível scripts/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 ingest busca e grava de novo os últimos JANELA dias (padrão 7, provisório), do mais antigo para o mais novo; make janela lista 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, por tests/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.sh grava pelo persist um ato publicado em 15/03/2024 que chega hoje, roda o make lake e confere no DuckDB que ele entrou em ano=2024 (inseridas=1), que nenhuma outra partição mudou e que a reexecução não duplica (inseridas=0 atualizadas=0). Roda no make 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 contra snappy com 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: o deltalake grava snappy se nada for dito, e o código fixa WriterProperties(compression="ZSTD"). Script e resultados em docs/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 classificacao aponte para uma chave estrangeira real em ato(id) com origem = 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 fila orgao_alias e apenas as 96 matérias pertinentes foram gravadas em ato).
  • 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 ​

SemanaDiário
725/09 e 26/09
829/09
906/10
1013/10

Processo da squad ​

Projeto Integrado de Banco de Dados 2 — FCTE/UnB, 2026/2.