Utilizando Amazon SQS com suporte a DLQ

Fabio Henrique
Troopers-Legacy
Published in
12 min readOct 2, 2023

Faaaala galera, como vocês estão? Tudo certo e naaaaada resolvido? Espero que sim ! Rsrsrs

O papo hoje é sobre Dead-letter queues (DLQ). Considerado um suporte a Amazon simple queue service (Amazon SQS) é uma funcionalidade poderosa que podemos tirar proveito desse recurso.

Peço que antes desta leitura, dê uma olhadinha no que são os recursos Amazon SQS, Lambda Function e Lambda Triggers para que possam ter bom entendimento do que será dito aqui. #Let’s Booooora!

Aplicabilidade da Amazon SQS

No dia-a-dia do projeto, temos utilizado vários recursos da cloud, tais como: EC2, DynamoDB, Redis, Lambda Function, Amazon SNS, Amazon SQS e outros … Também, sabemos que é possível fazer uso desses recursos unificados, para que um se comunique com o outro, assim como: DynamoDB com trigger para Lambda Function, SQS com trigger para Lambda Function, SNS enviando mensagem para SQS, entre outros.

Há vários Recursos AWS que suportam DLQ, no entanto, para nosso material, vamos discutir apenas sobre o recurso Amazon SQS com suporte a DLQ.

É sabido que a Amazon SQS’s são filas capazes de gerenciar mensagens que podem ser utilizadas por aplicações ou Recursos AWS para serem processadas. Portanto, é apropriado saber controlar o processamento destas mensagens. O recurso Amazon SQS permite que possamos consumir mensagens de uma fila de duas formas:

  • SQS sendo consumida por Aplicações(Batch, Worker, API-Server) obtendo as mensagens. — Modo ATIVO [imagem AWS BATCH — aplicação consumindo mensagens SQS]
  • SQS enviando mensagens para Lambda Function (Lambda Triggers). — Modo ReAtivo [imagem AWS Lambda Function — SQS enviando mensagens para um recurso com funcionalidade trigger ]

A seguir, vamos falar um pouco sobre ambas possibilidades.

Atenção: Os termos ATIVO e ReATIVO que serão utilizados em breve no material servem para descrever o comportamento de consumo de mensagens em Amazon SQS. Até a data da escrita deste material não foi encontrado nada na literatura(documentação oficial da AWS, artigos e periódicos — como fonte de conhecimento) que utilizem esses termos.

Modo ATIVO

Recurso AWS Batch consumindo mensagens SQS

Neste cenário as mensagens são consumidas por uma aplicação/Recurso AWS que tem a responsabilidade de controlar o processamento da mensagem. É importante mencionar que neste modelo as mensagens que são processadas devem ser removidas, caso contrário as mesmas serão mantidas na Amazon SQS. Nas situações em que a mensagem não é processada(condições de negócio ou problemas técnicos) o consumidor(aplicação/Recurso AWS) deve “soltar” a mensagem … isso fará com que a Amazon SQS entenda que houve tentativa de processamento desta mensagem. Se houver sucesso no processamento da mensagem a mesma deverá ser removida da SQS.

O termo “soltar” está relacionado diretamente ao comportamento da visibilidade de uma mensagem recebida por um consumidor(aplicação/Recurso AWS). Ao criar uma Amazon SQS pode-se configurar a opção “Visibility timeout — Tempo limite de visibilidade” para que seja possível estipular um tempo máximo do processamento da mensagem, em relação ao consumidor. Na imagem [Console Amazon SQS — configurações], podemos ver a configuração mencionada.

Console Amazon SQS — configurações

*** Perigo: Nesta configuração podemos pensar que o tempo recomendado para adicionar nesta configuração é o tempo máximo que será gasto para o consumidor processar a mensagem!

Segundo a Documentação Oficial,

The visibility timeout begins when Amazon SQS returns a message. If the consumer fails to process and delete the message before the visibility timeout expires, the message becomes visible to other consumers. If a message must be received only once, your consumer must delete it within the duration of the visibility timeout.(Amazon SQS)

Na tradução adaptada … “O tempo limite de visibilidade inicia quando o Amazon SQS retorna uma mensagem. Se o consumidor não processar e excluir a mensagem antes que o tempo limite de visibilidade expire, a mensagem se tornará visível para outros consumidores. Se uma mensagem deve ser recebida apenas uma vez, seu consumidor deverá excluí-la dentro do tempo limite de visibilidade.” Fundamentando-se nisso, na situação em que o tempo de processamento de uma mensagem no consumidor é maior que o tempo limite de visibilidade definido na Amazon SQS, We have a possible problem !!!

