DEV Community

Como servir os 68 milhões de CNPJs da Receita com ~10ms de latência em Go

Todo dev brasileiro que já precisou consultar CNPJ conhece o dilema: ou você usa uma API que faz proxy da Receita (3 a 10 segundos por consulta, quando não cai), ou baixa o dump de dados abertos e monta a própria base - e descobre que "baixar um CSV" era a parte fácil.

Eu montei a própria base. Este post é o diário honesto do que funcionou, do que quebrou e dos números reais - 217 milhões de linhas servidas em ~10ms de p50 dentro do datacenter, num Postgres de 1 vCPU.

A arquitetura em uma frase

Não consulte a Receita em tempo real. Ingira o dump mensal e sirva da sua infra. O resto é decorrência.

Receita (dump mensal, ~6GB zip) ──▶ ingestão Go (COPY) ──▶ Postgres ──▶ API (chi)
CGU (CEIS/CNEP, zip diário) ──▶ job diário ──┘

O dump da Receita: as pegadinhas que ninguém documenta

O layout oficial existe, mas o que quebra parser de verdade é o que está fora dele:

  • Encoding latin1 (ISO-8859-1) - acento vira lixo se você ler como UTF-8. Em Go: charmap.ISO8859_1.NewDecoder() num transform.Reader streaming.
  • Decimal com vírgula ("1000000,00") e datas YYYYMMDD onde 0 e 00000000 significam nulo.
  • CNPJ quebrado em 3 colunas (básico 8 + ordem 4 + DV 2). A chave de junção entre empresas, estabelecimentos e sócios é o básico - errar isso custa um dia.
  • As partições 0–9 não se alinham entre arquivos. O estabelecimento da partição 3 pode ser de uma empresa da partição 7. Foreign key rígida entre as tabelas = COPY quebrando no meio da carga. A solução: sem FK; a integridade vem da fonte.
  • Bytes NUL (0x00) no meio dos dados. O Postgres rejeita NUL em text. Um strings.ReplaceAll(s, "\x00", "") no parser economizou três recargas.
  • Desde jan/2026 o repositório é um Nextcloud do SERPRO+ com WebDAV público - dá pra listar meses com PROPFIND e baixar com o token do share como usuário. Adeus, scraping.

COPY ou morte

A diferença entre INSERT em lote e o protocolo COPY não é incremental - é outra categoria. Com pgx.CopyFrom e lotes de 50k:

  • 28,1 milhões de empresas em 1m28s (~320k linhas/s) num MacBook
  • A base completa (68,6M empresas + 71,9M estabelecimentos + 27,8M sócios + 49M de registros do Simples) entra em cerca de uma hora numa VM modesta

O truque é não materializar nada: latin1→UTF-8→parse→COPY, tudo streaming, memória constante.

O índice que paga o aluguel

A feature mais pedida por quem faz compliance é a busca reversa: de um nome de pessoa para todas as empresas em que ela é sócia. Com 27,8 milhões de sócios:

CREATE INDEX idx_socios_nome_trgm ON socios USING gin (nome_socio gin_trgm_ops);

Lição aprendida: ORDER BY similarity(...) avalia todos os matches antes do LIMIT. Com threshold 0.4 num nome comum ("JOSE..."), timeout; com 0.6, ~1s. O operador % respeita o set_limit() da sessão - alinhe o threshold do índice com o do filtro ou o plano de query mente pra você.

Cruzar sanções com o QSA (o pulo do gato)

O CEIS/CNEP (empresas e pessoas punidas, CGU) publica CPF completo. O QSA da Receita publica CPF mascarado: ***XXXXXX** - os 6 dígitos do meio.

Então o match sócio-sancionado é:

substring(cpf_mascarado, 4, 6) = substring(cpf_completo, 4, 6)
+ similaridade trigram de nome ≥ 0.5

Não é prova - é sinal forte, e em compliance sinal forte com explicação vale mais que caixa-preta. Numa base real: empresa limpa, sócio proibido de contratar com o poder público até 2030. É esse cruzamento que consulta nenhuma te dá de graça.

Números de produção (Cloud Run + Cloud SQL 1 vCPU)

Operação Latência
Ficha completa (3 queries + JOINs de tradução) ~8ms no datacenter, ~134ms na minha casa
Checagem de sanções (empresa + sócios) 14–34ms
Score com sinais ~96ms
Busca reversa em 27,8M de nomes ~1s

Custo total da infra: ~R$ 300/mês. O concorrente que faz proxy da Receita responde em 3.000–10.000ms e cai junto com ela.

Coisas que quebraram e viraram conhecimento

  • O Google Frontend reserva /healthz nos domínios *.run.app - o request nem chega no container. Use /health.
  • Cloud Run com scale-to-zero: primeiro request paga ~6s de cold start (pool do Postgres). Aceitável para o meu caso; min-instances=1 resolve por ~R$ 30/mês.
  • gcloud sql connect não funciona se seu ISP te dá IPv6. cloud-sql-proxy autentica via IAM e ignora o problema.
  • Payment Links do Stripe suportam assinatura de R$ 0 sem cartão (payment_method_collection: if_required) - free tier com identidade real, zero código de checkout.

O resultado

A API está no ar em fides.api.br - tem playground sem cadastro na landing e um free tier de 50 consultas/mês que pede só email.

Se você só precisa de razão social e endereço, a BrasilAPI é grátis e resolve; eu mantenho uma comparação honesta que inclui quando não usar o meu produto.

O código do parser de latin1 e a estratégia de COPY descritos aqui são genéricos - servem pra qualquer dataset dos dados abertos brasileiros.

Dúvidas sobre o pipeline? Comenta que eu respondo - a parte de infra rendeu mais história do que coube aqui.

Comments

No comments yet. Start the discussion.