Executando verificação de segurança...
2

Importei 55 milhões de empresas para PostgreSQL em menos de 3 horas, e como a IA não ajudou nisso

Construí o CNPJ Aberto em cima dos CSVs da Receita (latin-1, ;, vários ZIPs). O desafio foi repetir a carga todo mês sem passar o dia inteiro esperando.

Resumo do que aprendi: ORM e executemany não são ferramenta de bulk load. O caminho foi COPY, temp table, dropar índice na carga e CREATE INDEX CONCURRENTLY depois.

Resumo mais importante: A IA é limitada neste ponto, ela otimiza um pouco, mas depois é com você. A pesquisa ainda se faz necessária para chegar nas 3 horas. Não cheguei a fazer benchmark disso, mas estivo que, depois de conversar muito com IA e todos modelos, cheguei ao máximo de ~4hrs.

Números rápidos

TabelaRegistros (ordem de grandeza)CSV
empresas~55M~4 GB
estabelecimentos~70M~15 GB
sócios~25M~3 GB
simples~35M~2 GB

Uns ~25 GB descompactados.

O que não funcionou

SQLAlchemy em loop — milhares de inserts por segundo, horas de fila.

executemany em batch — melhor, mas ainda um INSERT por linha (parse/plan por statement). Continuava na casa das horas.

O que funcionou: COPY

O COPY do Postgres escreve no heap sem passar pelo mesmo caminho de um INSERT normal. Na prática foi ordens de magnitude mais rápido.

Sessão mais “agressiva” só para carga (se cair, reimporta):

SET synchronous_commit = off;
SET work_mem = '256MB';

Índices: derrubar os que não forem PK, importar, recriar com CONCURRENTLY para não travar leitura do site.

Upsert mensal: COPY não faz ON CONFLICT. Fluxo que usei: CREATE TEMP TABLE tmp (...) LIKE real INCLUDING DEFAULTSCOPY na temp → INSERT ... ON CONFLICT DO UPDATE da temp para a tabela final → dropar temp.

Paralelismo: ZIPs independentes → ThreadPoolExecutor com poucos workers importando tabelas diferentes em paralelo; SSD aguenta bem.

Comparativo (ordem de grandeza)

AbordagemThroughputTempo total (empresa + resto)
ORM~1,2k/s12+ h (só empresas já dava nisso)
executemany~4k/s4+ h
COPY + temp + índice depois + paralelo~80k/s< 3 h

Lições que eu levaria para outro projeto, ou que você deve levar ao seu projeto

  1. COPY é o default mental para carga massiva no Postgres.
  2. Índice na carga quase sempre piora tempo total; recriar depois ganha.
  3. Temp table liga COPY a merge/upsert sem gambiarra no parser.
  4. synchronous_commit = off em job de importação controlado é tradeoff aceitável.
  5. Tamanho de batch (~200k linhas por transação COPY) equilibra memória vs overhead.

O que acabou salvando bastante foi o uso de trigramas na base. Vou organizar um post sobre isso na sequência.

Carregando publicação patrocinada...