<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Messias Freitas</title>
    <description>The latest articles on DEV Community by Messias Freitas (@tekhnoai).</description>
    <link>https://dev.to/tekhnoai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4017779%2Fcb46917e-13d9-4494-b10d-9975232496a0.png</url>
      <title>DEV Community: Messias Freitas</title>
      <link>https://dev.to/tekhnoai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tekhnoai"/>
    <language>en</language>
    <item>
      <title>Como servir os 68 milhões de CNPJs da Receita com ~10ms de latência em Go</title>
      <dc:creator>Messias Freitas</dc:creator>
      <pubDate>Mon, 06 Jul 2026 12:35:31 +0000</pubDate>
      <link>https://dev.to/tekhnoai/como-servir-os-68-milhoes-de-cnpjs-da-receita-com-10ms-de-latencia-em-go-284p</link>
      <guid>https://dev.to/tekhnoai/como-servir-os-68-milhoes-de-cnpjs-da-receita-com-10ms-de-latencia-em-go-284p</guid>
      <description>&lt;p&gt;Todo dev brasileiro que já precisou consultar CNPJ conhece o dilema: ou você&lt;br&gt;
usa uma API que faz proxy da Receita (3 a 10 segundos por consulta, quando não&lt;br&gt;
cai), ou baixa o dump de dados abertos e monta a própria base — e descobre que&lt;br&gt;
"baixar um CSV" era a parte fácil.&lt;/p&gt;

&lt;p&gt;Eu montei a própria base. Este post é o diário honesto do que funcionou, do&lt;br&gt;
que quebrou e dos números reais — 217 milhões de linhas servidas em ~10ms de&lt;br&gt;
p50 dentro do datacenter, num Postgres de 1 vCPU.&lt;/p&gt;
&lt;h2&gt;
  
  
  A arquitetura em uma frase
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Não consulte a Receita em tempo real. Ingira o dump mensal e sirva da sua&lt;br&gt;
infra.&lt;/strong&gt; O resto é decorrência.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receita (dump mensal, ~6GB zip) ──▶ ingestão Go (COPY) ──▶ Postgres ──▶ API (chi)
CGU (CEIS/CNEP, zip diário)     ──▶ job diário          ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  O dump da Receita: as pegadinhas que ninguém documenta
&lt;/h2&gt;

&lt;p&gt;O layout oficial existe, mas o que quebra parser de verdade é o que está fora dele:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Encoding latin1 (ISO-8859-1)&lt;/strong&gt; — acento vira lixo se você ler como UTF-8.
Em Go: &lt;code&gt;charmap.ISO8859_1.NewDecoder()&lt;/code&gt; num &lt;code&gt;transform.Reader&lt;/code&gt; streaming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decimal com vírgula&lt;/strong&gt; (&lt;code&gt;"1000000,00"&lt;/code&gt;) e &lt;strong&gt;datas &lt;code&gt;YYYYMMDD&lt;/code&gt;&lt;/strong&gt; onde &lt;code&gt;0&lt;/code&gt; e
&lt;code&gt;00000000&lt;/code&gt; significam nulo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNPJ quebrado em 3 colunas&lt;/strong&gt; (básico 8 + ordem 4 + DV 2). A chave de
junção entre empresas, estabelecimentos e sócios é o &lt;strong&gt;básico&lt;/strong&gt; — errar isso
custa um dia.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As partições 0–9 não se alinham entre arquivos.&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bytes NUL (&lt;code&gt;0x00&lt;/code&gt;) no meio dos dados.&lt;/strong&gt; O Postgres rejeita NUL em &lt;code&gt;text&lt;/code&gt;.
Um &lt;code&gt;strings.ReplaceAll(s, "\x00", "")&lt;/code&gt; no parser economizou três recargas.&lt;/li&gt;
&lt;li&gt;Desde jan/2026 o repositório é um Nextcloud do SERPRO+ com &lt;strong&gt;WebDAV
público&lt;/strong&gt; — dá pra listar meses com &lt;code&gt;PROPFIND&lt;/code&gt; e baixar com o token do
share como usuário. Adeus, scraping.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  COPY ou morte
&lt;/h2&gt;

