Tipps & Tricks

In diesem Dokument werden Best Practices für das Entwerfen, Implementieren, Testen und Bereitstellen von Cloud Functions beschrieben.

Richtigkeit

In diesem Abschnitt werden allgemeine Best Practices für den Entwurf und die Implementierung von Cloud Functions beschrieben.

Schreiben Sie idempotente Funktionen

Ihre Funktionen sollten das gleiche Ergebnis liefern, auch wenn sie mehrmals aufgerufen werden. Auf diese Weise können Sie einen Aufruf wiederholen, wenn der vorherige Aufruf mitten in Ihrem Code fehlschlägt. Weitere Informationen finden Sie unter Wiederholen ereignisgesteuerter Funktionen .

Starten Sie keine Hintergrundaktivitäten

Unter Hintergrundaktivität versteht man alles, was passiert, nachdem Ihre Funktion beendet wurde. Ein Funktionsaufruf wird beendet, sobald die Funktion zurückkehrt oder auf andere Weise den Abschluss signalisiert, z. B. durch Aufrufen des callback Arguments in ereignisgesteuerten Node.js-Funktionen. Code, der nach einer ordnungsgemäßen Beendigung ausgeführt wird, kann nicht auf die CPU zugreifen und macht keinen Fortschritt.

Wenn außerdem ein nachfolgender Aufruf in derselben Umgebung ausgeführt wird, wird Ihre Hintergrundaktivität fortgesetzt, was den neuen Aufruf beeinträchtigt. Dies kann zu unerwartetem Verhalten und Fehlern führen, die schwer zu diagnostizieren sind. Der Zugriff auf das Netzwerk nach Beendigung einer Funktion führt in der Regel zum Zurücksetzen der Verbindungen (Fehlercode ECONNRESET ).

Hintergrundaktivitäten können häufig in Protokollen einzelner Aufrufe erkannt werden, indem alles gefunden wird, was nach der Zeile protokolliert wird, die angibt, dass der Aufruf beendet wurde. Hintergrundaktivitäten können manchmal tiefer im Code vergraben sein, insbesondere wenn asynchrone Vorgänge wie Rückrufe oder Timer vorhanden sind. Überprüfen Sie Ihren Code, um sicherzustellen, dass alle asynchronen Vorgänge abgeschlossen sind, bevor Sie die Funktion beenden.

Löschen Sie immer temporäre Dateien

Der lokale Festplattenspeicher im temporären Verzeichnis ist ein In-Memory-Dateisystem. Von Ihnen geschriebene Dateien verbrauchen den für Ihre Funktion verfügbaren Speicher und bleiben manchmal zwischen Aufrufen bestehen. Wenn diese Dateien nicht explizit gelöscht werden, kann dies möglicherweise zu einem Fehler wegen unzureichendem Arbeitsspeicher und einem anschließenden Kaltstart führen.

Sie können den von einer einzelnen Funktion verwendeten Speicher sehen, indem Sie sie in der Liste der Funktionen in der GCP-Konsole auswählen und das Diagramm „Speichernutzung“ auswählen.

Versuchen Sie nicht, außerhalb des temporären Verzeichnisses zu schreiben, und stellen Sie sicher, dass Sie plattform-/betriebssystemunabhängige Methoden zum Erstellen von Dateipfaden verwenden.

Sie können den Speicherbedarf bei der Verarbeitung größerer Dateien mithilfe von Pipelining reduzieren. Sie können beispielsweise eine Datei in Cloud Storage verarbeiten, indem Sie einen Lesestream erstellen, ihn durch einen Stream-basierten Prozess leiten und den Ausgabestream direkt in Cloud Storage schreiben.

Funktions-Framework

Wenn Sie eine Funktion bereitstellen, wird das Functions Framework automatisch als Abhängigkeit hinzugefügt, wobei die aktuelle Version verwendet wird. Um sicherzustellen, dass dieselben Abhängigkeiten in verschiedenen Umgebungen konsistent installiert werden, empfehlen wir Ihnen, Ihre Funktion an eine bestimmte Version des Functions Framework anzuheften.

Fügen Sie dazu Ihre bevorzugte Version in die entsprechende Sperrdatei ein (z. B. package-lock.json für Node.js oder requirements.txt für Python).

Werkzeuge

Dieser Abschnitt enthält Richtlinien zur Verwendung von Tools zum Implementieren, Testen und Interagieren mit Cloud Functions.

Lokale Entwicklung

Die Funktionsbereitstellung nimmt etwas Zeit in Anspruch, daher ist es oft schneller, den Code Ihrer Funktion lokal zu testen.

Firebase-Entwickler können den Firebase CLI Cloud Functions Emulator verwenden.

Verwenden Sie Sendgrid, um E-Mails zu versenden

