Porady i wskazówki

W tym dokumencie opisujemy sprawdzone metody projektowania, wdrażania, testowania i wdrażania funkcji w Cloud Functions.

Poprawność

W tej sekcji znajdziesz ogólne sprawdzone metody projektowania i wdrażania funkcji w Cloud Functions.

Zapisz funkcje idempotentne

Funkcje powinny dawać ten sam wynik nawet wtedy, gdy są wywoływane więcej niż raz. Dzięki temu możesz ponawiać próbę, jeśli poprzednie wywołanie zakończy się niepowodzeniem w kodzie. Więcej informacji znajdziesz w artykule o ponownym próbie użycia funkcji opartych na zdarzeniach.

Nie uruchamiaj działań w tle

Aktywność w tle to wszystko, co dzieje się po zakończeniu działania funkcji. Wywołanie funkcji kończy się, gdy funkcja zwraca lub w inny sposób sygnalizuje zakończenie, np. przez wywołanie argumentu callback w funkcjach opartych na zdarzeniach w Node.js. Po zakończeniu płynnego działania kod nie będzie miał dostępu do procesora i nie będzie wykonywać żadnych postępów.

Dodatkowo, gdy kolejne wywołanie zostanie wykonane w tym samym środowisku, działanie w tle zostanie wznowione, zakłócając działanie nowego wywołania. Może to prowadzić do nieoczekiwanego działania i trudnych do zdiagnozowania błędów. Dostęp do sieci po zakończeniu funkcji zwykle prowadzi do zresetowania połączeń (ECONNRESET kod błędu).

Aktywność w tle można często wykrywać w logach z poszczególnych wywołań, znajdując w dzienniku po wierszu informację o zakończeniu wywołania. Aktywność w tle może być czasami ukryta głębiej w kodzie, zwłaszcza gdy występują operacje asynchroniczne, takie jak wywołania zwrotne lub liczniki czasu. Sprawdź swój kod, aby upewnić się, że wszystkie operacje asynchroniczne zostały zakończone, zanim zamkniesz funkcję.

Zawsze usuwaj pliki tymczasowe

Przechowywanie danych na dysku lokalnym w katalogu tymczasowym jest systemem plików w pamięci. Zapisywane pliki wykorzystują pamięć dostępną dla funkcji i czasami zachowują się między wywołaniami. Jeśli nie usuniesz tych plików, może to doprowadzić do wystąpienia błędu braku pamięci i uruchomienia „na zimno”.

Aby zobaczyć wykorzystanie pamięci przez pojedynczą funkcję, wybierz ją na liście funkcji w konsoli GCP i kliknij wykres Wykorzystanie pamięci.

Nie próbuj zapisywać plików poza katalogiem tymczasowym. Do tworzenia ścieżek plików używaj metod niezależnych od platformy i systemu operacyjnego.

Możesz zmniejszyć wymagania dotyczące pamięci podczas przetwarzania większych plików przy użyciu potoku. Aby na przykład przetworzyć plik w Cloud Storage, możesz utworzyć strumień odczytu, przekazać go przez proces oparty na strumieniach i zapisać strumień wyjściowy bezpośrednio do Cloud Storage.

Platforma funkcji

Po wdrożeniu funkcji platforma funkcji jest automatycznie dodawana jako zależność z użyciem jej bieżącej wersji. Aby mieć pewność, że te same zależności są instalowane spójnie w różnych środowiskach, zalecamy przypięcie funkcji do określonej wersji platformy Functions.

Aby to zrobić, umieść preferowaną wersję w odpowiednim pliku blokady (np. package-lock.json w przypadku Node.js lub requirements.txt w przypadku Pythona).

Narzędzia

Ta sekcja zawiera wskazówki dotyczące korzystania z narzędzi do wdrażania i testowania funkcji w Cloud Functions oraz interakcji z nimi.

Programowanie lokalne

Wdrożenie funkcji zajmuje trochę czasu, więc często szybciej jest przetestować jej kod lokalnie.

Programiści Firebase mogą używać emulatora funkcji w Cloud Functions wiersza poleceń Firebase.

Używaj Sendgrid do wysyłania e-maili

Cloud Functions nie zezwala na połączenia wychodzące przez port 25, więc nie można nawiązywać niezabezpieczonych połączeń z serwerem SMTP. Zalecany sposób wysyłania e-maili to SendGrid. Inne opcje wysyłania e-maili znajdziesz w samouczku Wysyłanie e-maila z instancji dotyczącej Google Compute Engine.

Wydajność

W tej sekcji znajdziesz sprawdzone metody optymalizacji skuteczności.

Mądrze korzystaj z zależności

Ponieważ funkcje są bezstanowe, środowisko wykonawcze jest często inicjowane od zera (podczas tzw. „zimnego startu”). Gdy następuje uruchomienie „na zimno”, sprawdzany jest kontekst globalny funkcji.

