O Amazon Simple Queue Service (SQS) é um serviço poderoso projetado para gerenciar as necessidades de mensagens de microsserviços robustos e desacoplados.
Ele se integra fortemente com o AWS Lambda, seu parceiro nativo, como um destino alvo. O SQS pode disparar uma função Lambda como seu alvo e enviar os itens de lote como entrada.
Entretanto, no lado da função Lambda, lidar com essa entrada pode ser complicado, especialmente quando erros e tentativas surgem.
Este é o segundo artigo de uma série de três partes sobre as melhores práticas do SQS
Este artigo ensinará como lidar com falhas de processamento em lote do Amazon SQS e dominar novas tentativas automáticas com o AWS Lambda Powertools para exemplos de código Python e AWS CDK.
Outros artigos da série:
Todos os exemplos de código CDK podem ser encontrados aqui .
Todo o código Lambda pode ser encontrado aqui .
Índice
SQS Re:cap
No primeiro artigo, em minhas melhores práticas de SQS, definimos o padrão de função SQS para Lambda. Implementamos a infraestrutura AWS CDK e os códigos do manipulador de função Lambda.
No entanto, não focamos em falhas de processamento. Isso está prestes a mudar.
Observe que este artigo se baseia nos exemplos de código do primeiro artigo.
Melhores práticas de repetição do SQS
Vamos revisar as práticas recomendadas de novas tentativas do SQS e o fluxo de processamento em lote.
O SQS aciona nossa função Lambda com um evento SQS contendo itens de lote.
A função Lambda iterará cada item, processará e continuará para o próximo item.
Entretanto, o que acontece se um item falhar no processamento devido a problemas momentâneos de rede, bugs de serviços externos ou outros problemas aleatórios, e como podemos nos recuperar?
Então, vamos rever possíveis métodos para lidar com falhas e, ao mesmo tempo, permanecer resiliente e autocurativo.
Existem três opções para lidar com falhas:
Execute uma nova tentativa durante o tempo de execução da função Lambda, ou seja, use a biblioteca tenacity (ou qualquer outra biblioteca de nova tentativa) para decorar qualquer função lógica interna com nova tentativa automática. No entanto, como com o SQS lidamos com lotes de registros, e não apenas um, precisamos estar cientes do tempo de execução total da função e deixar tempo suficiente para o restante do lote e suas possíveis novas tentativas, o que pode ser um pouco complicado, resultando em um potencial tempo limite da função.
Use o suporte integrado do SQS para retry . Uma retry automática pode ser acionada em dois casos de uso:
Uma função gera uma exceção no final de sua execução, retornando assim o lote inteiro para a fila.
Uma função marca as mensagens parcialmente falhadas.
Uma abordagem híbrida - use as opções 1 e 2 juntas, que implementaremos como o método preferencial neste post.
Vamos colocar esses métodos em um diagrama:
(1) O SQS aciona uma função com um lote. A função Lambda itera sobre o lote e manipula cada item do lote e tenta novamente seu processamento (1a no diagrama, se necessário) com 'tenacity'.
(2) A função marca para o SQS quais itens falharam no processamento (após tentativas de repetição com falha) ou termina com uma exceção se o lote inteiro falhou. Se todos os itens forem processados com sucesso, a função marca que zero itens falharam no processamento.
(3) Se o Lambda marcou itens como falhados no processamento, o SQS invocará a função novamente com um lote contendo apenas os itens com falha assim que o tempo limite de visibilidade do SQS tiver passado.
Tenha em mente que, quando novos itens forem adicionados ao SQS, os itens do lote com falha farão parte do último lote que a função recebe, mas tenha em mente que as mensagens têm tempo de retenção para que não permaneçam para sempre na fila.
Às vezes você não deve tentar novamente
Esteja ciente de que erros como validação ou verificação de entrada inválida (em termos de segurança) não devem ser repetidos, pois eles falharão repetidamente.
No entanto, problemas momentâneos de rede, bugs e tempo de inatividade do serviço são dinâmicos por natureza, e uma estratégia de nova tentativa pode resolver o problema de processamento.
Agora que entendemos a teoria, vamos escrever o código da função Lambda aprimorado com a abordagem híbrida.
Manipulador Lambda
Usaremos o utilitário de lote do AWS Lambda Powertools em combinação com o tenacity para novas tentativas e manuseio seguro. Processaremos cada item no lote e, caso uma exceção seja gerada, tentaremos novamente seu processamento automaticamente. No entanto, se essas tentativas de nova tentativa também falharem, usaremos o recurso do utilitário de lote de marcar o item de lote específico como uma falha parcial para que ele seja retornado ao SQS.
O SQS tentará novamente aquele item específico e invocará a função Lambda novamente. Em muitos casos, essas múltiplas tentativas de repetição podem ser o suficiente; no entanto, na próxima parte dois deste post, veremos como lidar com falhas de repetição com filas de letras mortas.
Vamos dar uma olhada no manipulador que definimos no primeiro artigo.
Na linha 12, inicializamos o processador de lote e colocamos a classe de esquema Pydantic que definimos e que é um lote SQS. O processador de lote suporta outros tipos de lote , como fluxos de dados Kinesis e fluxos DynamoDB.
Nas linhas 18-23, enviamos o evento para a função start do utilitário de lote do Powertools. Fornecemos a ele o processador, o evento e nosso manipulador de itens de lote. O manipulador de itens é nossa função lógica interna que manipulará cada item no lote.
Observe que mencionei que deveria ser uma função na camada lógica e, caso você queira aprender mais sobre como estruturar seu Lambda em camadas (manipulador, lógica e acesso a dados), não deixe de ler minha postagem sobre isso.
Todo o código Lambda pode ser encontrado aqui .
Implementação record_handler
Vamos dar uma olhada na implementação da função lógica interna de processamento de itens.
No primeiro artigo, parecia assim:
Agora, queremos adicionar 'tenacidade' para tentar novamente itens com falha durante o tempo de execução do Lambda.
Na linha 9, definimos os casos de uso para disparar uma nova tentativa automática. Neste caso, defini no máximo duas novas tentativas e esperar 2^x * 1 segundo entre cada nova tentativa, começando com 4 segundos e depois até 10 segundos. É muito tempo para tentar novamente, mas é um exemplo. 'Tenacity' fornece muitas condições de nova tentativa, incluindo ignorar tipos específicos de exceções e outros casos de uso avançados. Leia mais sobre isso aqui .
Nas linhas 10-11, definimos a lógica de processamento do item principal. Esta função deve ser segura para nova tentativa, ou seja, é idempotente. Se não for o caso, você deve certificar-se de que é. Se quiser aprender sobre idempotência e como tornar o código do seu Lambda idempotente, confira meu post de blog aqui .
Na linha 15, registramos o item do pedido que recebemos.
Na linha 17, chamamos a função lógica interna que processará a função e tentará novamente automaticamente (graças à 'tenacidade') caso exceções sejam geradas.
Nas linhas 18-20, capturamos um RetryError que 'tenacity' gera quando todas as tentativas de nova tentativa falharam. Capturamos a exceção para efetuar login e adicionar quaisquer metadados para medidas de depuração. Em seguida, o geramos novamente para marcar o utilitário de lote do Powertools que este é um item com falha.
Mecânica de Falha Parcial
O utilitário de lote itera nos itens de lote e chama o 'record_handler' para manipular cada item. Uma falha nessa função está relacionada a uma exceção não capturada sendo levantada. O utilitário de lote do Powertools irá capturá-la e marcar o item como falhado. Se o 'record_handler' não levantar uma exceção, o item será considerado processado com sucesso.
Há três valores de retorno potenciais para o manipulador Lambda de acordo com a documentação do Powertools:
Todos os registros foram processados com sucesso . Retornaremos uma lista vazia de falhas de itens {'batchItemFailures': []}. Todos os itens foram processados com sucesso, nenhum item retorna ao SQS.
Sucesso parcial com algumas exceções . Retornaremos uma lista de todos os IDs de itens/números de sequência que falharam no processamento. Apenas os itens com falha retornam ao SQS.
Todos os registros falharam ao serem processados . Levantaremos uma exceção BatchProcessingError com uma lista de todas as exceções levantadas durante o processamento e todos os itens serão retornados ao SQS.
Considerações Finais
O processamento em lote é um dos casos de uso mais básicos de qualquer aplicativo sem servidor.
É fácil errar. Há muitas ressalvas; por exemplo, você adiciona 'tenacity' mas esquece de estender o tempo limite geral da função, potencialmente atingindo um tempo limite.
Você pode limitar o tamanho do lote, calcular o tempo potencial por item, supondo que todos os itens falharão após a tentativa máxima de repetição, e definir o tempo limite adequadamente.
Além disso, você precisa considerar casos de uso em que as tentativas extras não funcionam. Nesse caso, você precisa usar uma fila de letras mortas. A próxima postagem do blog abordará falhas e práticas recomendadas de filas de letras mortas.