Najlepsze praktyki dotyczące ponownego wykorzystania pojemnika Lambda na AWS

Optymalizacja ciepłego startu podczas podłączania AWS Lambda do innych usług

AWS Lambda zapewnia wysoką skalowalność, ponieważ jest bezserwerowy i bezstanowy, umożliwiając natychmiastowe odrodzenie wielu kopii funkcji lambda (jak opisano tutaj). Jednak podczas pisania kodu aplikacji prawdopodobnie potrzebujesz dostępu do niektórych danych stanowych. Oznacza to połączenie z magazynem danych, takim jak instancja RDS lub S3. Łączenie się z innymi usługami AWS Lambda dodaje jednak czasu do kodu funkcji. Mogą wystąpić również skutki uboczne związane z wysoką skalowalnością, takie jak osiągnięcie maksymalnej liczby dozwolonych połączeń z instancją RDS. Jedną z opcji, aby temu zaradzić, jest ponowne użycie kontenera w AWS Lambda w celu utrzymania połączenia i skrócenia czasu pracy lambda.

Istnieje kilka przydatnych diagramów wyjaśniających cykl życia żądania lambda.

Podczas zimnego startu, gdy funkcja jest wywoływana po raz pierwszy lub po okresie bezczynności, występują następujące zdarzenia:

  • Kod i zależności są pobierane.
  • Uruchomiono nowy kontener.
  • Środowisko wykonawcze jest ładowane.

Ostatnim działaniem jest uruchomienie kodu, co dzieje się za każdym razem, gdy wywoływana jest funkcja lambda. Jeśli kontener zostanie ponownie użyty do kolejnego wywołania funkcji lambda, możemy przejść do uruchomienia kodu. Nazywa się to „ciepłym startem” i jest to krok, który możemy zoptymalizować, łącząc się z innymi usługami, definiując połączenie poza zakresem metody modułu obsługi.

Łączenie z innymi usługami AWS od Lambda

Przykład: Połącz się z instancją RDS, ikony AWS pochodzą stąd

Mamy prosty i powszechny przykład do przejścia - chcemy połączyć się z zasobem kontenera, aby pobrać dane dotyczące wzbogacania. W tym przykładzie ładunek JSON przychodzi z identyfikatorem, a funkcja Lambda łączy się z instancją RDS z uruchomionym PostgreSQL, aby znaleźć odpowiednią nazwę identyfikatora, abyśmy mogli zwrócić wzbogacony ładunek. Ponieważ funkcja lambda łączy się z RDS, która mieszka w VPC, funkcja lambda musi teraz również żyć w prywatnej podsieci. Dodaje to kilka kroków do zimnego startu - należy dołączyć elastyczny interfejs sieciowy VPC (ENI) (jak wspomniano na blogu Jeremy Daly, to dodaje czasu do zimnych startów).

Uwaga: moglibyśmy uniknąć korzystania z VPC, gdybyśmy korzystali z magazynu kluczy / wartości z DynamoDB zamiast z RDS.

Omówię dwa rozwiązania tego zadania, pierwsze to moje „naiwne” rozwiązanie, a drugie rozwiązanie optymalizuje czas ciepłego startu, wykorzystując połączenie do kolejnych wywołań. Następnie porównamy wydajność każdego rozwiązania.

Opcja 1 - Połącz się z RDS w module obsługi

Ten przykład kodu pokazuje, jak naiwnie podchodzę do tego zadania - połączenie z bazą danych znajduje się w metodzie modułu obsługi. Istnieje proste zapytanie wyboru, aby pobrać nazwę identyfikatora przed zwróceniem ładunku, który teraz zawiera nazwę.

Zobaczmy, jak ta opcja działa podczas małego testu z serią 2000 wywołań przy współbieżności 20. Minimalny czas trwania wynosi 18 ms ze średnią 51ms i nieco ponad 1 sekundę (czas trwania zimnego startu).

Czas trwania Lambda

