🏗️ Arquitetura de Streaming e Performance
Esta seção descreve as decisões de engenharia que permitem ao DataProfiler processar arquivos massivos (GBs) mantendo uma pegada de memória mínima (MBs).
A arquitetura resolve o problema clássico de Big Data em Hardware Pequeno.
🎯 1. O Desafio de Engenharia
Em abordagens tradicionais de Ciência de Dados (como Python/Pandas ou R), o padrão é carregar todo o dataset na memória RAM (In-Memory Processing).
Cenário Tradicional
- Arquivo de entrada: CSV de 10 GB
- Infraestrutura: Container serverless (Render, AWS Lambda) com 512 MB de RAM
- Resultado: Processo encerrado com erro
OOMKilledantes da análise começar
Abordagem DataProfiler
Adotamos uma arquitetura de Streaming Pipeline. Em vez de carregar o dataset inteiro, os dados são tratados como um fluxo contínuo:
Lê → Processa → Descarta da memória
🔄 2. Pipeline de Processamento
O fluxo de dados segue o padrão Producer–Consumer, utilizando as primitivas de concorrência do Go (Channels e Goroutines).
graph LR
User[Usuário] -->|Upload HTTP Multipart| Server[Servidor Go]
subgraph "Camada de Ingestão (I/O)"
Server -->|Stream 32MB chunks| Disk[Disco Temporário]
Disk -->|Buffer 1MB| Reader[Leitor Bufio]
Reader -->|Parse CSV| Parser[CSV Parser]
end
subgraph "Camada de Processamento (Concorrência)"
Parser -->|Envia Linha| Chan{Channel Buffer: 1000}
Chan -->|Consome| W1[Worker 1: Inferência]
Chan -->|Consome| W2[Worker 2: Estatística]
Chan -->|Consome| W3[Worker 3: Regex PII]
end
subgraph "Camada de Agregação"
W1 & W2 & W3 -->|Resultados Parciais| Agg[Acumulador]
Agg -->|JSON Final| UI[Frontend React]
end
⚙️ 3. Tuning de Performance
Os “Números Mágicos”
A eficiência do sistema depende do ajuste preciso de buffers e limites. Abaixo estão as principais decisões técnicas.
📀 3.1 Otimização de I/O de Disco (bufio)
Ler dados do disco é uma operação lenta. A leitura byte a byte gera milhões de syscalls, degradando a performance.
bufio.NewReaderSize(file, 1024*1024)
- Buffer de 1 MB
- Reduz drasticamente o número de acessos ao disco
- Aumenta o throughput de leitura
📤 3.2 Limite de Upload (Multipart Form)
Uploads grandes podem esgotar a memória do servidor se não houver controle.
🚦 3.3 Backpressure (Channel Buffering)
A leitura de disco é mais rápida que o processamento em CPU. Sem controle, isso pode gerar acúmulo excessivo de dados na memória.
jobs := make(chan []string, 1000)
- Channel com buffer de 1000 linhas
- Quando o buffer enche:
- O leitor de disco é automaticamente bloqueado
- Cria backpressure natural, equilibrando I/O e CPU
Resultado
O sistema se auto-regula conforme a velocidade do processamento, garantindo estabilidade e previsibilidade.
🧵 4. Concorrência: Worker Pool
As goroutines do Go são extremamente leves (~2 KB), muito menores que threads do sistema operacional (~1 MB).
Estratégia
- Um número fixo de workers é iniciado (baseado no número de CPUs)
- Todos consomem linhas do mesmo channel
- Se um worker ficar lento (ex.: regex pesada), os outros continuam processando Benefício: ✔️ Melhor uso da CPU ✔️ Paralelismo real ✔️ Alta escalabilidade com baixo consumo de memória
📦 5. Distribuição: Binário Único (Embed)
Para simplificar o deploy e eliminar dependências externas (Nginx/Apache), utilizamos o embed do Go (v1.16+).
Durante o go build, os arquivos estáticos do frontend (React) são embutidos diretamente no binário.
//go:embed frontend/dist/*
var frontendFS embed.FS
- Um único arquivo executável
- Contém:
- API
- Pipeline de processamento
- Interface Web
- Deploy simples, portátil e previsível
Resumo Final
O DataProfiler combina streaming, concorrência eficiente e deploy simplificado para processar Big Data em ambientes com recursos extremamente limitados.