stamatios
← Voltar ao feed
Esteira de produção freia sozinha com válvula própria enquanto pacotes se acumulam, mecanismo da Canva contra sobrecarga
Dev & Engenharia

Canva ensina workers de fila a desacelerar sob sobrecarga

resumo de ~3 min

O problema dos workers gananciosos

Grande parte do trabalho no Canva acontece de forma assíncrona: a requisição chega, o serviço coloca uma mensagem em uma fila e um worker a processa depois - redimensionar um asset, rodar um modelo de classificação, enviar um e-mail ou reconciliar uma assinatura. Isso mantém o caminho da requisição rápido e confiável, mas os workers são projetados para serem gananciosos: assim que há capacidade, eles puxam mensagens sem parar.

O problema surge quando uma dependência - um banco de dados ou outro serviço - começa a falhar. O worker não percebe e continua puxando mensagens em velocidade máxima, o que piora a recuperação da dependência, desperdiça recursos em processamentos fadados ao erro, empurra mensagens para a Dead Letter Queue (DLQ) e aciona a paginação do engenheiro de plantão. No ritmo de operação do Canva, com milhares de filas e lógica de negócio diversa, isso não é caso raro: um banco lento durante uma migração pode escalar para um incidente de alta severidade com exportações falhando para milhares de usuários.

Alternativas existentes e suas limitações

As equipes tentavam mitigar o problema com diferentes mecanismos, cada um com desvantagens: escalonamento manual da frota de workers (um palpite, que pode sobrecarregar a dependência que já falha); rate limits fixos (corretos apenas para um mundo fixo); circuit breakers (sem transição gradual, com flood súbito ao religar); exponential backoff (por mensagem, sem regular a taxa global); e adaptive backoff - inspirado no throttling adaptativo do capítulo "Handling Overload" do livro de SRE do Google - que rejeita uma fração das chamadas conforme os erros sobem. Essa última abordagem funcionava bem em uma equipe, mas vivia fora da biblioteca compartilhada de filas e era fixada a um único algoritmo.

A solução: Worker Backpressure

O Canva construiu o Worker Backpressure, um mecanismo integrado à sua biblioteca de filas que forma um loop de feedback em torno das chamadas do worker à sua dependência. Ele tem três componentes:

  • Sinais: após cada mensagem processada, o worker registra sucesso ou falha.
  • Backpressure Controller: um controlador plugável (uma interface, não um algoritmo fixo) que consome os resultados e mantém um único número, o fator de backoff, de 0.0 (velocidade total) a 1.0 (totalmente contido). Ele opera contra um set point - a taxa de falha tratada como ruído de fundo aceitável (5% nos casos descritos). Abaixo do set point, o controller não interfere.
  • Permits: antes de cada poll, o controller escala a concorrência do worker em proporção ao backoff atual.

Tudo roda localmente, sem coordenador externo ou chamadas de rede adicionais; o custo de runtime são duas operações aritméticas. O nome, segundo os autores, é uma concessão: o conceito é mais próximo dos concurrency-limits da Netflix, mas mantiveram "backpressure" porque recusar trabalho no worker deixa a carga na fila, o único ponto upstream em que podem pressionar.

Testado em dois incidentes reais

O mecanismo enfrentou duas falhas de perfil oposto em produção:

Indisponibilidade do provedor de nuvem: cerca de 4 horas de picos intermitentes de erro, com várias dependências falhando simultaneamente. O worker desacelerou e reaccelerou sozinho, com a configuração padrão. De 1.610.173 sucessos, houve 498 erros - média de 0,03% no incidente todo e pico de 1,42% na frota (instâncias individuais chegaram a picos de 50% e foram contidas). A DLQ cresceu apenas 1 mensagem durante todo o evento.

Sobrecarga sustentada por 32,5 horas: um worker que envia mensagens a outra fila com cota rígida de throughput estourou o limite. O controller ficou ativo o tempo todo; a taxa de falha da frota atingiu pico de 3,7% (média de 2,76%), enquanto a frota processava cerca de 2 milhões de mensagens por hora, acima da baseline pré-incidente. Das 1,8 milhão de tentativas falhas, apenas 22 mensagens chegaram à DLQ - aproximadamente uma a cada 82 mil falhas. Os autores estimam que, sem backpressure, a DLQ teria recebido pelo menos cerca de 16 mil mensagens, com base na taxa de falha de ~19% observada em instâncias ainda não contidas, e ressaltam que essa é uma estimativa conservadora.

Trade-offs e próximos passos

O mecanismo reduz a taxa de erro, mas tem custo de throughput - aceitável nos dois incidentes, em que os workers tinham folga, mas sentível por um worker operando no limite. O controller usa um único sinal (sucesso/falha) como proxy da saúde da dependência, o que é simples de raciocinar, mas pode não servir a todos os workloads; a equipe espera descobrir as lacunas conforme a adoção se expande, e o design permite adicionar sinais como latência ou mensagens em andamento.

Os próximos passos incluem estender o backpressure a todos os workers de fila do Canva. Uma segunda parte do texto deve detalhar como o fator de backoff se move, como um worker totalmente contido detecta a recuperação da dependência, como escolher os parâmetros de ajuste e resultados de simulações de falhas.