Portante, é importante levar em consideração essa configuração no momento em que está sendo criado a Amazon SQS, para que as mensagens não caiam em concorrência e possam ser processadas mais de uma vez (por consumidores distribuídos na cloud)

Abaixo, o diagrama mostra a relação entre filas de atraso e tempos limite de visibilidade.

Diagrama do tempo de visibilidade de mensagem. Fonte: Documentação Oficial — Developer Guide

O termo “Modo ATIVO” utilizado anteriormente faz referência a como o consumidor obtém a mensagem, indo ao encontro da fonte(Recurso Amazon SQS). A ideia é insinuar que o consumidor tem um papel ATIVO com relação à obtenção das mensagens.

Modo ReATIVO

SQS enviando mensagens para Recurso AWS Lambda Function com Lambda Trigger

Ao contrário do cenário anterior, as mensagens são enviadas para um Recurso AWS. Neste modo as mensagens que são processadas pelo recurso não precisam ser removidas. O papel do gerenciamento é feito pelo próprio Recurso(configurações padrões). No entanto, para que seja possível gerenciar mensagens em lote, precisamos configurar o recurso a fim de que interprete a resposta da aplicação — Resposta parcial (acoplada dentro de um Recurso — Lambda Function).

Um ponto interessante nas características da Amazon SQS é que o próprio recurso controla a tentativa de processamento de mensagens através de um atributo “ApproximateReceiveCount(Console AWS apresenta-se como Receive count — número de vezes que uma mensagem foi recebida mas não foi excluída) que pode ser encontrado nas propriedades de uma mensagem (imagem a seguir). É relevante dizer que a aba “Attributes” diz respeito aos atributos a nível de metadados que podem ser inseridos ao enviar uma mensagem para uma SQS enquanto aba “Details” tem relação ao atributo nativos do Recurso Amazon SQS, tal como ApproximateReceiveCount.

Saber trabalhar com esse controle de “count received” é uma das recomendações dadas na própria documentação. Isso fará com que possamos usar o máximo possível das funcionalidades fornecidas pelo Recurso Amazon SQS.

Similaridade no consumo de mensagens entre os Modos ATIVO e ReATIVO

Embora os modos ATIVO e ReATIVO estejam consumindo e manipulando mensagens em uma Amazon SQS, a forma de controle é razoavelmente diferente … mas o que ambas têm em comum é como deve ser feito o controle das mensagens processadas para que o Recurso Amazon SQS saiba interpretar os retornos — se a mensagem foi processada com sucesso(deletar mensagem) ou falhou(manter mensagem). Na sequência vamos dar um exemplo de sucesso e falha de processamento de mensagem.

Certifiquemos-nos que o objetivo da fila SQS_ASYNC_REQUEST tem a atribuição de receber mensagens para que uma aplicação/Recurso — em um determinado momento — consuma as mensagens e monte uma requisição HTTP para uma determinada URL. ***Destaco que a requisição será montada a partir dos dados que virão do body da mensagens no momento da leitura de cada mensagem da fila***

Desenho do modelo SQS sendo consumida por um Batch

Seguindo, foram postadas 5 mensagens na fila SQS_ASYNC_REQUEST. A ideia é que cada mensagem será uma requisição HTTP para uma API, logo, quem consumir a Amazon SQS deve executar essa tarefa. No momento em que a aplicação/Recurso estava consumindo a SQS, ao ler uma das mensagens executou uma requisição HTTP(com os dados oriundos do body da mensagem SQS) e obteve Response Status Code 500 (🤔). Por algum motivo … essa requisição consumiu um serviço que pode estar com algum problema de infraestrutura-estrutura.

Agora surge uma pergunta: Levando em consideração que a aplicação leu a mensagem e fez a requisição(conforme regra de negócio) a mensagem(SQS) foi processada com sucesso ?

R: DEPENDE!

Podemos considerar que sim, já que a mensagem foi lida com sucesso … e até processada com sucesso também. A grande questão aqui é o que devemos considerar em nossa regra de negócio. Se considerarmos que ao fazermos uma requisição e obter Response Status Code entre [200–299] podemos avaliar como uma falha no processamento da mensagem(SQS) — embora a aplicação tenha feito a requisição e o serviço ao qual está sendo consumido retornou um erro(Response Status Code 500).

É importante entender o que sua regra de negócio determina o sucesso/falha no processamento da mensagem(SQS). Com essas diretrizes sabemos definir algumas ações a serem tomadas com os casos de mensagens que não foram possíveis processar. É aqui que de fato entramos no assunto principal: Suporte DQL.

Suporte a Dead-latter Queue — DLQ

