Conceitos
Load shedding e backpressure
Intuição
Imagine um restaurante pequeno numa noite em que chega muito mais gente do que a cozinha dá conta. Se o recepcionista aceitar todo mundo, as mesas ficam esperando tanto que os clientes vão embora antes de o prato chegar, e a cozinha gasta esforço em comida que ninguém vai comer. Se ele avisar logo na porta que não há lugar, quem já está sentado é servido a tempo, e quem foi recusado fica sabendo na hora, em vez de esperar em vão. Rejeitar cedo e de forma explícita protege quem já foi aceito.
Como funciona
Segundo o artigo da Amazon Builders' Library sobre load shedding, a latência cresce com a utilização; quando passa do timeout do cliente, as requisições expiram, todo o trabalho que o servidor já fez nelas é desperdiçado e as retentativas multiplicam a carga, de modo que a sobrecarga se sustenta sozinha. Por isso o artigo distingue throughput (tudo o que chega por segundo) de goodput (o subconjunto tratado sem erro e rápido o bastante para o cliente aproveitar), e o goodput é o que a proteção tenta manter. No load shedding, o servidor perto da sobrecarga rejeita o excedente para manter baixa a latência das requisições que aceitou, e o resultado ideal num teste de carga é um platô de goodput, formado quando o serviço está perto da utilização plena, que permanece plano mesmo com mais carga aplicada. Rejeitar também custa recursos, então o goodput acaba caindo em algum ponto. O artigo defende testar até quebrar, e muito além, porque quem não testou deve supor que o serviço falhará da pior maneira possível; trata como falso positivo a rejeição feita quando ainda havia capacidade; e pede instrumentar quem foi rejeitado e em qual operação, mantendo as falhas rápidas fora das métricas de latência, porque elas reduzem a mediana e a latência ruim das requisições bem-sucedidas fica escondida, como no exemplo de um serviço que rejeita 60% do tráfego. As filas escondem o problema: limitar o tamanho e o tempo de espera e descartar o que ficou velho demais (ou usar LIFO, quando o protocolo permite) libera o servidor para requisições novas, com mais chance de sucesso; no balanceador, o artigo recomenda o spillover, que falha rápido em vez de enfileirar o excedente (ao contrário da surge queue); fora do artigo, devolver assim a saturação a quem envia costuma ser chamado de backpressure. Para não desperdiçar trabalho, o cliente pode mandar uma dica de timeout, e o servidor abandona a requisição que já expirou; além disso, prioriza o que conclui trabalho já iniciado e o ping do balanceador, porque, se o servidor não responder a tempo, o balanceador para de lhe mandar requisições por um período; o artigo também recomenda configurar alto o máximo de conexões do balanceador e do proxy, sem passar do número de threads, processos e descritores de arquivo do servidor, para que sobrem recursos aos health checks, e deixar o servidor fazer o descarte mais preciso com informação local. Em um serviço que depende de muitos servidores em paralelo, o artigo The Tail at Scale aponta que várias camadas de filas em servidores intermediários e switches amplificam a variabilidade de latência, e que a latência final é ditada pelo componente mais lento; o paper adverte que reenviar sem critério acrescenta carga inaceitável, e por isso uma das variações de hedged requests só reenvia a uma segunda réplica depois de a primeira passar da latência esperada do percentil 95 para aquela classe de requisições, o que limita a carga extra a cerca de 5%.
Na prática
Em um serviço com balanceador na frente, uma configuração segura é deixar o balanceador falhar rápido (spillover), configurar alto o máximo de conexões dele sem exceder as threads e os descritores do servidor, para sobrar recurso aos health checks, e deixar o servidor aplicar o load shedding preciso com informação local. Use isso sempre que os clientes tiverem timeouts e fizerem retentativas, limitando também o hedging a um teto de carga extra, e valide com testes de sobrecarga levados até quebrar. Meça as rejeições por cliente e operação e não deixe as respostas rápidas de erro entrarem na latência, ou a mediana parecerá ótima mesmo com a latência das requisições bem-sucedidas péssima.
Use em um desenho
Pratique na escola de System Design os casos que dependem deste conceito.