PGO em Go, ou Profile-Guided Optimization, é uma forma prática de deixar o compilador tomar decisões melhores usando dados reais de execução. Em vez de otimizar apenas olhando para o código-fonte, o build passa a considerar quais funções realmente aparecem no caminho quente da aplicação, quais chamadas são frequentes e onde o runtime gasta tempo sob uma carga parecida com produção.
Esse recurso ficou disponível de forma experimental no Go 1.20 e passou a ser recomendado para uso normal a partir do Go 1.21. A proposta é simples: você coleta um perfil de CPU com pprof, salva esse arquivo no projeto e compila com go build -pgo. O compilador usa o perfil para melhorar decisões de inlining, devirtualização e layout de código. O ganho varia conforme o serviço, mas é comum enxergar melhorias sem reescrever algoritmo, trocar framework ou adicionar cache prematuro.
O ponto importante é tratar PGO como engenharia de performance, não como botão mágico. Ele complementa benchmarks em Go, testes de tabela, observabilidade e rollout seguro com feature flags. Se o perfil não representa o tráfego real, o binário pode otimizar o caminho errado. Se o problema é uma query ruim, uma chamada externa lenta ou um lock mal desenhado, PGO não substitui diagnóstico.
Quando PGO faz sentido
PGO vale investigar quando você já tem um serviço Go em produção, um CLI usado em volume, um worker com carga previsível ou uma API com hot paths bem conhecidos. Alguns cenários comuns:
- APIs HTTP com endpoints de alta frequência.
- Workers que processam eventos em lote.
- CLIs usadas em pipelines de CI ou automação interna.
- Exporters, agents e componentes de plataforma rodando continuamente.
- Serviços com CPU relevante depois de otimizar banco, rede e cache.
- Bibliotecas internas chamadas por muitos serviços.
Não comece por PGO se a aplicação ainda não tem métrica básica. Primeiro saiba latência, throughput, consumo de CPU, alocações, tempo de GC e taxa de erro. Depois descubra onde o tempo é gasto com pprof. Só então faz sentido perguntar se o compilador consegue melhorar aquele caminho.
Também não use PGO para esconder código confuso. Se uma função é lenta porque faz trabalho desnecessário, aloca sem motivo ou chama o banco em loop, corrija o desenho. PGO é mais útil quando o algoritmo já está razoável e você quer capturar ganhos do compilador em cima do comportamento real.
Fluxo básico com pprof
O fluxo mínimo tem quatro etapas: coletar perfil, salvar o arquivo, compilar com PGO e comparar resultado.
Em um serviço HTTP, você pode expor net/http/pprof em uma porta interna. Nunca exponha isso publicamente sem autenticação e controle de rede.
package main
import (
"log"
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
mux := http.NewServeMux()
mux.HandleFunc("/debug/pprof/", http.DefaultServeMux.ServeHTTP)
log.Println(http.ListenAndServe("127.0.0.1:6060", mux))
}()
// Inicie aqui seu servidor principal, worker ou CLI.
}
Com a aplicação sob carga representativa, colete um perfil de CPU:
curl -o default.pgo "http://127.0.0.1:6060/debug/pprof/profile?seconds=60"
Depois compile usando esse arquivo:
go build -pgo=default.pgo -o bin/app ./cmd/app
O nome default.pgo tem uma conveniência: se o arquivo estiver no diretório do pacote principal, go build pode encontrá-lo automaticamente em builds futuros. Mesmo assim, em CI costuma ser mais claro passar -pgo=default.pgo de forma explícita para evitar surpresa quando o layout do projeto muda.
Como coletar um perfil bom
Um perfil ruim gera decisão ruim. O arquivo usado pelo PGO deve representar o tráfego que você quer otimizar, não uma navegação aleatória no ambiente local.
Um perfil bom costuma ter estas características:
- Carga parecida com produção, com proporção realista entre endpoints ou tipos de job.
- Duração suficiente para reduzir ruído, normalmente 30 a 120 segundos.
- Versão do código próxima da versão que será compilada com PGO.
- Ambiente sem tarefas artificiais dominando CPU, como debug local ou benchmark mal configurado.
- Dados sanitizados: perfil de CPU normalmente não contém payload bruto, mas nomes de funções, caminhos e labels podem revelar detalhes internos.
Se você tem um endpoint que responde por 80% do tráfego, ele deve aparecer no perfil. Se você otimiza usando apenas um teste sintético de um endpoint raro, o binário pode ficar melhor no benchmark e irrelevante na produção.
Em APIs Go, combine coleta de perfil com métricas. Use Prometheus, OpenTelemetry ou a ferramenta da sua empresa para confirmar que o período escolhido não teve incidente, deploy parcial, cache frio anormal ou tráfego de robô. PGO precisa de perfil real, mas real não significa descontrolado.
Validando antes de publicar
Depois de compilar com PGO, compare contra o binário sem PGO. O mínimo saudável é medir:
go test ./...
go test -bench=. -benchmem ./...
go build -o bin/app-sem-pgo ./cmd/app
go build -pgo=default.pgo -o bin/app-com-pgo ./cmd/app
Se a aplicação tem benchmark específico para o hot path, use esse benchmark como primeiro sinal. Se não tem, crie um pequeno benchmark ou teste de carga focado. Não precisa simular o mundo inteiro; precisa medir o caminho que motivou o PGO.
Em serviços web, uma validação prática é rodar wrk, hey, vegeta ou uma ferramenta interna contra os dois binários no mesmo ambiente. Compare throughput, p50, p95, p99, uso de CPU e alocações. Se a latência melhora 2% mas o consumo de memória sobe muito, talvez o ganho não pague a complexidade. Se a melhora aparece apenas em uma máquina e desaparece no CI, investigue ruído antes de publicar.
Também rode go tool pprof no perfil para entender o que o compilador está vendo:
go tool pprof default.pgo
Dentro do console:
(pprof) top
(pprof) list NomeDaFuncao
(pprof) web
Isso ajuda a revisar se o perfil representa o serviço. Se o topo mostra serialização JSON, roteamento HTTP, validação, compressão ou uma função de domínio esperada, ótimo. Se mostra logs de debug, endpoint administrativo ou teste artificial, colete de novo.
PGO no CI/CD
O arquivo de perfil deve entrar no repositório ou em um artefato versionado? Depende do fluxo da empresa. Para muitos times, versionar default.pgo no repositório é simples e auditável. O diff mostra quando o perfil mudou, o build fica reproduzível e o rollback acompanha o código.
Um desenho prático:
- Coletar perfil de uma versão estável em staging ou produção controlada.
- Revisar o perfil com
go tool pprof top. - Salvar como
default.pgojunto ao pacote principal. - Rodar testes e benchmarks no pull request.
- Compilar imagem ou binário com
go build -pgo=default.pgo. - Publicar primeiro em canary.
- Comparar métricas antes de expandir o rollout.
Se você já usa GoReleaser, inclua a flag de build na configuração de release. Para serviços em container, registre no changelog ou no label da imagem qual perfil foi usado. Isso facilita investigar uma regressão depois.
builds:
- id: app
main: ./cmd/app
flags:
- -pgo=default.pgo
Em monorepos, cuidado para não aplicar o perfil de um binário a outro. Um worker de fila e uma API HTTP podem compartilhar pacote, mas ter hot paths diferentes. Nesses casos, mantenha perfis separados e builds explícitos.
Rollout seguro em produção
PGO altera otimizações do compilador. Em geral, isso deve preservar comportamento, mas deploy seguro continua obrigatório. Trate como qualquer mudança de performance: publique pequeno, observe e tenha rollback.
Métricas úteis no rollout:
- Latência p50, p95 e p99 por endpoint.
- CPU por pod, instância ou processo.
- Taxa de erro e timeouts.
- Alocações e pressão de GC, quando disponíveis.
- Throughput por worker ou mensagens por segundo.
- Saturação de banco, fila e serviços externos.
Se você usa Kubernetes, rode um canary com poucas réplicas e compare contra a versão anterior. Se usa deploy mais simples, faça um rollout manual em uma instância ou janela de menor tráfego. Combine com graceful shutdown para não confundir melhoria de binário com queda causada por encerramento brusco.
Não declare vitória só porque o benchmark local melhorou. A pergunta é: a métrica de usuário ou de operação melhorou? Menos CPU pode permitir reduzir custo, aumentar margem de headroom ou absorver pico. Menos latência pode melhorar conversão, tempo de resposta de API ou estabilidade de fila. Ganho que não move nenhuma dessas coisas talvez não valha manter mais um artefato.
Armadilhas comuns
A primeira armadilha é coletar perfil em ambiente errado. Perfil local com banco vazio, cache quente e um único endpoint raramente representa produção. Use local para aprender o fluxo; use staging ou produção controlada para gerar perfil de verdade.
A segunda é deixar o perfil envelhecer. Se o serviço mudou bastante, se os endpoints principais mudaram ou se o tráfego migrou para outro fluxo, o default.pgo antigo perde valor. Revise o perfil quando houver mudança grande de produto, arquitetura ou versão do Go.
A terceira é ignorar segurança. Endpoints pprof podem revelar nomes internos de pacotes, rotas, funções e comportamento. Restrinja por rede, autenticação, sidecar, túnel interno ou coleta fora do processo público. Em incidente, você quer poder coletar perfil com segurança, não abrir /debug/pprof/ para a internet.
A quarta é confundir PGO com APM. PGO usa perfil para compilar melhor; observabilidade mostra o que está acontecendo no sistema. Você ainda precisa de logs, métricas e traces para investigar falhas. O guia de OpenTelemetry em Go cobre essa camada operacional.
A quinta é esquecer o mercado. Muitas vagas Go backend e vagas Go DevOps/SRE pedem performance, observabilidade, Kubernetes, profiling e ownership de produção. Saber explicar PGO com pprof, benchmark, canary e rollback mostra maturidade além da sintaxe. Para comparar a conversa de performance com outra linguagem de sistemas, veja também como o portal Rust Brasil contrasta Go e Rust em concorrência, performance e controle de memória.
Checklist prático
Antes de colocar PGO no build principal, confirme:
- Existe métrica de baseline antes da mudança.
- O perfil foi coletado sob carga representativa.
go tool pprof topmostra hot paths esperados.- Testes e benchmarks passam com e sem PGO.
- O build registra qual perfil foi usado.
- O rollout começa pequeno e tem rollback claro.
- O endpoint
pprofnão está exposto publicamente. - O perfil será revisado quando o tráfego ou o código mudarem.
PGO é uma das melhores expressões da filosofia prática de Go: usar ferramenta simples, medir comportamento real e melhorar sem cerimônia desnecessária. Quando aplicado com disciplina, ele transforma dados de produção em um binário melhor. O próximo passo é conectar esse fluxo ao seu processo normal de release: perfil, build, teste, canary, métrica e rollback.