A Amazon SQS oferece suporte a Dead-letter Queue — DQL ou Fila de mensagem morta cujo objetivo é receber mensagens que não foram processadas por suas filas de origem. Apesar de ser chamada de Fila de mensagem morta, é considerada um SQS “normal’’. A diferença é que a Fila de mensagem morta será utilizada pela fila de origem que deve configurar o suporte a DQL.

Configurações

É necessário definir as informações de Fila de mensagem morta na SQS de origem, a qual deseja implementar o controle de mensagens não processadas. Neste momento podemos indicar a quantidade de vezes que a mensagem foi recebida através do atributo “maxReceiveCount”(Console AWS apresenta-se como Maximum receives) antes de enviar para Fila de mensagem morta.

SQS_ASYNC_REQUEST direcionando mensagens mortas para fila SQS_ASYNC_REQUEST_DLQ

Consumindo Amazon SQS’s com suporte a DLQ

Agora, sabendo definir as configurações no Recurso Amazon SQS, é hora de codar !!

Faremos dois exemplos seguindo os modos ATIVO e ReATIVO conforme explanado anteriormente. Em ambos os casos faremos uso da linguagem de programação Java para implementar as soluções.

Para nossos exemplos, vamos considerar que qualquer tipo de erro(a nível de Exception customizada, RuntimeException ou Exception) que houver no código, será tomado/classificado como mensagem não processada.

Como dependências para essas soluções, usaremos as bibliotecas oficiais dos recursos AWS.

Consumidor ATIVO

Dependências maven:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.20.45</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
...
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sqs</artifactId>
</dependency>
</dependencies>

Segue abaixo um exemplo para obter mensagens de uma determinada fila.

Obs: para esse exemplo, poderíamos usar vários outros tipos de aplicação, tais como: Batch, Workers ou simplesmente uma aplicação embutida em um servidor de aplicação (ex: aplicações com Spring Boot) consumindo uma SQS. Utilizaremos a abordagem de @Scheduled visto que é uma maneira simples e curta de implementar o exemplo.

Para explanar sobre o código acima, vamos focar apenas no método readMessageSimpleQueueService() cujo será executado de acordo com as definições configuradas (Time inicial = 10 segundos, Tempo fixo estimado para cada execução = 60 segundos) na linha 11. Nas linhas [13–17] são declaradas variáveis para inserir configurações como: região onde se encontra a SQS, nome da SQS, instância de um objeto SqsClient(para conectar-se com recurso AWS SQS — credenciais já definidas).
Vamos à parte mais importante do código da classe MainTesk.Java. Como havia mencionado, o exemplo refere-se ao modo ATIVO, logo, o mecanismo é que a aplicação vá buscar as mensagens na fila, linha 22. Em seguida, entre as linhas [23–33] ocorre “a mágica” do processamento da mensagem. Colocou-se alguns “//TODO” para que pudesse deixar claro a ideia do que poder ser implementado para ser considerado processado com sucesso(linha 24–30) e falha(linha 32). Dada a implementação do “//TODO: Regra de negócio”, se não houver erro a nível de Exception, a mensagem é tida como processada com sucesso e é removida da fila (linha [26–30). Se houver algum tipo de erro(Exception) na rotina(código implementado no //TODO: regra de negócio) o fluxo da execução do código segue para linha 32 a qual é posto um “TODO: Tratativa da Regra de negócio”. Neste //TODO temos um ponto para destacar: É sabido que existiu um erro no processamento da mensagem, por conseguinte, não podemos removê-la da fila. Considere proposital no catch não ter nenhuma ação a ser feita, pois, como o próprio comentário do //TODO já diz, deve-se “soltar” a mensagem. No começo do material falamos sobre a ação de “soltar”, para que a mensagem seja devolvida para a fila e contabilize a tentativa de processamento. Uma sugestão para implementação desse //TODO seria alguma tratativa de negócio, como um log de aplicação (para que seja possível visualizar o erro em questão) por exemplo.

É interessante reforçar que o exemplo está considerando falha no processamento da mensagem na ocasião em que há algum tipo de erro(Exception customizada, RuntimeException ou Exception), entretanto, é possível desenvolver um código em que é possível lidar com uma rotina que direcione para um fluxo de erro(catch) lançando exceção, segue exemplo:

Em um cenário perfeito onde a mensagem é processada com sucesso, é importante remover a mensagem da fila para que ela não seja devolvida e posteriormente seja processada novamente (chamada do método para remover mensagem da Amazon SQS).

Consumidor ReATIVO