Poniższy wykres pokazuje, że istnieje maksymalnie osiem połączeń z bazą danych.

Liczba połączeń z bazą danych RDS w 5-minutowym oknie.

Opcja 2 - Użyj połączenia globalnego

Drugą opcją jest zdefiniowanie połączenia jako globalnego poza metodą modułu obsługi. Następnie w module obsługi dodajemy sprawdzenie, aby sprawdzić, czy połączenie istnieje, i łączymy się tylko wtedy, gdy nie istnieje. Oznacza to, że połączenie jest nawiązywane tylko raz dla jednego kontenera. Ustawienie połączenia w ten sposób z wprowadzeniem warunkowym oznacza, że ​​nie musimy nawiązywać połączenia, jeśli nie jest to wymagane przez logikę kodu.

Nie zamykamy już połączenia z bazą danych, więc połączenie pozostaje w celu późniejszego wywołania funkcji. Ponowne użycie połączenia znacznie skraca czas trwania ciepłego startu - średni czas trwania jest około 3 razy szybszy, a minimalny to 1 ms zamiast 18 ms.

Czas trwania Lambda

Łączenie się z instancją RDS jest czasochłonnym zadaniem, a brak konieczności łączenia się z każdym wywołaniem jest korzystny dla wydajności. Łącząc się z bazą danych w celu wykonania prostego zapytania do bazy danych, osiągamy maksymalną liczbę połączeń z bazą danych wynoszącą 20, która odpowiada poziomowi współbieżności (wykonaliśmy 20 równoczesnych wywołań x 100 razy). Kiedy seria inwokacji ustaje, połączenia stopniowo się zamykają.

Teraz, gdy AWS zwiększył limit czasu trwania lambda do 15 minut, oznacza to, że połączenia z bazą danych mogą trwać dłużej i możesz być w niebezpieczeństwie osiągnięcia maksymalnej liczby połączeń RDS. Domyślne maks. Połączenia można nadpisać w ustawieniach grupy parametrów RDS, chociaż zwiększenie maksymalnej liczby połączeń może spowodować problemy z alokacją pamięci. Mniejsze instancje mogą mieć domyślną wartość max_connections mniejszą niż 100. Należy pamiętać o tych limitach i dodawać logikę aplikacji tylko w razie potrzeby, aby połączyć się z bazą danych.

Korzystanie z globalnego połączenia do innych zadań

Lambda Podłączanie do S3

Typowym zadaniem, które może być konieczne przy użyciu Lambda, jest dostęp do danych stanowych z S3. Poniższy fragment kodu jest dostarczonym przez AWS planem funkcji lambda z funkcją Python - do którego można przejść, logując się do konsoli AWS i klikając tutaj. W kodzie widać, że klient S3 jest w pełni zdefiniowany poza modułem obsługi podczas inicjowania kontenera, podczas gdy dla przykładu RDS globalne połączenie zostało ustawione w module obsługi. Oba podejścia ustawią zmienne globalne, umożliwiając ich dostępność do kolejnych wywołań.

fragment kodu projektu s3-get-object lambda https://console.aws.amazon.com/lambda/home?region=us-east-1#/create/new?bp=s3-get-object-python

Odszyfrowywanie zmiennych środowiskowych

Konsola lambda daje możliwość szyfrowania zmiennych środowiskowych dla dodatkowego bezpieczeństwa. Poniższy fragment kodu jest dostarczonym przez AWS przykładem Java skryptu pomocniczego do odszyfrowywania zmiennych środowiskowych z funkcji Lambda. Możesz przejść do fragmentu kodu, wykonując ten samouczek (w szczególności krok 6). Ponieważ DECRYPTED_KEY jest zdefiniowany jako klasa globalna, funkcja i logika decryptKey () jest wywoływana tylko raz dla kontenera lambda. Dlatego zauważymy znaczną poprawę czasu trwania ciepłego rozruchu.

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

Używanie zmiennych globalnych w innych rozwiązaniach FaaS