Cloud Functions lässt keine ausgehenden Verbindungen auf Port 25 zu, sodass Sie keine unsicheren Verbindungen zu einem SMTP-Server herstellen können. Die empfohlene Methode zum Senden von E-Mails ist die Verwendung von SendGrid . Weitere Optionen zum Senden von E-Mails finden Sie im Tutorial zum Senden von E-Mails von einer Instanz für Google Compute Engine.

Leistung

In diesem Abschnitt werden Best Practices zur Optimierung der Leistung beschrieben.

Nutzen Sie Abhängigkeiten mit Bedacht

Da Funktionen zustandslos sind, wird die Ausführungsumgebung häufig von Grund auf neu initialisiert (bei einem sogenannten Kaltstart ). Bei einem Kaltstart wird der globale Kontext der Funktion ausgewertet.

Wenn Ihre Funktionen Module importieren, kann die Ladezeit für diese Module die Aufruflatenz während eines Kaltstarts erhöhen. Sie können diese Latenz sowie die für die Bereitstellung Ihrer Funktion benötigte Zeit reduzieren, indem Sie Abhängigkeiten korrekt laden und keine Abhängigkeiten laden, die Ihre Funktion nicht verwendet.

Verwenden Sie globale Variablen, um Objekte in zukünftigen Aufrufen wiederzuverwenden

Es gibt keine Garantie dafür, dass der Status einer Cloud-Funktion für zukünftige Aufrufe erhalten bleibt. Allerdings recycelt Cloud Functions häufig die Ausführungsumgebung eines vorherigen Aufrufs. Wenn Sie eine Variable im globalen Bereich deklarieren, kann ihr Wert in nachfolgenden Aufrufen wiederverwendet werden, ohne dass eine Neuberechnung erforderlich ist.

Auf diese Weise können Sie Objekte zwischenspeichern, deren Neuerstellung bei jedem Funktionsaufruf möglicherweise kostspielig ist. Das Verschieben solcher Objekte vom Funktionskörper in den globalen Bereich kann zu erheblichen Leistungsverbesserungen führen. Das folgende Beispiel erstellt ein schweres Objekt nur einmal pro Funktionsinstanz und teilt es für alle Funktionsaufrufe, die die angegebene Instanz erreichen:

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}")
  

Diese HTTP-Funktion nimmt ein Anforderungsobjekt ( flask.Request ) und gibt den Antworttext oder einen beliebigen Satz von Werten zurück, der mit make_response in ein Response umgewandelt werden kann.

Es ist besonders wichtig, Netzwerkverbindungen, Bibliotheksverweise und API-Clientobjekte im globalen Bereich zwischenzuspeichern. Beispiele finden Sie unter Optimieren des Netzwerks .

Führen Sie eine verzögerte Initialisierung globaler Variablen durch

Wenn Sie Variablen im globalen Bereich initialisieren, wird der Initialisierungscode immer über einen Kaltstartaufruf ausgeführt, wodurch sich die Latenz Ihrer Funktion erhöht. In bestimmten Fällen führt dies zu zeitweiligen Zeitüberschreitungen bei den aufgerufenen Diensten, wenn diese in einem try / catch Block nicht ordnungsgemäß behandelt werden. Wenn einige Objekte nicht in allen Codepfaden verwendet werden, sollten Sie erwägen, sie bei Bedarf langsam zu initialisieren:

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}.")
  

Diese HTTP-Funktion verwendet verzögert initialisierte Globals. Es nimmt ein Anforderungsobjekt ( flask.Request ) und gibt den Antworttext oder einen beliebigen Satz von Werten zurück, der mit make_response in ein Response umgewandelt werden kann.

Dies ist besonders wichtig, wenn Sie mehrere Funktionen in einer einzigen Datei definieren und verschiedene Funktionen unterschiedliche Variablen verwenden. Wenn Sie keine verzögerte Initialisierung verwenden, verschwenden Sie möglicherweise Ressourcen für Variablen, die initialisiert, aber nie verwendet werden.

Reduzieren Sie Kaltstarts, indem Sie eine Mindestanzahl an Instanzen festlegen

Standardmäßig skaliert Cloud Functions die Anzahl der Instanzen basierend auf der Anzahl der eingehenden Anfragen. Sie können dieses Standardverhalten ändern, indem Sie eine Mindestanzahl von Instanzen festlegen, die Cloud Functions zur Bearbeitung von Anfragen bereithalten muss. Durch das Festlegen einer Mindestanzahl von Instanzen werden Kaltstarts Ihrer Anwendung reduziert. Wir empfehlen, eine Mindestanzahl von Instanzen festzulegen, wenn Ihre Anwendung latenzempfindlich ist.

Weitere Informationen zu diesen Laufzeitoptionen finden Sie unter Skalierungsverhalten steuern .

Zusätzliche Ressourcen

Erfahren Sie mehr über die Optimierung der Leistung im Video „ Cloud Functions Cold Boot Time “ des „Google Cloud Performance Atlas“.