Лучшие практики для повторного использования лямбда-контейнеров AWS

Оптимизация теплых запусков при подключении AWS Lambda к другим сервисам

AWS Lambda обеспечивает высокую масштабируемость благодаря отсутствию серверов и состояний, что позволяет мгновенно создавать множество копий лямбда-функции (как описано здесь). Однако при написании кода приложения вы, вероятно, захотите получить доступ к некоторым данным с состоянием. Это означает подключение к хранилищу данных, такому как экземпляр RDS или S3. Однако подключение к другим сервисам из AWS Lambda добавляет время к коду вашей функции. Также могут быть побочные эффекты от высокой масштабируемости, такие как достижение максимального количества разрешенных подключений к экземпляру RDS. Одним из вариантов противодействия этому является использование повторного использования контейнера в AWS Lambda для сохранения соединения и сокращения времени работы лямбды.

Здесь есть несколько полезных диаграмм, объясняющих жизненный цикл лямбда-запроса.

Следующее происходит во время холодного запуска, когда ваша функция вызывается в первый раз или после периода бездействия:

  • Код и зависимости загружены.
  • Новый контейнер запущен.
  • Среда выполнения загружается.

Последнее действие - запустить ваш код, что происходит каждый раз, когда вызывается лямбда-функция. Если контейнер используется повторно для последующего вызова лямбда-функции, мы можем перейти к запуску кода. Это называется «горячим стартом», и этот шаг мы можем оптимизировать при подключении к другим сервисам, определяя соединение вне области метода-обработчика.

Подключение к другим сервисам AWS от Lambda

Пример: подключение к экземпляру RDS, отсюда исходные иконки AWS

У нас есть простой и распространенный пример - мы хотим подключиться к ресурсу контейнера для получения данных обогащения. В этом примере приходит полезная нагрузка JSON с идентификатором, и функция Lambda подключается к экземпляру RDS, на котором работает PostgreSQL, чтобы найти соответствующее имя идентификатора, чтобы мы могли вернуть обогащенную полезную нагрузку. Поскольку лямбда-функция подключается к RDS, который находится в VPC, лямбда-функция теперь должна также находиться в частной подсети. Это добавляет пару шагов к холодному старту - необходимо подключить интерфейс эластичной сети VPC (ENI) (как упоминалось в блоге Джереми Дейли, это добавляет время вашим холодным стартам).

Примечание: мы могли бы избежать использования VPC, если бы мы использовали хранилище ключей / значений с DynamoDB вместо RDS.

Я рассмотрю два решения этой задачи, первое - мое «наивное» решение, тогда как второе решение оптимизирует время горячего старта, повторно используя соединение для последующих вызовов. Затем мы сравним производительность каждого решения.

Вариант 1 - подключение к RDS в обработчике

Этот пример кода показывает, как я могу наивно подойти к этой задаче - соединение с базой данных находится внутри метода-обработчика. Существует простой запрос на выборку для получения имени идентификатора перед возвратом полезной нагрузки, которая теперь включает имя.

Давайте посмотрим, как эта опция работает во время небольшого теста с серией вызовов 2000 с параллелизмом 20. Минимальная продолжительность составляет 18 мс со средним значением 51 мс и максимум чуть более 1 секунды (длительность холодного запуска).

Длительность лямбды

На приведенном ниже графике показано, что существует максимум восемь соединений с базой данных.

Число подключений к базе данных RDS в 5-минутном окне.

Вариант 2 - использовать глобальное соединение

Второй вариант - определить соединение как глобальное вне метода-обработчика. Затем внутри обработчика мы добавляем проверку, чтобы увидеть, существует ли соединение, и подключаемся, только если его нет. Это означает, что соединение выполняется только один раз для каждого контейнера. Установка соединения таким образом с условным условием означает, что нам не нужно устанавливать соединение, если этого не требует логика кода.

Мы больше не закрываем соединение с базой данных, поэтому соединение остается для последующего вызова функции. Повторное использование соединения значительно сокращает длительность горячего запуска - средняя продолжительность примерно в 3 раза быстрее, а минимальная составляет 1 мс, а не 18 мс.

Длительность лямбды

Подключение к экземпляру RDS является трудоемкой задачей, и отсутствие необходимости подключения для каждого вызова благоприятно сказывается на производительности. При подключении к базе данных для простого запроса к базе данных мы достигаем максимального числа подключений к базе данных, равного 20, что соответствует уровню параллелизма (мы сделали 20 одновременных вызовов x 100 раз). Когда разрыв вызовов прекращается, соединения постепенно закрываются.

Теперь, когда AWS увеличил допуск длительности лямбды до 15 минут, это означает, что соединения с базой данных могут длиться дольше, и вы можете быть в опасности достичь максимального числа соединений RDS. Максимальное количество подключений по умолчанию может быть перезаписано в настройках группы параметров RDS, хотя увеличение максимального количества подключений может привести к проблемам с распределением памяти. Меньшие экземпляры могут иметь значение max_connections по умолчанию меньше 100. Помните об этих ограничениях и добавляйте логику приложения только для подключения к базе данных при необходимости.

