A pia que transborda
Se a torneira despeja água mais rápido do que o ralo escoa, a pia começa a encher. Você pode aumentar a pia (um buffer maior) — mas se a torneira continuar mais rápida que o ralo, mais cedo ou mais tarde transborda no chão. A única solução real é a torneira sentir que a pia está cheia e diminuir o fluxo. Backpressure é exatamente esse aviso subindo o cano até a torneira: o ralo dita o ritmo, não a torneira.
Quem dita o ritmo
A raiz do problema é o modelo push: o produtor empurra no ritmo dele e assume que o consumidor dá conta. Quando não dá, o excesso vai para um buffer, e o buffer é uma bomba-relógio — cresce silenciosamente até a memória acabar.
Backpressure inverte para pull: o consumidor sinaliza quanto aguenta e o produtor só envia isso. O ritmo passa a ser ditado por quem processa, não por quem gera, e o buffer para de crescer porque a fonte desacelera junto.
Quando o buffer enche mesmo com o sinal, existe uma decisão de negócio a tomar, e ela depende do dado. Pausar/bloquear o produtor preserva tudo, mas propaga a lentidão para trás (bom para dados que não podem ser perdidos, como pagamentos). Descartar — dropar o mais antigo, ou amostrar — mantém o ritmo às custas de perder itens (aceitável para métricas, telemetria, posições de mouse). Recusar novas entradas com um erro claro empurra a decisão para o cliente (o território do rate limiting). Não existe opção universal: existe a que combina com o valor do dado.
O sinal precisa atravessar a cadeia inteira para funcionar. Se um pipeline tem fonte → transformação → gravação, e a gravação está lenta, o backpressure só ajuda se a transformação e a fonte respeitarem o pedido de desacelerar. Um único elo que ignora o sinal — que bufferiza sem limite 'para não travar' — quebra a corrente: o buffer volta a crescer nele, e o out-of-memory apenas mudou de lugar. É por isso que APIs como o Reactive Streams padronizam o request(n) de ponta a ponta, em vez de deixar cada elo inventar o seu.
Como isso aparece na prática
A ingestão que derrubava o serviço à noite
Cenário
Um serviço recebia eventos por webhook e os gravava num banco. Em picos noturnos, os eventos chegavam mais rápido do que o banco gravava; o buffer em memória crescia até o processo morrer por falta de memória.
Como o padrão entra
A ingestão passou a ler do stream com backpressure: enquanto o banco não confirmava a gravação, a leitura de novos eventos pausava, e o webhook passava a responder mais devagar sob pressão em vez de acumular tudo.
Trade-off: Sob pico, o produtor (o emissor do webhook) passou a ver respostas mais lentas e a precisar de retry. Em troca, o serviço parou de morrer — degradou o ritmo em vez de cair inteiro.
A telemetria que preferiu descartar
Cenário
Um pipeline de métricas de UI enviava cada movimento e clique. Sob carga, o buffer enchia, e pausar o produtor travava a interface do usuário — inaceitável para dado que é só telemetria.
Como o padrão entra
Em vez de pausar, o pipeline passou a amostrar: sob pressão, descartava parte dos eventos de baixo valor e mantinha os importantes, preservando a fluidez da interface.
Trade-off: As métricas ficaram amostradas, não completas, sob carga. Foi a escolha certa para o tipo de dado: perder alguns pontos de telemetria é aceitável; travar a interface do usuário para não perdê-los, não.
Onde isto dá errado
Resolver com um buffer maior
Aumentar o buffer não elimina a diferença de velocidade, só adia o estouro e piora a latência no caminho. Enquanto o produtor for mais rápido, qualquer buffer finito enche — a solução é desacelerar a fonte, não guardar mais.
Um elo da cadeia que ignora o sinal
Backpressure só funciona se toda a cadeia respeita o 'vá mais devagar'. Um único estágio que bufferiza sem limite para 'não travar' quebra a corrente, e o out-of-memory apenas se muda para dentro dele.
Pausar dado que não podia esperar, ou descartar o que não podia sumir
A resposta ao buffer cheio depende do dado: pausar um pagamento é seguro, pausar a interface do usuário não; descartar telemetria é aceitável, descartar uma transação não. Escolher a estratégia errada para o tipo de dado troca um problema por outro.
Do push sem limite ao pull com ritmo
// Sem backpressure: o produtor despeja no ritmo dele, o consumidor
// nao acompanha, e o que sobra vai para um buffer que so cresce.
for (const item of fonteRapida) {
fila.push(item); // fila cresce sem limite -> memoria estoura
processarLento(item); // nao vaza no ritmo da entrada
}
// Com backpressure: o consumidor DITA o ritmo. Ele pede o que
// aguenta, e o produtor so manda quando ha demanda.
async function consumir(stream: Readable) {
for await (const item of stream) { // for await respeita o fluxo:
await processarLento(item); // enquanto processa, o stream
} // PAUSA a leitura da fonte.
}
// O sinal "va mais devagar" sobe a cadeia inteira, ate a origem.
// Quando o buffer enche, as opcoes sao: PAUSAR o produtor,
// DESCARTAR o excesso (drop/sample), ou RECUSAR (rejeitar novos).Quando usar × quando evitar
Use quando
3- Pipelines de streaming onde a fonte pode superar o destino.
- Ingestão que grava em um sistema mais lento.
- Sempre que um buffer entre produtor e consumidor puder crescer sem limite.
Evite quando
2- Quando o consumidor sempre acompanha o produtor com folga.
- Em lotes pequenos e limitados, sem ameaça à memória.
Testar o que ficou
3 armadilhas de Backpressure viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.
Onde isto aparece de verdade
Você já usa isto, talvez sem saber o nome.
request(n) do Reactive StreamsA API em que o consumidor pede exatamente quantos itens aguenta, impedindo o produtor de despejar mais do que ele processa.highWaterMark dos streams do NodeO limite do buffer que faz o stream pausar a leitura da fonte quando o consumidor não vaza rápido o bastante.a fila que enche e recusaQuando o buffer lota e o sistema passa a recusar ou bloquear novas mensagens, é backpressure pedindo à origem para desacelerar.
O padrão em uma linha
Sem nome de domínio, sem cerimônia. Só a ideia.
// Produtor para quando o consumidor não aguenta.
if (fila.length > LIMITE) await esperarDrenar(fila);O que ele cobra
1 salto a mais entre a chamada e o efeito.
- O produtor precisa saber lidar com 'vá mais devagar' — pausar, bufferizar ou descartar, cada opção com seu custo
- Propagar o sinal por toda a cadeia exige que cada elo o respeite, senão ele para no primeiro que o ignora
Não vale se: o consumidor sempre acompanha o produtor com folga — sem risco de afogamento, controlar o fluxo é cerimônia sem retorno.
Onde isto aparece
Continue por aqui
- Rate limitingCapacidade é finita, e demanda não pede licença. Limitar a taxa é escolher conscientemente quem é recusado quando a demanda passa do que se aguenta — em vez de deixar a sobrecarga escolher, degradando o serviço para todo mundo ao mesmo tempo.
- BulkheadRecursos compartilhados propagam falha: se todas as chamadas disputam o mesmo pool, a dependência mais lenta consome tudo e derruba as rotas que nem falavam com ela. O anteparo divide o recurso em compartimentos com teto próprio.
- Fila × Pub/SubDois modelos de mensageria que parecem iguais e resolvem problemas opostos. A fila divide trabalho: uma mensagem é processada por um único consumidor entre vários. O pub/sub difunde um fato: um evento chega a todos os inscritos, cada um com sua cópia. Escolher errado ou duplica trabalho que devia ser único, ou deixa de avisar quem precisava saber.