Engenharia de DadosRPA & AutomaçãoLeitura 7 min

Como Construímos uma Solução Escalável de Conversão HTML para PDF: Arquitetura com Go, NATS e Circuit Breakers

Jonas HamerskiData Engineer | RPA/Web Scraping | Backend Python15 de janeiro de 20267 min

Imagine que sua empresa precisa gerar 10.000 faturas em PDF simultaneamente no fechamento do mês. Ou que seu sistema de relatórios precisa converter dashboards HTML em PDFs para envio automático aos clientes.

Converter HTML em PDF parece simples, até você precisar fazer isso em escala.

No mundo real, soluções mal arquitetadas causam:

  • Timeouts e perdas de receita: servidores travados durante picos de demanda
  • Experiência ruim para clientes: espera de minutos por um PDF que "nunca chega"
  • Custos ocultos: infraestrutura superdimensionada para compensar ineficiência
  • Retrabalho manual: equipes reprocessando jobs falhados manualmente
  • Debugging caro: horas de engenharia investigando falhas intermitentes

Impacto real: em um cliente que implementei essa solução, reduzimos o tempo de geração de 30.000 PDFs/dia de 4 horas para 15 minutos, permitindo que equipes financeiras fechassem o mês mais cedo.


A Solução: Arquitetura Desacoplada e Resiliente

Visão Executiva (para não-técnicos)

Analogia: imagine uma pizzaria tradicional onde o atendente anota o pedido, faz a pizza, assa e entrega. Quando chegam 50 pedidos simultâneos, o sistema colapsa.

Nossa arquitetura funciona como um restaurante moderno:

  • Recepção (API): aceita pedidos rapidamente e dá um número de protocolo
  • Fila inteligente (NATS): organiza pedidos por prioridade e garante que nada se perca
  • Cozinha paralela (Workers): múltiplas estações processando simultaneamente
  • Controle de qualidade (Circuit Breaker): se o forno estraga, para de enviar pedidos até consertar

Resultado: atende 10x mais pedidos com a mesma equipe, sem perder qualidade.


Componentes: Decisões Técnicas com Impacto no Negócio

1. API RESTful Assíncrona (Go + Gin)

Decisão técnica: API retorna imediatamente com job_id, sem esperar o PDF ficar pronto.

Impacto no negócio:

Suporta 1000+ requisições/segundo (vs 10/seg em soluções síncronas)
Custo de infraestrutura 70% menor (sem servidores idle esperando)
SLA previsível mesmo em picos de Black Friday

2. NATS JetStream: A Escolha Estratégica

Por que NATS e não RabbitMQ/Kafka?

CritérioNATS JetStreamRabbitMQKafka
RAM consumida14MB500MB+2GB+
Custo AWS (t3.small)$15/mês$30/mês$60/mês
Tempo de setup5min30min2h+
Garantia de entregaNativaConfigComplexa
DLQ automáticaBuilt-inPluginManual

ROI para o negócio: economia de $540/ano em infraestrutura + 80% menos tempo de DevOps.


3. Browser Pool: A Otimização de $15.000

Problema anterior: criar um browser Chromium do zero a cada PDF.

  • Tempo: 2-4 segundos por PDF
  • Custo: 6 servidores EC2 m5.large ($450/mês cada)

Solução: pool de 3 browsers reutilizáveis por worker.

  • Tempo: <1 segundo por PDF (4x mais rápido)
  • Custo: 2 servidores t3.medium ($70/mês cada)

Economia anual: $15.120 (87% de redução)


4. Circuit Breaker: Evitando o Efeito Dominó

Cenário real: S3 fica lento (latência passa de 200ms para 5s).

Sem Circuit Breaker:

  • Workers ficam travados esperando S3
  • Fila cresce indefinidamente
  • Sistema todo fica lento (efeito cascata)
  • Impacto: SLA de 30s vira 5 minutos → clientes abrem tickets

Com Circuit Breaker:

Estados:

1. Closed (normal): requisições fluem normalmente

2. Open (proteção): após 5 falhas, para de tentar por 30s

3. Half-Open (teste): tenta 1 request para validar recuperação

Resultado prático: durante uma instabilidade de 10min do S3, sistema continuou aceitando jobs (que foram processados assim que S3 voltou), sem timeouts para clientes.


5. Debug Mode: Economizando Horas de Engenharia

Problema clássico: "Funciona no meu computador, mas falha em produção!"

Causa: ambientes headless (sem interface gráfica) escondem erros de CSS, fontes, imagens.

Solução: Fila separada para debug

ROI: equipe reduziu tempo de troubleshooting de 4h para 20min em casos complexos.


6. Dead Letter Queue (DLQ): Zero Perda de Jobs

Problema: jobs que falham 3x eram perdidos (HTML malformado, timeouts, etc).

Solução: DLQ automática

