Quando um sistema recebe mais trabalho do que consegue processar, algo precisa ceder. Sem controle, o resultado é degradação progressiva até colapso. Backpressure é o mecanismo que propaga sinais de sobrecarga de volta à origem, permitindo controle de fluxo.
Backpressure não é fracasso. É comunicação honesta sobre capacidade.
O Problema da Sobrecarga
Sem backpressure
Producer: 1000 req/s
Consumer: 500 req/s
Segundo 1: fila = 500
Segundo 2: fila = 1000
Segundo 3: fila = 1500
...
Minuto 10: fila = 300.000
→ Memória esgota
→ Latência explode
→ Sistema falha
Com backpressure
Producer: 1000 req/s
Consumer: 500 req/s
Limite de fila: 1000
Segundo 1: fila = 500
Segundo 2: fila = 1000 (limite!)
Segundo 3: producer bloqueado ou rejeitado
→ Producer desacelera
→ Sistema permanece estável
→ Latência controlada
Estratégias de Backpressure
1. Drop (Descarte)
# Descarta requests quando sobrecarregado
if queue.size() >= MAX_QUEUE:
drop(request)
metrics.increment('dropped_requests')
else:
queue.add(request)
Quando usar:
- Dados não críticos (métricas, logs)
- Sistemas de streaming em tempo real
- Melhor perder alguns do que perder todos
Quando evitar:
- Transações financeiras
- Dados que não podem ser perdidos
2. Buffer com Limite
# Bloqueia producer quando buffer cheio
queue = BlockingQueue(max_size=1000)
def produce(item):
queue.put(item, timeout=5) # Bloqueia até 5s
def consume():
return queue.get()
Quando usar:
- Picos temporários de carga
- Producers que podem esperar
- Comunicação interna entre serviços
3. Reject (Rejeição)
# Rejeita com erro quando sobrecarregado
if current_load > MAX_LOAD:
return HttpResponse(status=503,
headers={'Retry-After': '30'})
Quando usar:
- APIs públicas
- Quando client pode retry
- Proteção contra abuse
4. Throttle (Limitação)
# Rate limiting por cliente
@rate_limit(100, per='minute')
def api_endpoint(request):
return process(request)
Quando usar:
- APIs multi-tenant
- Proteção de recursos compartilhados
- Garantir fairness entre clientes
5. Adaptive (Adaptativo)
# Ajusta limites baseado em métricas
def adaptive_limit():
if latency_p99 > SLO:
decrease_rate_limit()
elif latency_p99 < SLO * 0.5:
increase_rate_limit()
Quando usar:
- Sistemas com carga variável
- Quando limites fixos não funcionam
- Otimização contínua
Implementando Backpressure
Em APIs HTTP
# Middleware de backpressure
class BackpressureMiddleware:
def __init__(self, max_concurrent=100):
self.semaphore = Semaphore(max_concurrent)
self.current = 0
def process_request(self, request):
if not self.semaphore.acquire(blocking=False):
return HttpResponse(status=503)
self.current += 1
def process_response(self, response):
self.semaphore.release()
self.current -= 1
return response
Em Filas de Mensagem
# Kafka consumer com backpressure
consumer = KafkaConsumer(
'topic',
max_poll_records=100, # Limita batch
fetch_max_wait_ms=500, # Limita espera
)
# Pausa quando processing lento
if processing_lag > threshold:
consumer.pause()
elif processing_lag < threshold / 2:
consumer.resume()
Em Streams Reativos
// Project Reactor com backpressure
Flux.create(sink -> {
// Producer
while (hasData()) {
sink.next(getData());
}
})
.onBackpressureBuffer(1000) // Buffer de 1000
.onBackpressureDrop(dropped -> { // Drop se exceder
log.warn("Dropped: {}", dropped);
})
.subscribe(item -> process(item));
Em gRPC
// Streaming com flow control
service DataService {
rpc StreamData(StreamRequest) returns (stream DataChunk);
}
// Server controla envio baseado em acks
func (s *server) StreamData(req *pb.StreamRequest, stream pb.DataService_StreamDataServer) error {
for data := range dataChannel {
if err := stream.Send(data); err != nil {
// Client não está acompanhando
return err
}
}
return nil
}
Padrões de Backpressure
Circuit Breaker
class CircuitBreaker:
def __init__(self, failure_threshold=5, reset_timeout=30):
self.failures = 0
self.state = 'CLOSED'
def call(self, func):
if self.state == 'OPEN':
raise CircuitOpenError()
try:
result = func()
self.failures = 0
return result
except Exception:
self.failures += 1
if self.failures >= self.failure_threshold:
self.state = 'OPEN'
schedule(self.reset, self.reset_timeout)
raise
def reset(self):
self.state = 'HALF-OPEN'
Load Shedding
# Descarta requests de baixa prioridade primeiro
def should_shed(request):
current_load = get_current_load()
if current_load > 90:
# Só requests críticos
return request.priority != 'critical'
elif current_load > 70:
# Descarta low priority
return request.priority == 'low'
return False
Bulkhead
# Isola recursos por tenant/funcionalidade
pools = {
'critical': ThreadPool(50),
'standard': ThreadPool(30),
'batch': ThreadPool(20),
}
def process(request):
pool = pools.get(request.priority, pools['standard'])
try:
return pool.submit(handle, request, timeout=5)
except ThreadPoolExhausted:
# Backpressure específico deste pool
return HttpResponse(status=503)
Sinais de Falta de Backpressure
1. Latência crescente sob carga
Carga 50%: latência = 100ms
Carga 80%: latência = 500ms
Carga 100%: latência = 5000ms
→ Sistema aceita tudo, performance degrada para todos
2. Memória crescente
Filas internas crescendo indefinidamente
Buffers sem limite
GC cada vez mais frequente
3. Cascata de falhas
Serviço A sobrecarregado
→ Timeout em serviço B esperando A
→ Fila de B cresce
→ B também falha
→ Cascata continua
Métricas de Backpressure
O que monitorar
# Taxa de rejeição
rejected_requests_total
dropped_messages_total
# Utilização de buffers
queue_size / queue_capacity
buffer_utilization_percent
# Tempo de espera
queue_wait_time_seconds
# Rate limiting
rate_limited_requests_total
throttled_requests_by_client
Alertas
# Backpressure ativando frequentemente
- alert: HighRejectionRate
expr: |
rate(rejected_requests_total[5m])
/ rate(total_requests[5m]) > 0.05
for: 5m
# Fila próxima da capacidade
- alert: QueueNearCapacity
expr: |
queue_size / queue_capacity > 0.8
for: 2m
Armadilhas Comuns
1. Backpressure muito agressivo
❌ Rejeita com fila 10% cheia
→ Capacidade subutilizada
✅ Rejeita quando realmente necessário
→ Usa capacidade disponível
2. Sem feedback para cliente
❌ return 503 # Cliente não sabe quando retry
✅ return 503 with Retry-After: 30
→ Cliente sabe quando tentar novamente
3. Backpressure só no final
❌ API → Fila → Worker → DB
↑
Backpressure só aqui
(fila já está enorme)
✅ API → Fila → Worker → DB
↑ ↑ ↑
Backpressure em cada estágio
Conclusão
Backpressure é essencial para sistemas estáveis:
- Escolha a estratégia certa para cada contexto
- Implemente em cada camada, não só no final
- Monitore rejeições e utilização de buffers
- Dê feedback ao cliente sobre quando retry
- Teste comportamento sob sobrecarga
A regra fundamental:
É melhor rejeitar alguns requests
do que degradar todos
Sistema sem backpressure é bomba-relógio esperando o próximo pico.
Backpressure é honestidade: "não consigo agora, tente depois". Mentir sobre capacidade leva ao colapso.