Использование глобального соединения для других задач

Лямбда подключение к S3

Распространенная задача, которую нам, возможно, потребуется выполнить с помощью Lambda, - получить доступ к данным с состоянием из S3. Приведенный ниже фрагмент кода представляет собой план Python Lambda Function, предоставленный AWS, к которому вы можете перейти, войдя в консоль AWS и нажав здесь. В коде видно, что клиент S3 полностью определен вне обработчика при инициализации контейнера, тогда как для примера RDS глобальное соединение было установлено внутри обработчика. Оба подхода устанавливают глобальные переменные, позволяя им быть доступными для последующих вызовов.

Фрагмент кода лямбда-кода s3-get-object https://console.aws.amazon.com/lambda/home?region=us-east-1#/create/new?bp=s3-get-object-python

Расшифровка переменных среды

Лямбда-консоль дает вам возможность шифровать переменные среды для дополнительной безопасности. Следующий фрагмент кода представляет собой предоставленный AWS пример Java-сценария-помощника для дешифрования переменных среды из функции Lambda. Вы можете перейти к фрагменту кода, следуя этому руководству (в частности, шаг 6). Поскольку DECRYPTED_KEY определен как глобальный класс, функция и логика decryptKey () вызываются только один раз для лямбда-контейнера. Следовательно, мы увидим значительное улучшение продолжительности теплого старта.

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

Использование глобальных переменных в других решениях FaaS

Этот подход не изолирован от AWS Lambda. Метод использования глобального соединения может быть применен и к безсерверным функциям других облачных провайдеров. На странице советов и рекомендаций по облачным функциям Google дается хорошее объяснение не ленивых переменных (когда переменная всегда инициализируется вне метода-обработчика) по сравнению с ленивыми переменными (глобальная переменная устанавливается только при необходимости) глобальными переменными.

Другие лучшие практики

Вот некоторые другие лучшие практики, которые нужно иметь в виду.

тестирование

Использование FaaS облегчает архитектуру микросервисов. А наличие небольших отдельных функциональных блоков идет рука об руку с эффективным модульным тестированием. Чтобы помочь вашим юнит-тестам:

  • Не забудьте исключить тестовые зависимости из пакета lambda.
  • Отделите логику от метода-обработчика, как если бы вы использовали основной метод программы.

Зависимости и размер пакета

Уменьшение размера пакета развертывания означает, что загрузка кода будет быстрее при инициализации и, следовательно, уменьшит время холодного запуска. Удалите неиспользуемые библиотеки и мертвый код, чтобы уменьшить размер файла ZIP для развертывания. AWS SDK предоставляется для сред выполнения Python и JavaScript, поэтому нет необходимости включать их в пакет развертывания.

Если Node.js является вашим предпочтительным временем выполнения Lambda, вы можете применить минификацию и увеличение для уменьшения размера кода вашей функции и минимизации размера вашего пакета развертывания. Некоторые, но не все аспекты минификации и углификации могут быть применены к другим средам выполнения, например. вы не можете удалить пробелы из кода Python, но вы можете удалить комментарии и сократить имена переменных.

Установка памяти

Эксперимент, чтобы найти оптимальный объем памяти для лямбда-функции. Вы платите за выделение памяти, поэтому удвоение памяти означает, что вам придется платить дважды за миллисекунду; но вычислительная мощность увеличивается с выделенной памятью, так что это может потенциально сократить время работы до половины того, что было. Уже есть несколько полезных инструментов для выбора оптимальных настроек памяти, таких как этот.

Заключить…

Одна вещь, чтобы рассмотреть, является ли применение метода повторного использования соединения необходимым. Если ваша лямбда-функция вызывается нечасто, например раз в день, оптимизация для теплых запусков не принесет вам пользы. Часто приходится выбирать между оптимизацией производительности и удобочитаемостью вашего кода - термин «увеличение» говорит сам за себя! Кроме того, добавление глобальных переменных в ваш код для повторного использования соединений с другими сервисами может потенциально затруднить отслеживание вашего кода. На ум приходят два вопроса:

  • Будет ли новый член команды понимать ваш код?
  • Сможете ли вы и ваша команда отладить код в будущем?

Но есть вероятность, что вы выбрали Lambda для его масштаба и хотите высокую производительность и низкие затраты, поэтому найдите баланс, который соответствует потребностям вашей команды.

Эти мнения принадлежат автору. Если в этом посте не указано иное, Capital One не является аффилированным лицом и не поддерживается ни одной из упомянутых компаний. Все торговые марки и другая интеллектуальная собственность, используемая или отображаемая, являются собственностью их соответствующих владельцев. Эта статья © 2019 Capital One.