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

Sirvo 55 milhões de páginas em uma VPS de R$ 200/mês: o setup completo

Olá pessoal!

Seguindo a sequência de posts que venho fazendo nos últimos dias. Hoje falo sobre minha infra atual para rodar o cnpjaberto.com.br. No último (e penúltimo) post algumas pessoas vieram me perguntar qual era o custo de infra disso, e a respota é, bastante baixo! Mas isso só acontece devido a milhares de otimizações, truques que aprendi ao longo dos anos e outros que a IA (ela mesmo) acabou ensinando. Desfrutem!


Resumo da operação: 1 servidor, 4 containers Docker, 0 CDN, 0 load balancer, 0 serviço gerenciado. Hetzner CCX23 — 4 vCPU AMD dedicado, 16 GB RAM, 160 GB NVMe, 20 TB de tráfego. €29,74/mês líquido, uns R$ 200 dependendo do dólar. Acabo sempre arredondando para cima.

A máquina roda apenas o projeto cnpjaberto.com.br: um site para consultar o CNPJ de forma gratuita, os dados vem diretamente Receita Federal, hoje, o site possui ~70 milhões de estabelecimentos, ~67 milhões de empresas e ~27 milhões de sócios. 80 GB de dados ativos no Postgres, e cada estabelecimento ativo tem sua própria página com sitemap apontando, daí o "55 milhões de páginas" do título dos posts anteriores que postei aqui.

1. O hardware

Hetzner CCX23 (linha CCX é AMD dedicado, não a CX compartilhada, diferença concreta em paralelismo do Postgres):

  • 4 vCPU AMD dedicado
  • 16 GB RAM
  • 160 GB NVMe local
  • 20 TB de saída/mês (rede 1 Gbps)

Datacenter em Ashburn, US. Latência pro usuário brasileiro fica ~120ms. Isso complica um pouco, mas um servidor em SP ficaria muito mais caro e não quero gastar, ao menos agora.

2. Quatro containers, um host

  • postgres # 16-alpine, 80 GB de dados
  • redis # 7-alpine, 2GB maxmemory, allkeys-lru
  • backend # FastAPI, 4 workers uvicorn
  • frontend # Next.js 15, SSR sob demanda

Algumas pessoas comentaram em trocar o postgres pelo duckdb. Acabei não alterando pois o sistema já funciona bem com postgres, mas parece uma dica muito válida. Outro ponto: hoje mantenho o postgresql pois quero uma solução SAAS. O duckdb me parece uma solução de data lake ou analytics, me corrijam se estiver errado.

3 (gargalo). Postgres 80GB de dados, 16GB de RAM

Os parâmetros que importam do Postgres:

  • shared_buffers 3 GB # buffer interno (~20% da RAM)
  • effective_cache_size 9 GB # estimativa do que SO + PG cacheia
  • work_mem 16 MB # baixo de propósito (sort/hash por op)
  • maintenance_work_mem 1 GB # alto pra CREATE INDEX / VACUUM rápidos
  • random_page_cost 1.1 # NVMe ≈ leitura sequencial
  • max_wal_size 4 GB
  • jit off # planning alto, ganho marginal em OLTP

A parte crítica é shared_buffers + effective_cache_size + (work_mem × max_connections). Com 100 conexões, posso estourar 1.6 GB só em ordenação se o work_mem estiver alto. Por isso 16 MB e não os 64+ MB que tutoriais de internet
recomendam.

  • random_page_cost=1.1 foi a virada de chave pra fazer o planner usar índices em vez de scan sequencial em quase tudo, NVMe não tem latência rotacional (go SSDs!), a heurística default (4.0) está mentindo pra ele.

Os índices que mais carregam o trabalho:

  • B-tree em cnpj em estabelecimentos (lookup direto)
  • GIN com pg_trgm em razão social, fantasia e nome de sócio (busca fuzzy — escrevi um post sobre isso aqui também)
  • B-tree em cnae_principal, municipio, uf (filtros do panorama estatístico)

4. As seis camadas de cache

A maioria do tráfego não chega a tocar o Postgres em runtime. Sim.

CamadaOndeTTL
Lookup TablesRAM do processo Pythonaté restart (para sempre)
Detalhes CNPJRedis1h
Resultados de buscaRedis1h
Panorama (cnpjaberto.com.br/panorama)Snapshot postgres30d
Sitemap XMLunstable_cache30d
Bolsa/FIIRAM do Next7d

O redis usa allkeys-lru, ou seja, uma key mais recente invalida outras keys. Isso ajuda a controlar o espaço/tamanho do Redis.

5. Next.js sem SSG, SSR sob demanda

Já escrevi aqui um post inteiro sobre isso, então só o resumo: gerar 55 milhões de páginas estáticas em build time é inviável (tempo, disco, e o delta mensal da RFB destruiria qualquer estratégia de rebuild). A solução é SSR sob demanda unstable_cache por rota. Primeiro hit em página fria custa caro; segundo hit no mesmo CNPJ vem de cache em milissegundos.

6. Onde o sistema vai bater a parede

Sim, eu sei que isso vai acontecer.

Disco (160 GB): hoje ocupo ~110 GB. A base da RFB cresce ~2-3 GB/mês. Em 18 meses preciso particionar e arquivar histórico, ou subir pra CCX33 (240 GB).

CPU em ingestão: durante a ingestão mensal (2-4h) o Postgres consome os 4 vCPUs. A app fica lenta. Contornável rodando de madrugada, mas é um ponto real de contenção.

Quando bater na parede, o caminho é vertical: CCX33 custa €59/mês (8 vCPU, 32 GB, 240 GB) e provavelmente compra mais 12-18 meses. Sair pra arquitetura distribuída (réplica de leitura, CDN, object storage) só faz sentido depois de outro 10x de tráfego.

Por hoje era isso, obrigado pelo tempo de leitura :)


Menções honrrosas:

Todos que agregaram de alguma forma nos posts anteriores. Em especial ao @Detinho, que recomendou o uso de unstable cache.

Carregando publicação patrocinada...
2

Colirio para os olhos, relato real, sem ser escrito por IA, e sem teorismo de merda.

Obrigado por compartilhar, o fato de você ser pão duro com certeza levou a procurar formas de otimizar, 👏

1

Eu uso uma vps com 16gb ram, 8núclios AMD 16 threads e 512gb de armasenamento.
Pago só 105 reais por mês.
Ainda vem com a possibilidade de vc colocar qualquer iso que quiser, tráfego ilimitado etc...
netcup
Eu recomendaria instalar o zram e o vram, criar um swap pra isso também.

1
1
1
1
1

Muito obrigado! A motivação foi justamente por usabilidade. Passei muito tempo tentando encontrar informações nos sites existentes até decidir fazer o meu, abraços!

0