Práticas recomendadas para a reutilização do AWS Lambda Container

Otimizando o Warm Starts ao conectar o AWS Lambda a outros serviços

O AWS Lambda oferece alta escalabilidade devido à ausência de servidor e sem estado, permitindo que muitas cópias da função lambda sejam geradas instantaneamente (conforme descrito aqui). No entanto, ao escrever o código do aplicativo, é provável que você queira acessar alguns dados com estado. Isso significa conectar-se a um armazenamento de dados, como uma instância do RDS ou S3. No entanto, a conexão com outros serviços do AWS Lambda adiciona tempo ao seu código de função. Também pode haver efeitos colaterais da alta escalabilidade, como atingir o número máximo de conexões permitidas para uma instância do RDS. Uma opção para combater isso é usar a reutilização de contêiner no AWS Lambda para manter a conexão e reduzir o tempo de execução do lambda.

Existem alguns diagramas úteis aqui para explicar o ciclo de vida de uma solicitação lambda.

O seguinte ocorre durante uma partida a frio, quando sua função é chamada pela primeira vez ou após um período de inatividade:

  • O código e as dependências são baixados.
  • Um novo contêiner é iniciado.
  • O tempo de execução é inicializado.

A ação final é iniciar seu código, o que acontece sempre que a função lambda é chamada. Se o contêiner for reutilizado para uma chamada subseqüente da função lambda, podemos avançar para iniciar o código. Isso é chamado de início quente e é a etapa que podemos otimizar ao conectar-se a outros serviços, definindo a conexão fora do escopo do método manipulador.

Conectando-se a outros serviços da AWS a partir do Lambda

Exemplo: conectar-se à instância do RDS, ícones da AWS originados aqui

Temos um exemplo básico e comum para executar - queremos nos conectar a um recurso de contêiner para buscar dados de enriquecimento. Neste exemplo, uma carga útil JSON é fornecida com um ID e a Função Lambda se conecta a uma instância do RDS executando o PostgreSQL para encontrar o nome correspondente da identificação, para que possamos retornar a carga útil enriquecida. Como a função lambda está se conectando ao RDS, que vive em uma VPC, a função lambda agora também precisa viver em uma sub-rede privada. Isso adiciona algumas etapas ao cold start - uma interface de rede elástica (ENI) da VPC precisa ser anexada (como mencionado no blog de Jeremy Daly, isso adiciona tempo aos seus cold start).

Nota: poderíamos evitar o uso de uma VPC se usássemos um armazenamento de chave / valor com o DynamoDB em vez do RDS.

Vou abordar duas soluções para esta tarefa, a primeira é a minha solução 'ingênua', enquanto a segunda solução otimiza os tempos de início mais quentes, reutilizando a conexão para chamadas subseqüentes. Em seguida, compararemos o desempenho de cada solução.

Opção 1 - Conecte-se ao RDS dentro do manipulador

Este exemplo de código mostra como eu poderia abordar essa tarefa ingenuamente - a conexão com o banco de dados está no método manipulador. Há uma consulta de seleção simples para buscar o nome do ID antes de retornar a carga, que agora inclui o nome.

Vamos ver como essa opção é executada durante um pequeno teste com uma explosão de 2000 invocações com uma simultaneidade de 20. A duração mínima é de 18 ms, com média de 51ms e pouco mais de 1 segundo no máximo (a duração de partida a frio).

Duração Lambda

O gráfico abaixo mostra que há um número máximo de oito conexões com o banco de dados.

Número de conexões com o banco de dados RDS em uma janela de 5 minutos.

Opção 2 - Use uma conexão global

A segunda opção é definir a conexão como um global fora do método manipulador. Em seguida, dentro do manipulador, adicionamos uma verificação para ver se a conexão existe e só conectamos se não existir. Isso significa que a conexão é feita apenas uma vez por contêiner. Definir a conexão dessa maneira com a condicional em vigor significa que não precisamos fazer uma conexão se não for exigido pela lógica do código.

Como não estamos mais fechando a conexão com o banco de dados, a conexão permanece para uma chamada subsequente da função. A reutilização da conexão reduz significativamente as durações de inicialização a quente - a duração média é aproximadamente três vezes mais rápida e a mínima é de 1 ms em vez de 18 ms.

Durações Lambda

Conectar-se a uma instância do RDS é uma tarefa demorada e não ter que se conectar para todas as chamadas é benéfico para o desempenho. Ao conectar-se ao banco de dados para uma consulta simples ao banco de dados, atingimos uma contagem máxima de conexões de banco de dados de 20, que corresponde ao nível de simultaneidade (fizemos 20 invocações simultâneas x 100 vezes). Quando a explosão de invocações pára, as conexões se fecham gradualmente.

Agora que a AWS aumentou a permissão de duração lambda para 15 minutos, isso significa que as conexões com o banco de dados podem durar mais e você pode estar em risco de atingir o número máximo de conexões do RDS. As conexões máximas padrão podem ser substituídas nas configurações do grupo de parâmetros do RDS, embora o aumento do número máximo de conexões possa resultar em problemas com a alocação de memória. Instâncias menores podem ter um valor padrão max_connections menor que 100. Lembre-se desses limites e inclua apenas a lógica do aplicativo para conectar-se ao banco de dados quando necessário.