To podejście nie jest izolowane dla AWS Lambda. Metodę korzystania z połączenia globalnego można również zastosować do funkcji serwerowych innych dostawców chmury. Strona wskazówek i trików Google Cloud Functions zawiera dobre wyjaśnienie zmiennych nieleniwych (gdy zmienna jest zawsze inicjowana poza metodą modułu obsługi) w porównaniu do zmiennych leniwych (zmienna globalna jest ustawiana tylko w razie potrzeby) zmiennych globalnych.

Inne najlepsze praktyki

Oto kilka innych dobrych praktyk, o których należy pamiętać.

Testowanie

Korzystanie z FaaS ułatwia posiadanie architektury mikrousług. Posiadanie małych, dyskretnych elementów funkcjonalnych idzie w parze ze skutecznym testowaniem jednostkowym. Aby wspomóc testy jednostkowe:

  • Pamiętaj, aby wykluczyć zależności testowe z pakietu lambda.
  • Oddziel logikę od metody modułu obsługi, tak jak w przypadku głównej metody programu.

Zależności i wielkość paczki

Zmniejszenie rozmiaru pakietu wdrożeniowego oznacza, że ​​pobieranie kodu będzie szybsze przy inicjalizacji, a zatem poprawi czas zimnego startu. Usuń nieużywane biblioteki i martwy kod, aby zmniejszyć rozmiar pliku ZIP wdrożenia. Zestaw AWS SDK jest dostarczany dla środowisk wykonawczych Python i JavaScript, więc nie ma potrzeby dołączania ich do pakietu wdrażania.

Jeśli preferowanym środowiskiem wykonawczym Lambda jest Node.js, możesz zastosować minimalizację i uglifikację, aby zmniejszyć rozmiar kodu funkcji i zminimalizować rozmiar pakietu wdrożeniowego. Niektóre, ale nie wszystkie aspekty minimalizacji i uglifikacji można zastosować do innych środowisk wykonawczych, np. nie można usunąć białych znaków z kodu Pythona, ale można usuwać komentarze i skracać nazwy zmiennych.

Ustawianie pamięci

Eksperymentuj, aby znaleźć optymalną ilość pamięci dla funkcji lambda. Płacisz za przydział pamięci, więc podwojenie pamięci oznacza, że ​​musisz płacić podwójnie za milisekundę; ale pojemność obliczeniowa wzrasta wraz z przydzieloną pamięcią, więc może potencjalnie skrócić czas działania do mniej niż połowy tego, co było. Istnieją już przydatne narzędzia do wyboru optymalnego ustawienia pamięci, takie jak to.

Podsumowując…

Jedną rzeczą do rozważenia jest to, czy konieczne jest zastosowanie metody ponownego użycia połączenia. Jeśli twoja funkcja lambda jest przywoływana rzadko, na przykład raz dziennie, nie odniesiesz korzyści z optymalizacji pod kątem ciepłego rozruchu. Często trzeba dokonać kompromisu między optymalizacją wydajności a czytelnością kodu - termin „glifikacja” mówi sam za siebie! Ponadto dodanie zmiennych globalnych do kodu w celu ponownego wykorzystania połączeń z innymi usługami może potencjalnie utrudnić śledzenie kodu. Przychodzą mi na myśl dwa pytania:

  • Czy nowy członek zespołu zrozumie Twój kod?
  • Czy Ty i Twój zespół będziecie mogli debugować kod w przyszłości?

Ale są szanse, że wybrałeś Lambdę ze względu na jej skalę i chcesz wysokiej wydajności i niskich kosztów, więc znajdź równowagę, która odpowiada potrzebom Twojego zespołu.

Są to opinie autora. O ile nie wskazano inaczej w tym poście, Capital One nie jest powiązany ani nie jest wspierany przez żadną z wymienionych firm. Wszystkie znaki handlowe i inna własność intelektualna używana lub wyświetlana są własnością ich właścicieli. Ten artykuł jest © 2019 Capital One.