DEV Community

Cover image for Quick Framework e algumas Melhorias de Performance
Jefferson Otoni Lima
Jefferson Otoni Lima

Posted on

Quick Framework e algumas Melhorias de Performance

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>")
})
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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: "))
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

golang #webframework #performance #opensource #go #quick

Top comments (0)