Usando uma conexão global para outras tarefas

Lambda Conectando ao S3

Uma tarefa comum que precisamos executar com o Lambda é acessar dados com estado do S3. O snippet de código abaixo é um modelo da Função Python Lambda fornecida pela AWS - para o qual você pode navegar efetuando login no console da AWS e clicando aqui. Você pode ver no código que o cliente S3 está totalmente definido fora do manipulador quando o contêiner é inicializado, enquanto que para o exemplo do RDS a conexão global foi definida dentro do manipulador. Ambas as abordagens definirão as variáveis ​​globais, permitindo que elas estejam disponíveis para chamadas subseqüentes.

snippet de código de modelo lambda s3-get-object https://console.aws.amazon.com/lambda/home?region=us-east-1#/create/new?bp=s3-get-object-python

Descriptografando variáveis ​​de ambiente

O console lambda oferece a opção de criptografar suas variáveis ​​de ambiente para obter segurança adicional. O seguinte snippet de código é um exemplo Java fornecido pela AWS de um script auxiliar para descriptografar variáveis ​​de ambiente de uma função Lambda. Você pode navegar para o snippet de código seguindo este tutorial (especificamente a etapa 6). Como DECRYPTED_KEY é definida como uma classe global, a função e a lógica decryptKey () são chamadas apenas uma vez por contêiner lambda. Portanto, veremos uma melhora significativa nas durações de início a quente.

https://console.aws.amazon.com/lambda/home?region=us-east-1#/functions e https://docs.aws.amazon.com/lambda/latest/dg/tutorial-env_console.html

Usando variáveis ​​globais em outras soluções FaaS

Essa abordagem não é isolada da AWS Lambda. O método de usar uma conexão global também pode ser aplicado às funções sem servidor de outros provedores de nuvem. A página de dicas e truques do Google Cloud Functions fornece uma boa explicação para variáveis ​​não preguiçosas (quando a variável é sempre inicializada fora do método manipulador) versus variáveis ​​preguiçosas (a variável global é definida apenas quando necessário) variáveis ​​globais.

Outras práticas recomendadas

Aqui estão algumas outras práticas recomendadas a serem lembradas.

Teste

O uso do FaaS facilita a arquitetura de microsserviços. E ter pequenas e discretas peças de funcionalidade anda de mãos dadas com testes de unidade eficazes. Para ajudar nos seus testes de unidade:

  • Lembre-se de excluir as dependências de teste do pacote lambda.
  • Separe a lógica do método manipulador, como faria com o método principal de um programa.

Dependências e tamanho do pacote

Reduzir o tamanho do pacote de implantação significa que o download do código será mais rápido na inicialização e, portanto, melhorará seus horários de inicialização a frio. Remova as bibliotecas não utilizadas e o código morto para reduzir o tamanho do arquivo ZIP da implantação. O AWS SDK é fornecido para tempos de execução do Python e JavaScript, portanto, não há necessidade de incluí-los no seu pacote de implantação.

Se o Node.js for o seu tempo de execução Lambda preferido, você poderá aplicar minificação e uglificação para reduzir o tamanho do seu código de função e minimizar o tamanho do seu pacote de implantação. Alguns, mas nem todos os aspectos da minificação e da uglificação podem ser aplicados a outros tempos de execução, por exemplo. você não pode remover o espaço em branco do código python, mas pode remover comentários e encurtar os nomes das variáveis.

Configurando a memória

Experimente encontrar a quantidade ideal de memória para a Função Lambda. Você paga pela alocação de memória; portanto, dobrar a memória significa que você deve pagar o dobro por milissegundo; mas a capacidade de computação aumenta com a memória alocada, podendo potencialmente diminuir o tempo de execução para menos da metade do que era. Já existem algumas ferramentas úteis para selecionar a configuração ideal de memória para você, como esta.

Concluir…

Uma coisa a considerar é se é necessário aplicar o método de reutilização de conexão. Se sua função lambda estiver sendo chamada apenas com pouca frequência, como uma vez por dia, você não se beneficiará da otimização para partidas quentes. Geralmente, há uma troca a ser feita entre otimização para desempenho versus legibilidade do seu código - o termo “uglificação” fala por si! Além disso, adicionar variáveis ​​globais ao seu código para reutilizar conexões com outros serviços pode tornar seu código mais difícil de rastrear. Duas perguntas vêm à mente:

  • Um novo membro da equipe entenderá seu código?
  • Você e sua equipe poderão depurar o código no futuro?

Mas é provável que você tenha escolhido o Lambda por sua escala e queira alto desempenho e baixos custos, portanto encontre o equilíbrio que atenda às necessidades da sua equipe.

Essas opiniões são do autor. Salvo indicação em contrário neste post, a Capital One não é afiliada, nem é endossada por nenhuma das empresas mencionadas. Todas as marcas comerciais e outras propriedades intelectuais usadas ou exibidas são de propriedade de seus respectivos proprietários. Este artigo é © 2019 Capital One.