Кращі практики для використання контейнерів Lambda 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, якби замість RDS використовували сховище ключа / значення з DynamoDB.

Я перейду два варіанти вирішення цього завдання, перше - це моє «наївне» рішення, тоді як друге рішення оптимізує теплі старти часу, використовуючи з'єднання для наступних викликів. Тоді ми порівняємо ефективність кожного рішення.

Варіант 1 - Підключення до RDS в межах обробника

Цей приклад коду показує, як я міг би наївно підійти до цієї задачі - підключення до бази даних знаходиться в методі обробника. Існує простий запит вибору для отримання імені ідентифікатора перед поверненням корисного навантаження, який тепер включає ім'я.

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

Тривалість лямбда

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

Кількість підключень до бази даних RDS у вікні 5 хвилин.

Варіант 2 - Використовуйте глобальне з'єднання

Другий варіант - визначити з'єднання як глобальний поза методом обробника. Тоді всередині обробника ми додаємо чек, щоб перевірити, чи існує з'єднання, і підключимось лише тоді, коли він не є. Це означає, що з'єднання здійснюється лише один раз на контейнер. Встановлення з'єднання таким чином із умовним місцем означає, що нам не потрібно здійснювати з'єднання, якщо цього не вимагає логіка коду.

Ми більше не закриваємо з'єднання з базою даних, тому з'єднання залишається для подальшого виклику функції. Повторне використання з'єднання значно скорочує тривалі терміни старту - середня тривалість приблизно в 3 рази швидша, а мінімальна - 1 мс, а не 18 мс.

Довгі лямбда

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

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

Використання глобального з'єднання для інших завдань

Підключення лямбда до S3

Поширене завдання, яке нам може знадобитися для роботи з Lambda, - це доступ до значущих даних з S3. Фрагмент коду нижче - це план, що надається AWS, наданий програмою Python Lambda Function - до якої ви можете перейти, увійшовши в консоль 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 Cloud Functions дає хороше пояснення для нелених змінних (коли змінна завжди ініціалізується поза методом обробника) проти лінивих змінних (глобальна змінна встановлюється лише у разі потреби) глобальних змінних.

Інші кращі практики

Ось кілька інших найкращих практик, які слід пам’ятати.

Тестування

Використання FaaS полегшує створення архітектури мікросервісів. А наявність невеликих, дискретних фрагментів функціональності йде разом із ефективним тестуванням одиниць. Щоб допомогти вашим тестам на пристрій:

  • Не забудьте виключити тестові залежності з пакету лямбда.
  • Відокремте логіку від методу обробника, як це було б з основним методом програми.

Залежності та розмір пакету

Зменшення розміру пакету розгортання означає, що завантаження коду буде швидшим при ініціалізації, а отже, покращиться ваші холодні часи. Видаліть невикористані бібліотеки та мертвий код, щоб зменшити розмір ZIP-файлу розгортання. AWS SDK передбачений для Python та JavaScript, тому не потрібно включати їх у свій пакет розгортання.

Якщо Node.js є вашим бажаним режимом роботи Lambda, ви можете застосувати мінімізацію та знищення, щоб зменшити розмір коду вашої функції та мінімізувати розмір пакета розгортання. Деякі, але не всі аспекти мінімізації та угліфікації можуть бути застосовані до інших періодів виконання, наприклад. ви не можете видалити пробіл з коду python, але ви можете видалити коментарі та скоротити імена змінних.

Налаштування пам'яті

Експериментуйте, щоб знайти оптимальний об'єм пам'яті для функції лямбда. Ви платите за розподіл пам’яті, тому подвоєння пам’яті означає, що вам доведеться платити подвійно за мілісекунду; але обчислювальна ємність збільшується за допомогою виділеної пам’яті, щоб це потенційно могло скоротити час роботи до менш ніж половини того, що було. Вже є кілька корисних інструментів для вибору оптимального для вас налаштування пам’яті, наприклад, цього.

Прийти до висновку…

Варто враховувати, чи потрібно застосовувати метод повторного використання з'єднання. Якщо ваша лямбда-функція викликається лише нечасто, наприклад, раз на день, оптимізація для теплих стартів вам не скористається. Часто виникає компроміс між оптимізацією для продуктивності та читабельністю вашого коду - термін "уліфікація" говорить сам за себе! Крім того, додавання глобальних змінних до коду для повторного використання з'єднань з іншими службами потенційно може ускладнити ваш код. На думку спадають два питання:

  • Чи зрозуміє ваш новий член команди ваш код?
  • Чи зможете ви та ваша команда налагодити код у майбутньому?

Але, швидше за все, ви вибрали Lambda за її масштабом і хочете високої продуктивності та низьких витрат, тому знайдіть баланс, який відповідає потребам вашої команди.

Ці думки є думками автора. Якщо в цій посаді не зазначено інше, Capital One не є афілійованим, а також не схвалюється жодною із зазначених компаній. Усі торгові марки та інша інтелектуальна власність, що використовується або відображається, є власністю відповідних власників. Ця стаття © 2019 Capital One.