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()numtransform.Readerstreaming. - Decimal com vírgula (
"1000000,00") e datasYYYYMMDDonde0e00000000significam 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 =
COPYquebrando 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 emtext. Umstrings.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
PROPFINDe 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
/healthznos 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=1resolve por ~R$ 30/mês. gcloud sql connectnão funciona se seu ISP te dá IPv6.cloud-sql-proxyautentica 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.