Muito feliz em compartilhar mais melhorias implementadas no Quick Framework, desenvolvido por @jeffotoni. Essas atualizações focam em robustez, performance e suporte a protocolos modernos.
Madrugadas melhorando o Quick
Como estamos usando o Quick para projeto de comunicação com IA(Inteligência Artificial) apareceu diversas necessidades que levaram a novas implementações e melhorias no Quick. Então vou apresentar algumas melhorias que foram feitas essa semana.
Nada melhor do que colocar um framework à prova na prática e isso é fascinante! Estou utilizando em projetos de IA, desenvolvendo servidores nativos para orquestrar fluxos com LLMs, criando conectores personalizados e construindo soluções RAG continuamente. Tem sido uma experiência desafiadora e ao mesmo tempo muito empolgante.
Reflexões de um desenvolvedor curioso
O caminho é árduo, longo, mas incrivelmente prazeroso. Sou movido pela curiosidade e, às vezes, não me contenho, preciso estudar de novo, revisitar algo que estava ali ao meu lado o tempo todo, mas que só agora faz sentido, entende?. Não era o momento antes, talvez mas estava ali tão perto e ao mesmo tempo tão distante. E essa descoberta tardia me intriga ainda mais.
Apesar dos anos de prática e desenvolvimento, frequentemente me sinto como se estivesse vendo o universo da tecnologia por uma fresta como quem espreita o “todo” pela fechadura de uma porta. A realidade parece fragmentada acessamos apenas pedaços, flashes, nunca o todo completo e isso é intrigante. E talvez nunca o vejamos de fato.
Foi por isso que resolvi criar estes posts não somente para mostrar na prática o que foi feito para framework Quick mas algumas reflexões sinceras de um desenvolvedor em constante aprendizado.
O que foi implementado? 😁
1. Proteção contra WriteHeader Duplicado
Implementamos um wrapper customizado (responseWriter) que previne erros de "superfluous response.WriteHeader". Agora, múltiplas chamadas a WriteHeader são tratadas silenciosamente, evitando crashes em chains complexas de middleware.
2. Suporte Hijacker
O responseWriter agora implementa a interface http.Hijacker, permitindo upgrades de conexão de forma nativa. Isso habilita comunicação bidirecional em tempo real diretamente através do Quick.
3. HTTP/2 Server Push
Adicionamos suporte à interface http.Pusher com método c.Pusher(), habilitando HTTP/2 Server Push para melhorar performance ao enviar recursos proativamente ao cliente antes mesmo deles serem requisitados, reduzindo latência e round-trips.
Exemplo:
q.Get("/", func(c *quick.Ctx) error {
pusher, ok := c.Pusher()
if ok {
pusher.Push("/static/style.css", nil)
pusher.Push("/static/app.js", nil)
}
return c.Status(200).SendString("<html>...</html>")
})
4. Server-Sent Events(SSE) Simplificado para Streaming de LLMs
Implementamos o método c.Flusher() que facilita streaming de dados em tempo real, essencial para aplicações modernas com LLMs (Large Language Models). Permite enviar tokens progressivamente conforme são gerados, criando experiências interativas tanto no browser quanto em CLIs.
Casos de uso reais:
Streaming de respostas de ChatGPT, Claude, Gemini
Logs em tempo real de deployments
Atualizações progressivas de dashboards
Notificações push do servidor
Exemplo1:
q.Post("/ai/chat", func(c *quick.Ctx) error {
c.Set("Content-Type", "text/event-stream")
c.Set("Cache-Control", "no-cache")
c.Set("Connection", "keep-alive")
flusher, ok := c.Flusher()
if !ok {
return c.Status(500).SendString("Streaming not supported")
}
// Simula streaming de tokens de uma LLM
tokens := []string{"Hello", " this", " is", " a", " streaming", " response", " from", " AI"}
for _, token := range tokens {
// Formato SSE padrão
fmt.Fprintf(c.Response, "data: %s\n\n", token)
flusher.Flush() // Envia imediatamente ao cliente
time.Sleep(100 * time.Millisecond) // Simula latência da LLM
}
// Sinaliza fim do stream
fmt.Fprintf(c.Response, "data: [DONE]\n\n")
flusher.Flush()
return nil
})
Exemplo2:
q.Get("/events", func(c *quick.Ctx) error {
c.Set("Content-Type", "text/event-stream")
c.Set("Cache-Control", "no-cache")
flusher, ok := c.Flusher()
if !ok {
return c.Status(500).SendString("Streaming not supported")
}
for i := 0; i < 10; i++ {
fmt.Fprintf(c.Response, "data: Message %d\n\n", i)
flusher.Flush()
time.Sleep(time.Second)
}
return nil
})
Cliente JavaScript (Browser):
const eventSource = new EventSource('/ai/chat');
eventSource.onmessage = (event) => {
if (event.data === '[DONE]') {
eventSource.close();
return;
}
document.getElementById('response').innerText += event.data;
};
Cliente CLI (Go):
resp, _ := http.Post("http://localhost:8080/ai/chat", "application/json", body)
reader := bufio.NewReader(resp.Body)
for {
line, _ := reader.ReadString('\n')
if strings.Contains(line, "[DONE]") {
break
}
fmt.Print(strings.TrimPrefix(line, "data: "))
}
Funciona perfeitamente com HTTP/2 multiplexing, permitindo múltiplos streams simultâneos na mesma conexão.
5. Otimização de Pooling
O método Reset() foi otimizado para reusar o wrapper existente ao invés de recriá-lo a cada requisição. Isso reduz alocações de memória no hot path, melhorando throughput.
6. Prevenção de Memory Leaks
Implementamos limpeza completa de contexto no releaseCtx(), incluindo campos Context e wroteHeader, garantindo que nenhum state residual permaneça entre requisições.
Impacto
✅ Maior robustez em cenários de alta concorrência
✅ Suporte nativo a HTTP/2
✅ Server-Sent Events (SSE) facilitado com c.Flusher()
✅ Redução de alocações por requisição
✅ Zero breaking changes e 100% retrocompatível
Todas as mudanças mantêm compatibilidade total com código existente.
🆚 SSE vs WebSocket
Aparência | SSE | WebSocket |
---|---|---|
CPU do Servidor | 🟢 Baixa | 🟡 Média |
Memória do Servidor | 🟢 2-4 KB/conexão | 🟡 8-16 KB/conexão |
Comprimento de Banda | 🟢 Menor overhead | 🟡Maior overhead |
Latência | 🟡 ~50 ms | 🟢 ~5-10 ms |
Implementação | 🟢 Simples | 🟡 Complexa |
Depuração | 🟢 Ferramentas HTTP | 🔴 Ferramentas específicas |
Firewall/Proxy | 🟢 HTTP padrão | 🟡 Problemas de energia |
Bidirecional | 🔴 Não (apenas servidor→cliente) | 🟢 Sim |
Protocolo | HTTP/1.1 ou HTTP/2 | WebSocket (RFC 6455) |
Parser | Texto simples | Quadros binários |
Handshake | Solicitação HTTP normal | Atualização HTTP |
Reconexão automática | 🟢 Sim (nativo) | 🔴 Manual |
Suporte a navegadores | 🟢 Todos os modernos | 🟢 Todos os modernos |
Sobrecarga por Mensagem | ~45 bytes | ~50+ bytes |
Ideal para | Notificações, feeds, logs | Bate-papo, jogos, colaboração |
Escalabilidade | 🟢 Tenho ~10 mil conexões | 🟢 Milhares de conexões |
Compatível com CDN | 🟢 Sim | 🟡 Limitado |
Complexidade do Backend | 🟢 Baixa | 🟡 Alta |
📊 Comparação de Desempenho - Métodos de Escrita SSE
Este benchmark foi executado para identificar o método mais eficiente de detecção de eventos SSE não relacionados a http.ResponseWriter. Os testes foram realizados em um Apple M3 Max com Go 1.x, medindo nanossegundos por operação (ns/op), bytes alocados (B/op) e número de alocações (allocs/op).
Resultado do Benchmark
O método w.Write([]byte()) apresenta melhor desempenho com 13,56 ns/op e zero alocações, sendo aproximadamente 4x mais rápido que fmt.Fprint() e 9x mais rápido que io.WriteString().
Para mensagens grandes (>1 KB), recomenda-se usar sync.Pool para reutilizar buffers, reduzindo a alocação e pressionando o coletor de lixo.
Recomendações
Desenvolvimento/Depuração use fmt.Fprint() para simplificar
Produção (mensagens pequenas) use w.Write([]byte()) para desempenho máximo.
Produção (mensagens grandes) use sync.Pool com buffers reutilizados
Alto desempenho (>10 mil req/s): Combine w.Write() com buffer pool
O benchmark completo está disponível em /bench
.
Método | Desempenho | Alocações | Complexidade | Recomendação |
---|---|---|---|---|
fmt.Fprint() |
🟡 Médio (53 ns) | 3 alocações | 🟢 Simples | ✅ Desenvolvimento |
io.WriteString() |
🟡 Lento (116 ns) | 1 alocação | 🟢 Simples | ⚠️ Evite |
w.Write([]byte) |
🟢🟢 Excelente (13 ns) | 0 alocações | 🟢 Simples | ✅ Produção |
strings.Builder |
🟡 Lento (124 ns) | 1-2 alocações | 🟡 Mídia | ⚠️ Evitar |
Múltiplas Gravações |
🟢 Boa (21 ns) | 0 alocações | 🟢 Simples | ✅ Alternativo |
sync.Pool |
🟢🟢 Excelente (40 ns) | 0 alocações | 🔴 Complexa | ✅ Alto desempenho |
Inseguro |
🟢🟢 Excelente (21 ns) | 0 alocações | 🔴 Complexa | ⚠️ Especialistas |
🚀 Executando o Benchmark
Para reproduzir os testes de desempenho e validar os resultados em sua máquina, execute:
go test -bench=. -benchtime=1s -benchmem ctx_bench_test.go
Parâmetros do Benchmark
-bench=. - Executar todos os benchmarks
-benchtime=1s - Executar cada benchmark por 1 segundo
-benchmem - Incluir estatísticas de alocação de memória
📊 Resultados do Benchmark
Ambiente de Teste:
SO: macOS (darwin)
Arquitetura: ARM64
CPU: Apple M3 Max
Versão Go: 1.x
Método | ns/op | ops/seg | B/op | allocs/op | Desempenho |
---|---|---|---|---|---|
WriteBytes |
13,32 | 75,1M | 0 | 0 | 🥇 Vencedor |
MultipleWrites |
20,68 | 48,4M | 0 | 0 | 🥈 Excelente |
Inseguro |
20,98 | 47,7 milhões | 0 | 0 | 🥉 Ótimo |
Pooled |
39,00 | 25,6 milhões | 0 | 0 | ✅ Bom |
FmtFprint |
52,69 | 19,0 milhões | 16 | 1 | ⚠️ Lento |
FmtFprintf |
62,61 | 16,0 milhões | 16 | 1 | ⚠️ Lento |
IoWriteString |
111,6 | 9,0 milhões | 1024 | 1 | 🔴 Muito lento |
Otimizado |
119,7 | 8,4 milhões | 1024 | 1 | 🔴 Muito lento |
StringsBuilder |
122,7 | 8,2 milhões | 1032 | 2 | 🔴 Muito Lento |
Método | ns/op | Aceleração | B/op | allocs/op | Desempenho |
---|---|---|---|---|---|
PooledLarge |
234,5 | Linha de Base | 0 | 0 | 🥇 Vencedor |
WriteBytesLarge |
811,2 | 3,46x mais lento | 9472 | 1 | 🔴 Muito Mais Lento |
Exemplos e código fonte
Todos os testes pode ser acessados aqui -> Quick SSE
Aqui você consegue visualizar o codigo fonte do Bench bench
Contribuições
Quick é um projeto open-source em constante evolução. Feedbacks e contribuições são sempre bem-vindos!
GitHub: Quick
Top comments (0)