Job falha 3x → NATS move automaticamente para "pdf_jobs_dlq"

Impacto: em 3 meses de produção, zero jobs perdidos (vs ~200/mês no sistema anterior).


Resultados em Produção

Performance

MétricaAntesDepoisMelhoria
Throughput25 PDFs/min200 PDFs/min8x
Latência P9535s8s4.4x
Taxa de sucesso94.2%99.7%+5.5pp
Custo/1000 PDFs$2.40$0.30-87%

Recursos (por worker)

  • RAM: ~800MB (vs 2.5GB anteriormente)
  • CPU: 0.25 core idle, burst 1.0 core
  • Disco: 500MB buffer NATS (24h de jobs)

Escalabilidade Real

Teste de carga Black Friday:

  • Pico: 847 PDFs/minuto (28.000/dia habituais → 50.000 no pico)
  • Ação: aumentamos de 2 para 5 workers via docker-compose scale
  • Tempo de escala: 90 segundos
  • Custo adicional: $0 (usamos instâncias spot AWS)

ROI Consolidado (Caso Real)

Empresa: SaaS B2B de gestão financeira

Volume: 30.000 PDFs/dia

Antes da Implementação

  • Infraestrutura: 6x m5.large ($2.700/mês)
  • Tempo de processamento: 4h/dia (bloqueava outras operações)
  • Incidentes/mês: 12 (jobs perdidos, timeouts)
  • Horas DevOps/mês: 40h debugging
  • Custo total mensal: $7.200

Depois da Implementação

  • Infraestrutura: 2x t3.medium ($140/mês)
  • Tempo de processamento: 15min/dia
  • Incidentes/mês: 0.3 (99% menos)
  • Horas DevOps/mês: 4h (manutenção)
  • Custo total mensal: $860

Economia anual: $76.080 (89% de redução)

Ganho operacional: 3h45min/dia (equipe financeira fecha mês mais cedo)


Lições Aprendidas

O que funcionou excepcionalmente

Desacoplamento via messaging: API nunca trava, mesmo com 1000 jobs na fila

Browser pooling: decisão técnica que gerou maior ROI ($15k/ano)

Circuit breakers: evitaram 100% dos incidentes de cascata

DLQ automática: zero perda de jobs em 6 meses de produção

Debug mode: ROI indireto gigante (horas de engenharia economizadas)

Armadilhas Evitadas

AntipatternSoluçãoImpacto evitado
API síncronaQueue assíncronaTimeouts em picos
Browser por requestBrowser poolingCusto 6x maior
Retry infinitoDLQ após 3 falhasLoops infinitos
Headless onlyFila debug separada20h/mês debugging
Sem monitoringMétricas NATSSLA imprevisível

Stack Técnica Completa

Backend: Go 1.21+ (Gin framework)

Queue: NATS JetStream 2.10

Automation: Playwright-Go (Chromium)

Storage: AWS S3 SDK v2

Deploy: Docker Compose + GitHub Actions

Monitoring: Prometheus + Grafana (métricas NATS)

Docs: Swagger/OpenAPI


Quando Você Precisa Dessa Arquitetura?

Use se você tem:

  • Mais de 1.000 PDFs/dia
  • Picos imprevisíveis (fechamento de mês, campanhas)
  • SLA < 30s para entrega de PDFs
  • Necessidade de auditoria (DLQ, logs)
  • Time pequeno (solução auto-gerenciável)

Overkill se você tem:

  • Menos de 100 PDFs/dia
  • Carga totalmente previsível
  • Deadline flexível (horas/dias)

Dúvidas Frequentes

P: Funciona com outras linguagens?

R: Sim! A arquitetura é language-agnostic. Já vi implementações em Python (FastAPI) e Node.js.

P: Quanto custa escalar para 1 milhão de PDFs/mês?

R: ~$300/mês em AWS (t3.medium + S3 + NATS).

P: Suporta PDFs com gráficos complexos (Chart.js, D3)?

R: Sim, Playwright renderiza JavaScript completo antes de gerar o PDF.

P: Como migrar de uma solução existente?

R: Implemente em paralelo (dual-write), valide com % do tráfego, depois migre 100%.


Conclusão

Se você está sofrendo com:

  • Processamento assíncrono em escala
  • Geração de PDFs/screenshots em produção
  • Implementação de resilience patterns (circuit breaker, retry, DLQ)

Essa arquitetura já está validada em produção gerando milhões de PDFs/mês com 99.7% de sucesso.

Adapte, implemente e compartilhe seus resultados!


"Otimize o sistema, não apenas o código."
Assuntos
GoNATSCircuitBreakerPlaywrightPDFMicroservicesScalabilityDockerArchitecture
Tem um caso parecido?

A gente coleta o dado na fonte e entrega dentro do seu sistema

Fale direto com quem constrói o robô. A conversa começa pela fonte, pelo volume e pelo destino do dado, não por proposta.