Porady i wskazówki

Ten dokument zawiera 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

Twoje funkcje powinny dawać ten sam wynik, nawet jeśli zostaną wywoływane kilka razy. Dzięki temu możesz ponowić próbę wywołania, jeśli poprzednie wywołanie nie powiedzie się w części kodu. Więcej informacji znajdziesz w artykule o ponawianiu prób używania funkcji opartych na zdarzeniach.

Nie rozpoczynaj aktywności 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 Node.js opartych na zdarzeniach. Żaden kod uruchomiony po zakończeniu bez utraty danych nie ma dostępu do procesora i nie będzie wykonywał żadnych postępów.

Ponadto gdy kolejne wywołanie jest wykonywane w tym samym środowisku, aktywność w tle jest wznawiana, co koliduje z nowym wywołaniem. Może to prowadzić do nieoczekiwanego działania i błędów trudnych do zdiagnozowania. Dostęp do sieci po zakończeniu działania funkcji zwykle prowadzi do zresetowania połączeń (ECONNRESET kod błędu).

Aktywność w tle często można wykryć w logach poszczególnych wywołań, znajdując wszystkie dane zarejestrowane po wierszu z informacją o zakończeniu wywołania. Aktywność w tle może być czasem ukryta w kodzie, zwłaszcza gdy obecne są operacje asynchroniczne, takie jak wywołania zwrotne lub liczniki czasu. Sprawdź swój kod, aby upewnić się, że wszystkie operacje asynchroniczne zostaną zakończone, zanim zakończysz funkcję.

Zawsze usuwaj pliki tymczasowe

Dysk lokalny w katalogu tymczasowym to system plików w pamięci. Napisane pliki wykorzystują pamięć dostępną dla funkcji i czasami nieprzerwanie między wywołaniami. Jeśli tego nie zrobisz, może to spowodować błąd „brak pamięci”, a następnie uruchomienie „na zimno”.

Aby zobaczyć pamięć używaną przez daną funkcję, wybierz ją na liście funkcji w konsoli GCP, a następnie wybierz wykres Wykorzystanie pamięci.

Nie próbuj pisać poza katalogiem tymczasowym. Pamiętaj też, aby do tworzenia ścieżek plików używać metod niezależnych od platformy/systemu operacyjnego.

Podczas przetwarzania większych plików za pomocą potoku możesz zmniejszyć wymagania dotyczące pamięci. Możesz na przykład przetworzyć plik w Cloud Storage przez utworzenie strumienia odczytu, przekazanie go w procesie opartym na strumieniu i zapisanie go bezpośrednio w Cloud Storage.

Platforma funkcji

Podczas wdrażania 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 będą instalowane spójnie w różnych środowiskach, zalecamy przypięcie funkcji do konkretnej wersji platformy funkcji.

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 do interakcji z nimi.

Programowanie lokalne

Wdrażanie funkcji zajmuje trochę czasu, więc często szybciej jest przetestować kod funkcji lokalnie.

Programiści Firebase mogą używać emulatora Cloud Functions w interfejsie 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. Zalecanym sposobem wysyłania e-maili jest korzystanie z funkcji SendGrid. Inne opcje wysyłania e-maili znajdziesz w samouczku Wysyłanie e-maili z instancji dla Google Compute Engine.

Wydajność

W tej sekcji znajdziesz sprawdzone metody optymalizacji skuteczności.

Mądrze wykorzystuj zależności

Ze względu na to, że funkcje są bezstanowe, środowisko wykonawcze jest często inicjowane od podstaw (w ramach „zimnego startu”). W takiej sytuacji oceniany jest globalny kontekst funkcji.

Jeśli Twoje funkcje importują moduły, czas ich wczytywania może wydłużyć czas oczekiwania na wywołanie podczas uruchamiania „na zimno”. Możesz skrócić ten czas oczekiwania, a także skrócić czas potrzebny na wdrożenie funkcji, poprawnie ładując zależności i nie ładując zależności, których funkcja nie używa.

Używaj zmiennych globalnych, aby ponownie używać 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 być ponownie używana w kolejnych wywołaniach bez konieczności ponownego obliczania.

W ten sposób możesz buforować obiekty, których odtworzenie przy każdym wywołaniu funkcji może być kosztowne. Przeniesienie takich obiektów z treści funkcji do zakresu globalnego może spowodować znaczącą poprawę wydajności. W tym przykładzie tworzy się ciężki obiekt tylko raz na instancję funkcji i udostępnia go we wszystkich wywołaniach funkcji, które docierają 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 API w zakresie globalnym. Przykłady znajdziesz w artykule Optymalizowanie sieci.

Czy 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 tymczasowe przerwy w działaniu wywoływanych usług, jeśli nie są one odpowiednio obsługiwane w blokach try/catch. Jeśli niektóre obiekty nie są używane we wszystkich ścieżkach kodu, rozważ ich zainicjowanie 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 wykorzystuje globalne inicjowane leniwie. Zwraca on 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, gdy definiujesz kilka funkcji w jednym pliku, a poszczególne funkcje używają różnych zmiennych. Jeśli nie korzystasz z leniwego inicjowania, możesz zmarnować zasoby na zmienne, które są inicjowane, 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 zmniejsza liczbę uruchomień aplikacji „na zimno”. Jeśli aplikacja jest wrażliwa na opóźnienia, zalecamy ustawienie minimalnej liczby instancji.

Więcej informacji o tych opcjach środowiska wykonawczego znajdziesz w artykule o zarządzaniu zachowaniem skalowania.

Dodatkowe materiały

Więcej informacji o optymalizowaniu wydajności znajdziesz w filmie „Google Cloud PerformanceAtlas” („Cloud Functions „zimny rozruch”).