O blog da AWS
Executando código após retornar uma resposta de uma função do AWS Lambda
Este post foi escrito por Uri Segev, principal especialista em Serverless Specialist SA.
Ao invocar uma função do AWS Lambda de forma síncrona, você espera que a função retorne uma resposta. Por exemplo, esse é o caso quando um cliente invoca uma função do Lambda por meio do Amazon API Gateway ou do AWS Step Functions. Como o cliente está aguardando a resposta, você deve devolvê-la o mais rápido possível.
No entanto, pode haver casos em que você precise realizar um trabalho adicional que não afete a resposta e possa fazê-lo de forma assíncrona, depois de enviar a resposta. Por exemplo, você pode armazenar dados em um banco de dados ou enviar informações para um sistema de logs.
Depois de enviar a resposta da função, o serviço Lambda congela o ambiente de execução e a função não pode executar código adicional. Mesmo que você crie um encadeamento para executar uma tarefa em segundo plano, o serviço Lambda congela o ambiente de execução quando o manipulador (handler) retorna, fazendo com que o encadeamento congele até a próxima invocação. Embora você possa adiar o retorno da resposta ao cliente até que todo o trabalho seja concluído, essa abordagem pode impactar negativamente a experiência do usuário.
Este blog explora maneiras de executar uma tarefa que pode começar antes do retorno da função, mas continua em execução depois que a função retorna a resposta ao cliente.
Invocando uma função Lambda assíncrona
A primeira opção é dividir o código em duas funções. A primeira função executa o código síncrono; a segunda função executa o código assíncrono. Antes que a função síncrona retorne, ela invoca a segunda função de forma assíncrona, diretamente, usando a API Invoke, ou indiretamente, por exemplo, enviando uma mensagem ao Amazon SQS para acionar a segunda função.
Esse código em Python demonstra como implementar isso:
Use o streaming de resposta do Lambda
O streaming de resposta permite que os desenvolvedores comecem a transmitir a resposta assim que tiverem o primeiro byte da resposta, sem esperar pela resposta inteira. Normalmente, você usa streaming de resposta quando precisa minimizar o tempo até o primeiro byte (TTFB) ou quando precisa enviar uma resposta maior que 6 MB (o limite de tamanho da carga útil da resposta do Lambda).
Usando esse método, a função pode enviar a resposta usando o mecanismo de streaming de resposta e pode continuar executando o código mesmo depois de enviar o último byte da resposta. Dessa forma, o cliente recebe a resposta e a função Lambda pode continuar em execução.
Esse código Node.js demonstra como implementar isso:
Use extensões Lambda
As extensões do Lambda podem aumentar as funções do Lambda para integrá-las às suas ferramentas preferidas de monitoramento, observabilidade, segurança e governança. Você também pode usar uma extensão para executar seu próprio código em segundo plano para que ele continue em execução depois que sua função retornar a resposta ao cliente.
Há dois tipos de extensões do Lambda: extensões externas e extensões internas. As extensões externas são executadas como processos separados no mesmo ambiente de execução. A função Lambda pode se comunicar com a extensão usando arquivos na pasta /tmp ou usando uma rede local, por exemplo, por meio de solicitações HTTP. Você deve empacotar extensões externas como uma camada Lambda.
As extensões internas são executadas como encadeamentos separados no mesmo processo que executa o manipulador. O manipulador pode se comunicar com a extensão usando qualquer mecanismo em processo, como filas internas. Este exemplo mostra uma extensão interna, que é uma linha de execução dedicada dentro do processo do manipulador.
Quando o serviço Lambda invoca uma função, ele também notifica todas as extensões da invocação. O serviço Lambda só congela o ambiente de execução quando a função Lambda retorna uma resposta e todas as extensões sinalizam ao runtime que estão concluídas. Com essa abordagem, a função faz com que a extensão execute a tarefa independentemente da própria função e a extensão notifica o runtime do Lambda quando termina de processar a tarefa. Dessa forma, o ambiente de execução permanece ativo até que a tarefa seja concluída.
O exemplo de código Python a seguir isola o código da extensão em seu próprio arquivo e o manipulador o importa e o usa para executar a tarefa em segundo plano:
O código Python a seguir demonstra como implementar a extensão que executa a tarefa em segundo plano:
Use um runtime personalizado
O Lambda oferece suporte a vários runtimes prontos para uso: Python, Node.js, Java, Dotnet e Ruby. O Lambda também oferece suporte a runtimes personalizados, o que permite desenvolver funções do Lambda em qualquer outra linguagem de programação necessária.
Quando você invoca uma função do Lambda que usa um runtime personalizado, o serviço Lambda invoca um processo chamado ‘bootstrap’ que contém seu código personalizado. O código personalizado precisa interagir com a API Lambda Runtime. Ele chama o endpoint /next para obter informações sobre a próxima invocação. Essa chamada de API está bloqueando e espera até que uma solicitação chegue. Quando a função terminar de processar a solicitação, ela deve chamar o endpoint /response para enviar a resposta de volta ao cliente e, em seguida, deve chamar o endpoint /next novamente para aguardar a próxima invocação. O Lambda congela o ambiente de execução depois que você chama /next, até que uma solicitação chegue.
Usando essa abordagem, você pode executar a tarefa assíncrona depois de chamar /response e enviar a resposta de volta ao cliente e antes de chamar /next, indicando que o processamento foi concluído.
O exemplo de código Python a seguir isola o código de runtime personalizado em seu próprio arquivo e a função o importa e o usa para interagir com a API de runtime:
Esse código Python demonstra como interagir com a API de runtime:
Conclusão
Este blog mostra quatro maneiras de combinar tarefas síncronas e assíncronas em uma função Lambda, permitindo que você execute tarefas que continuam em execução após a função retornar uma resposta ao cliente. A tabela a seguir resume os prós e os contras de cada solução:
URLs de função, não podem ser usados com o API Gateway, sempre públicos
Invocação assíncrona | Streaming de resposta | Extensões Lambda | Runtime personalizado | |
Complexidade | Mais fácil de implementar | Mais fácil de implementar | A solução mais complexa de implementar, pois requer interação com a API de extensões e um thread dedicado | Médio, pois interage com a API de runtime |
Implantação | Precisa de dois artefatos: a função síncrona e a função assíncrona | Um único artefato de implantação que contém todo o código | Um único artefato de implantação que contém todo o código | Um único artefato de implantação requer o empacotamento de todos os arquivos de runtime necessários |
Custo | Mais caro, pois incorre em um custo adicional de invocação, e a duração geral de ambas as funções é maior do que tê-la em uma. | Menos caro | Menos caro | Menos caro |
Iniciando a tarefa assíncrona | Antes de retornar do manipulador | A qualquer momento durante a invocação do manipulador | A qualquer momento durante a invocação do manipulador | Depois de retornar a resposta ao cliente, a menos que você use um tópico dedicado |
Limitações | A carga enviada para a função assíncrona não pode exceder 256 KB | Compatível apenas com Node.js e runtimes personalizados. Requer URLs da função Lambda, não pode ser usado com o API Gateway, sempre público | — | — |
Benefícios adicionais | Melhor dissociação entre código síncrono e assíncrono | Capacidade de enviar respostas em etapas. Suporta cargas maiores que 6 MB (com custo adicional) | A tarefa assíncrona é executada em seu próprio thread, o que pode reduzir a duração e o custo gerais | — |
Tenta novamente em caso de falha no código assíncrono | Gerenciado pelo serviço Lambda | Responsabilidade do desenvolvedor | Responsabilidade do desenvolvedor | Responsabilidade do desenvolvedor |
A escolha da abordagem correta depende do seu caso de uso. Se você escrever sua função em Node.js e invocá-la usando URLs de funções Lambda, use streaming de resposta. Essa é a maneira mais fácil de implementar e a mais econômica.
Se houver uma chance de falha na tarefa assíncrona (por exemplo, um banco de dados não estiver acessível) e você precisar garantir que a tarefa seja concluída, use o método de invocação assíncrona do Lambda. O serviço Lambda tenta novamente sua função assíncrona até que ela seja bem-sucedida. Eventualmente, se todas as novas tentativas falharem, ele invocará um destino do Lambda para que você possa agir.
Se você precisar de um runtime personalizado porque precisa usar uma linguagem de programação à qual o Lambda não oferece suporte nativo, use a opção de runtime personalizado. Caso contrário, use a opção de extensões Lambda. É mais complexo de implementar, mas é econômico. Isso permite empacotar o código em um único artefato e começar a processar a tarefa assíncrona antes de enviar a resposta ao cliente.
Para obter mais recursos de aprendizado sem servidor, visite Serverless Land.
Este blog é uma tradução do conteúdo original em inglês (link aqui).
Biografia do Autor
Uri Segev é um especialista principal em Serverless Specialist SA. |
Biografia do tradutor
Daniel Abib é arquiteto de soluções sênior na AWS, com mais de 25 anos trabalhando com gerenciamento de projetos, arquiteturas de soluções escaláveis, desenvolvimento de sistemas e CI/CD, microsserviços, arquitetura Serverless & Containers e segurança. Ele trabalha apoiando clientes corporativos, ajudando-os em sua jornada para a nuvem. |
Biografia do Revisor
Rodrigo Peres é arquiteto de soluções na AWS, com mais de 20 anos de experiência trabalhando com arquitetura de soluções, desenvolvimento de sistemas e modernização de sistemas legados. |