Jeśli Twoje funkcje importują moduły, ich czas wczytywania może zwiększać czas oczekiwania na wywołanie podczas uruchamiania „na zimno”. Aby zmniejszyć czas oczekiwania, a także czas potrzebny do wdrożenia funkcji, możesz poprawnie wczytać zależności i nie wczytywać zależności, których funkcja nie używa.

Używanie zmiennych globalnych do ponownego używania obiektów w przyszłych wywołaniach

Nie ma gwarancji, że stan funkcji w Cloud Functions zostanie zachowany na potrzeby przyszłych wywołań. Jednak Cloud Functions często odświeża środowisko wykonawcze poprzedniego wywołania. Jeśli zadeklarujesz zmienną w zakresie globalnym, jej wartość będzie mogła zostać użyta w kolejnych wywołaniach bez konieczności ponownego obliczania wartości.

Dzięki temu możesz buforować obiekty, których odtworzenie może być kosztowne przy każdym wywołaniu funkcji. Przeniesienie takich obiektów z treści funkcji do zakresu globalnego może poprawić wydajność. Ten przykład tworzy ciężki obiekt tylko raz na instancję funkcji i dzieli go przez wszystkie wywołania funkcji docierające do danej instancji:

Node.js

console.log('Global scope');
const perInstance = heavyComputation();
const functions = require('firebase-functions');

exports.function = functions.https.onRequest((req, res) => {
  console.log('Function invocation');
  const perFunction = lightweightComputation();

  res.send(`Per instance: ${perInstance}, per function: ${perFunction}`);
});

Python

import time

from firebase_functions import https_fn

# Placeholder
def heavy_computation():
  return time.time()

# Placeholder
def light_computation():
  return time.time()

# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()

@https_fn.on_request()
def scope_demo(request):

  # Per-function scope
  # This computation runs every time this function is called
  function_var = light_computation()
  return https_fn.Response(f"Instance: {instance_var}; function: {function_var}")
  

Ta funkcja HTTP pobiera obiekt żądania (flask.Request) i zwraca tekst odpowiedzi lub dowolny zbiór wartości, które można przekształcić w obiekt Response za pomocą make_response.

Szczególnie ważne jest buforowanie połączeń sieciowych, odwołań do bibliotek i obiektów klienta interfejsu API w zakresie globalnym. Przykłady podano w artykule Optymalizowanie sieci.

Leniwe inicjowanie zmiennych globalnych

Jeśli zainicjujesz zmienne w zakresie globalnym, kod inicjowania będzie zawsze wykonywany przez wywołanie „na zimno”, co zwiększy czas oczekiwania funkcji. W niektórych przypadkach powoduje to sporadyczne upływy czasu do wywoływania usług, które nie są odpowiednio obsługiwane w bloku try/catch. Jeśli niektóre obiekty nie są używane we wszystkich ścieżkach kodu, możesz inicjować je leniwie na żądanie:

Node.js

const functions = require('firebase-functions');
let myCostlyVariable;

exports.function = functions.https.onRequest((req, res) => {
  doUsualWork();
  if(unlikelyCondition()){
      myCostlyVariable = myCostlyVariable || buildCostlyVariable();
  }
  res.status(200).send('OK');
});

Python

from firebase_functions import https_fn

# Always initialized (at cold-start)
non_lazy_global = file_wide_computation()

# Declared at cold-start, but only initialized if/when the function executes
lazy_global = None

@https_fn.on_request()
def lazy_globals(request):

  global lazy_global, non_lazy_global

  # This value is initialized only if (and when) the function is called
  if not lazy_global:
      lazy_global = function_specific_computation()

  return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
  

Ta funkcja HTTP używa globalnych zainicjowanych leniwie. Pobiera obiekt żądania (flask.Request) i zwraca tekst odpowiedzi lub dowolny zbiór wartości, które można przekształcić w obiekt Response za pomocą make_response.

Jest to szczególnie ważne, jeśli zdefiniujesz kilka funkcji w jednym pliku, a różne funkcje korzystają z różnych zmiennych. Jeśli nie korzystasz z leniwego inicjowania, możesz tracić zasoby na zmienne, które są zainicjowane, ale nigdy nie są używane.

Ogranicz uruchomienia „na zimno” przez ustawienie minimalnej liczby instancji

Domyślnie Cloud Functions skaluje liczbę instancji na podstawie liczby żądań przychodzących. Możesz zmienić to domyślne działanie, ustawiając minimalną liczbę instancji, które Cloud Functions musi utrzymywać w gotowości do obsługi żądań. Ustawienie minimalnej liczby instancji ogranicza uruchomienia aplikacji „na zimno”. Jeśli Twoja aplikacja zwraca uwagę na czas oczekiwania, zalecamy ustawienie minimalnej liczby instancji.

Więcej informacji o tych opcjach środowiska wykonawczego znajdziesz w artykule o sterowaniu skalowaniem.

Dodatkowe materiały

Więcej informacji o optymalizacji wydajności znajdziesz w filmie „Google Cloud Performance Atlas” poświęcony czasowi zimnego uruchamiania Cloud Functions.