&lt;p&gt;A diferença entre &lt;code&gt;INSERT&lt;/code&gt; em lote e o protocolo &lt;code&gt;COPY&lt;/code&gt; não é incremental — é&lt;br&gt;
outra categoria. Com &lt;code&gt;pgx.CopyFrom&lt;/code&gt; e lotes de 50k:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;28,1 milhões de empresas em 1m28s&lt;/strong&gt; (~320k linhas/s) num MacBook&lt;/li&gt;
&lt;li&gt;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&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O truque é não materializar nada: latin1→UTF-8→parse→COPY, tudo streaming,&lt;br&gt;
memória constante.&lt;/p&gt;
&lt;h2&gt;
  
  
  O índice que paga o aluguel
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_socios_nome_trgm&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;socios&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nome_socio&lt;/span&gt; &lt;span class="n"&gt;gin_trgm_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Cruzar sanções com o QSA (o pulo do gato)
&lt;/h2&gt;

&lt;p&gt;O CEIS/CNEP (empresas e pessoas punidas, CGU) publica &lt;strong&gt;CPF completo&lt;/strong&gt;. O QSA&lt;br&gt;
da Receita publica CPF &lt;strong&gt;mascarado&lt;/strong&gt;: &lt;code&gt;***XXXXXX**&lt;/code&gt; — os 6 dígitos do meio.&lt;/p&gt;

&lt;p&gt;Então o match sócio-sancionado é: &lt;code&gt;substring(cpf_mascarado, 4, 6) =&lt;br&gt;
substring(cpf_completo, 4, 6)&lt;/code&gt; + similaridade trigram de nome ≥ 0.5. Não é&lt;br&gt;
prova — é sinal forte, e em compliance sinal forte com explicação vale mais que&lt;br&gt;
caixa-preta. Numa base real: empresa limpa, sócio proibido de contratar com o&lt;br&gt;
poder público até 2030. É esse cruzamento que consulta nenhuma te dá de graça.&lt;/p&gt;

&lt;h2&gt;
  
  
  Números de produção (Cloud Run + Cloud SQL 1 vCPU)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operação&lt;/th&gt;
&lt;th&gt;Latência&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ficha completa (3 queries + JOINs de tradução)&lt;/td&gt;
&lt;td&gt;~8ms no datacenter, ~134ms na minha casa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checagem de sanções (empresa + sócios)&lt;/td&gt;
&lt;td&gt;14–34ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Score com sinais&lt;/td&gt;
&lt;td&gt;~96ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Busca reversa em 27,8M de nomes&lt;/td&gt;
&lt;td&gt;~1s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;h2&gt;
  
  
  Coisas que quebraram e viraram conhecimento
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  O resultado
&lt;/h2&gt;

&lt;p&gt;A API está no ar em &lt;a href="https://fides.api.br" rel="noopener noreferrer"&gt;fides.api.br&lt;/a&gt; — tem playground sem&lt;br&gt;
cadastro na landing e um free tier de 50 consultas/mês que pede só email. Se&lt;br&gt;
você só precisa de razão social e endereço, a BrasilAPI é grátis e resolve;&lt;br&gt;
eu mantenho uma &lt;a href="https://fides.api.br/comparativo" rel="noopener noreferrer"&gt;comparação honesta&lt;/a&gt; que&lt;br&gt;
inclui quando &lt;em&gt;não&lt;/em&gt; usar o meu produto.&lt;/p&gt;

&lt;p&gt;O código do parser de latin1 e a estratégia de COPY descritos aqui são&lt;br&gt;
genéricos — servem pra qualquer dataset dos dados abertos brasileiros.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dúvidas sobre o pipeline? Comenta que eu respondo — a parte de infra rendeu&lt;br&gt;
mais história do que coube aqui.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>go</category>
      <category>performance</category>
      <category>postgres</category>
    </item>
  </channel>
</rss>