Para este exemplo será empregado os recursos Lambda Function e Amazon SQS com suporte a trigger. Um comportamento que pode ser definido na trigger é a quantidade de mensagens que podem ser enviadas ao consumidor. De acordo com a necessidade do negócio, essas definições variam. Se a demanda é de enviar uma mensagem por vez ou em lote, cabe ao negócio definir tal comportamento.

Como dependências para essas soluções, usaremos as bibliotecas oficiais dos recursos AWS. Segue dependências maven:

<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
<version>3.11.3</version>
</dependency>

Com relação ao recurso Lambda Function, as configurações de Execução (Runtime Settings) estão setadas para chamar o método handleRequest da classe MainLambdaFunctionMessage nas invocações do lambda. Os detalhes da invocação e parâmetro (Context context) do método não é o foco deste material, no entanto, é interessante conhecer as propriedades e comportamento deste objeto (AWS Lambda context object in Java).

[Imagem — Configurações de Execução. Aba Code, Runtime Settings]

Sobre as Triggers setadas na lambda, conforme explicado no [Modo ReATIVO] existem algumas configurações que deve-se atentar no gerenciamento dos processamentos das mensagens. Por padrão a Trigger é configurada para não ter suporte a falha de mensagens não processadas em lote(configuração desabilitada). Essa configuração pode ser notada na imagem a seguir:

Vamos ao código!

Os pontos mais importantes a serem considerados no código acima são:

  • ClasseSQSBatchResponse (Linha 9)
  • Declaração de Lista do tipo SQSBatchResponse.BatchItemFailure (Linha 12)
  • //TODO no catch (Linhas [16–19])
  • “return” List do tipo SQSBatchResponse (Linha 21)

Para que possamos fazer os recursos SQS e Lambda se “comunicarem” e dizer se a mensagem foi processada ou não, é preciso implementar o retorno parcial e para isso utilizamos a classe SQSBatchResponse. Através deste classe, junto com as configurações de retorno de falha de mensagens, é possível comunicar para SQS quais mensagens NÃO foram processadas pelo Lambda.

Na linha 12 é declarado uma Lista do tipo SQSBatchResponse.BatchItemFailure cujo faz referência às mensagens que não foram processadas, logo, o conceito aqui é “alimentar” a lista com as mensagens que não foram processadas. Para reforçar, no que diz respeito à regra do que é uma mensagem processada ou não processada, seguiremos o mesmo princípio do exemplo anterior.

Entre as linhas [16–19] é feita a tratativa do processo propriamente dito da mensagem. Especificamente na linha 18 é realizado a adição da mensagem que “supostamente” não foi processada. Um ponto de atenção aqui é sobre o identificador message.getMessageId() da mensagem que deve estar na lista de mensagens não processadas.

Ao fim de todo processamento das mensagens, na linha 21 retorna uma instância da classe SQSBatchResponse com a lista de mensagens que não foram processadas.

Para ilustrar um exemplo de “retorno” de um Lambda Function em casos de sucesso e falha(mensagem repassada/devolvida para SQS), utilizaremos o “Test” que é disponibilizado pelo recurso Lambda Function. Essa funcionalidade se encontra na aba “Test” no console da Aws.

Sucesso:

Falha:

Obs: Quando é mencionado sobre “falha” no processamento da mensagem, refere-se a resposta parcial da Lambda Function e NÃO a execução/invocação da Lambda Function.

Retorno parcial

A implementação do retorno parcial é relativamente simples e a nomenclatura das classes ajuda bastante. Mas … é importante lembrar que a implementação do retorno parcial é opcional, pois depende da necessidade. Em nosso cenário, precisamos fazer com que a SQS saiba que as mensagens não foram processadas para que posteriormente sejam enviadas para uma DLQ. Em aplicações lambda é muito comum encontrar implementações que não utilizam retorno parcial por não haver criticidade na falha do processamento da mensagem. Segue exemplo de implementação trivial:

Conclusão

Suporte a DLQ em Amazon SQS é um recurso extremamente poderoso para gerenciar tratativas de falhas em processamento de mensagens. Mostramos como são feitas as configurações e as maneiras de como os consumidores(Aplicações ou Recursos AWS) se comunicam com a Amazon SQS — como interpretar as tentativas de processamento de mensagem.

Leituras recomendadas

https://aws.amazon.com/pt/what-is/dead-letter-queue/

https://aws.amazon.com/pt/about-aws/whats-new/2021/11/aws-lambda-partial-batch-response-sqs-event-source/

Referências

https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/Welcome.html[Documentação Oficial]

https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html [Guia de Desenvolvimento]

--

--

Fabio Henrique
Troopers-Legacy

Software Engineer Java | Java 8, 11 & 17 Certified Developer.