Fundamentos7 min

Backpressure: protegendo sistemas de sobrecarga

Backpressure é o mecanismo que impede sistemas de serem sobrecarregados. Aprenda a implementar e gerenciar pressão em sistemas distribuídos.

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:

  1. Escolha a estratégia certa para cada contexto
  2. Implemente em cada camada, não só no final
  3. Monitore rejeições e utilização de buffers
  4. Dê feedback ao cliente sobre quando retry
  5. 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.

backpressureresiliênciasistemas distribuídosfluxo
Compartilhar:
Read in English

Quer entender os limites da sua plataforma?

Entre em contato para uma avaliação de performance.

Fale Conosco