Descrição de PR: narre uma mudança da motivação ao rollout
Use isto quando você quiser que um revisor (ou o seu eu do futuro) entenda por que uma mudança aconteceu, quais arquivos se moveram e por quê, o único ponto delicado, e como ela entra em produção com segurança — não apenas leia um diff.
Este é um exemplar copiável. Leve a <section> abaixo para uma lição construída a partir de assets/lesson-template.html — mantenha intactos os tokens de design e o padrão de alternância Simples → Técnico.
1
Narrando a mudança no rate limit
Uma boa mudança conta uma história. Antes de alguém ler uma única linha de código, a pessoa deveria conhecer a motivação (por que mexemos nisso afinal), receber um rápido tour pelos arquivos (o que se moveu e por quê), ver o foco (a única parte sutil que merece um segundo olhar) e confiar no rollout (como entra em produção sem quebrar ninguém).
Pense nisso como… um guia turístico, não um despejo de mapa. Um mapa mostra todas as ruas de uma vez; um guia conduz você, aponta para a única estátua que importa e diz onde fica a saída. Os quatro botões abaixo são paradas nesse passeio.
acme/api · pull request #2184
Substituir o rate limiter de janela fixa por contador de janela deslizante
Pronto para revisão+318 −964 arquivosflag: ratelimit_sliding_v2
Por que estamos mexendo no rate limiter afinal.
Nosso limiter antigo contava requisições em baldes fixos de um minuto. Na fronteira entre dois baldes, um chamador podia disparar a cota inteira de um minuto no último segundo de uma janela e de novo no primeiro segundo da seguinte — o dobro do tráfego pretendido num piscar de olhos. Essa rajada derrubou um serviço a jusante em timeouts duas vezes na semana passada.
A dor
Rajadas de fronteira deixam os clientes enviarem 2× a taxa permitida por ~2 segundos, sobrecarregando o serviço de pagamentos.
O objetivo
Aplicação suave para que o limite valha em qualquer intervalo móvel de 60 segundos, não apenas em minutos alinhados ao relógio.
Por que agora
Dois incidentes em sete dias; a solução paliativa (baixar o teto) prejudicava os clientes bem-comportados.
Quatro arquivos se moveram. Aqui está cada um e por quê.
adicionadolimiter/sliding_window.goO novo algoritmo. Mantém um pequeno anel de subcontagens por segundo e soma os 60 mais recentes. Este é o coração da mudança.
editadolimiter/middleware.goTroca o limiter por trás da flag. Quando ratelimit_sliding_v2 está desligada, o antigo caminho de janela fixa fica intacto — zero mudança de comportamento por padrão.
editadoconfig/flags.yamlRegistra a nova flag, com padrão false. Permite habilitar por ambiente sem um redeploy.
testelimiter/sliding_window_test.goReproduz o caso de rajada na fronteira que as janelas fixas falham, e garante que o novo limiter o bloqueia. O teste é a especificação.
O único ponto delicado que merece uma leitura cuidadosa.
Leia isto devagar: o ring buffer é compartilhado entre goroutines, então o ler-somar-e-incrementar precisa ser atômico. Um "verifica e então adiciona" ingênuo deixa duas requisições concorrentes passarem ambas quando resta apenas um slot.
limiter/sliding_window.go — a seção crítica
// protegido por sw.mu — NÃO separe a leitura e a escritafunc (sw *SlidingWindow) Allow(now time.Time) bool {
sw.mu.Lock()
defer sw.mu.Unlock()
sw.evictOlderThan(now.Add(-60 * time.Second))
if sw.total >= sw.limit {
returnfalse// acima do orçamento para os últimos 60s
}
sw.record(now) // incrementa DENTRO do mesmo lockreturntrue
}
Se você revisar apenas um trecho, que seja este. Todo o resto é encanamento; a corretude mora aqui.
Como entra em produção sem acordar ninguém às 3 da manhã.
Subir no escuro por trás de uma flag
Faça o merge com ratelimit_sliding_v2=false em todos os lugares. Nenhum tráfego atinge o novo caminho. Seguro para dar merge hoje.
Canário em 5% no staging, depois em produção
Habilite a flag para 5% das chaves de API. Observe por 1 hora antes de ampliar.
Monitore os sinais certos
Dashboards: ratelimit.rejections (deve subir um pouco), payments.timeouts (deve cair a zero) e a latência limiter.allow.p99 (precisa ficar abaixo de 1ms). Alerte se o p99 dobrar.
Aumente 5% → 25% → 100%
Avance um degrau só quando o passo anterior estiver limpo por uma hora inteira. Rollout completo previsto ao longo de dois dias.
Plano de rollback
Mude ratelimit_sliding_v2=false — reversão instantânea para o limiter antigo, sem deploy. Mantenha a flag por duas semanas, depois remova o caminho morto num PR de acompanhamento.
Parada 1 de 4 · Motivação
O trabalho do revisor, tornado barato
Um diff responde "o que mudou?", mas as perguntas reais de um revisor são "por quê?", "onde eu olho?" e "isto vai quebrar produção?". O formato de quatro etapas entrega exatamente essas respostas logo de cara, então o tempo de revisão cai e o escrutínio certo recai sobre o trecho certo.
A motivação evita a correção errada
Sem o enquadramento do "por que agora", um revisor não consegue dizer se baixar um valor de configuração teria bastado. Declarar a contagem de incidentes e a solução paliativa rejeitada antecipa o bikeshed.
O foco direciona a atenção
A maioria dos trechos é mecânica; um é estrutural. Nomear a seção crítica de concorrência (ler-verificar-incrementar sob um único lock) faz a revisão cuidadosa acontecer onde um bug realmente machucaria, em vez de se diluir entre variáveis renomeadas.
O rollout conquista confiança
Protegido por flag, com canário, monitorado em métricas nomeadas, com rollback sem deploy. O revisor aprova um plano, não um salto — o merge é reversível por uma mudança de configuração, não por uma correria de reverter-e-redeployar.
2
O arco inteiro em uma imagem
Leia da esquerda para a direita: a mudança começa com um motivo e termina com segurança em produção. As quatro paradas acima são estes quatro momentos.
O caminho para frente envia a mudança; o caminho vermelho tracejado é o rollback instantâneo — uma mudança de configuração, não um redeploy.