Suggerimenti e informazioni trucchi

Questo documento descrive le best practice per la progettazione, l'implementazione, il test e la distribuzione di Cloud Functions.

Correttezza

Questa sezione descrive le best practice generali per la progettazione e l'implementazione di Cloud Functions.

Scrivere funzioni idempotenti

Le tue funzioni dovrebbero produrre lo stesso risultato anche se vengono chiamate più volte. Ciò ti consente di riprovare un'invocazione se l'invocazione precedente fallisce parzialmente nel codice. Per ulteriori informazioni, consulta ritentare le funzioni guidate dagli eventi .

Non avviare attività in background

L'attività in background è tutto ciò che accade dopo che la funzione è terminata. L'invocazione di una funzione termina quando la funzione restituisce o segnala in altro modo il completamento, ad esempio chiamando l'argomento callback nelle funzioni guidate dagli eventi di Node.js. Qualsiasi codice eseguito dopo la terminazione normale non può accedere alla CPU e non farà alcun progresso.

Inoltre, quando viene eseguita un'invocazione successiva nello stesso ambiente, l'attività in background riprende, interferendo con la nuova invocazione. Ciò potrebbe causare comportamenti imprevisti ed errori difficili da diagnosticare. L'accesso alla rete dopo la conclusione di una funzione porta solitamente al ripristino delle connessioni (codice di errore ECONNRESET ).

L'attività in background può spesso essere rilevata nei log delle singole invocazioni, trovando tutto ciò che viene registrato dopo la riga che indica che l'invocazione è terminata. L'attività in background a volte può essere sepolta più in profondità nel codice, soprattutto quando sono presenti operazioni asincrone come callback o timer. Esamina il codice per assicurarti che tutte le operazioni asincrone terminino prima di terminare la funzione.

Elimina sempre i file temporanei

L'archiviazione del disco locale nella directory temporanea è un file system in memoria. I file che scrivi consumano la memoria disponibile per la tua funzione e talvolta persistono tra le invocazioni. La mancata eliminazione esplicita di questi file potrebbe eventualmente causare un errore di memoria insufficiente e un successivo avvio a freddo.

Puoi visualizzare la memoria utilizzata da una singola funzione selezionandola nell'elenco delle funzioni nella console GCP e scegliendo il grafico di utilizzo della memoria .

Non tentare di scrivere all'esterno della directory temporanea e assicurarsi di utilizzare metodi indipendenti dalla piattaforma/sistema operativo per costruire percorsi di file.

È possibile ridurre i requisiti di memoria durante l'elaborazione di file di grandi dimensioni utilizzando il pipelining. Ad esempio, puoi elaborare un file su Cloud Storage creando un flusso di lettura, passandolo attraverso un processo basato sul flusso e scrivendo il flusso di output direttamente su Cloud Storage.

Quadro delle funzioni

Quando si distribuisce una funzione, il framework di funzioni viene aggiunto automaticamente come dipendenza, utilizzando la versione corrente. Per garantire che le stesse dipendenze siano installate in modo coerente in ambienti diversi, ti consigliamo di aggiungere la tua funzione a una versione specifica del framework delle funzioni.

A tale scopo, includi la tua versione preferita nel file di blocco pertinente (ad esempio, package-lock.json per Node.js o requirements.txt per Python).

Utensili

Questa sezione fornisce linee guida su come utilizzare gli strumenti per implementare, testare e interagire con Cloud Functions.

Sviluppo locale

La distribuzione della funzione richiede un po' di tempo, quindi spesso è più veloce testare il codice della funzione localmente.

Gli sviluppatori Firebase possono utilizzare l' emulatore Firebase CLI Cloud Functions .

Utilizza Sendgrid per inviare e-mail

Cloud Functions non consente connessioni in uscita sulla porta 25, quindi non puoi effettuare connessioni non sicure a un server SMTP. Il modo consigliato per inviare e-mail è utilizzare SendGrid . Puoi trovare altre opzioni per l'invio di email nel tutorial Invio di email da un'istanza per Google Compute Engine.

Prestazione

In questa sezione vengono descritte le procedure consigliate per ottimizzare le prestazioni.

Usa le dipendenze con saggezza

Poiché le funzioni sono stateless, l'ambiente di esecuzione viene spesso inizializzato da zero (durante il cosiddetto avvio a freddo ). Quando si verifica un avvio a freddo, viene valutato il contesto globale della funzione.

Se le tue funzioni importano moduli, il tempo di caricamento di tali moduli può aumentare la latenza di chiamata durante un avvio a freddo. Puoi ridurre questa latenza, così come il tempo necessario per distribuire la tua funzione, caricando correttamente le dipendenze e non caricando le dipendenze che la tua funzione non utilizza.

Utilizza variabili globali per riutilizzare gli oggetti nelle invocazioni future

Non vi è alcuna garanzia che lo stato di una Cloud Function venga preservato per invocazioni future. Tuttavia, Cloud Functions spesso ricicla l'ambiente di esecuzione di una chiamata precedente. Se dichiari una variabile nell'ambito globale, il suo valore può essere riutilizzato nelle invocazioni successive senza dover essere ricalcolato.

In questo modo è possibile memorizzare nella cache oggetti che potrebbero essere costosi da ricreare ad ogni invocazione di funzione. Lo spostamento di tali oggetti dal corpo della funzione all'ambito globale può comportare miglioramenti significativi delle prestazioni. L'esempio seguente crea un oggetto pesante solo una volta per istanza di funzione e lo condivide tra tutte le chiamate di funzione che raggiungono l'istanza specificata:

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}`);
});

Pitone

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

Questa funzione HTTP accetta un oggetto richiesta ( flask.Request ) e restituisce il testo della risposta o qualsiasi insieme di valori che può essere trasformato in un oggetto Response utilizzando make_response .

È particolarmente importante memorizzare nella cache le connessioni di rete, i riferimenti alla libreria e gli oggetti client API nell'ambito globale. Per esempi vedere Ottimizzazione della rete .

Esegue l'inizializzazione pigra delle variabili globali

Se inizializzi le variabili nell'ambito globale, il codice di inizializzazione verrà sempre eseguito tramite un'invocazione di avvio a freddo, aumentando la latenza della funzione. In alcuni casi, ciò causa timeout intermittenti per i servizi richiamati se non vengono gestiti adeguatamente in un blocco try / catch . Se alcuni oggetti non vengono utilizzati in tutti i percorsi del codice, valuta la possibilità di inizializzarli pigramente su richiesta:

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');
});

Pitone

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

Questa funzione HTTP utilizza valori globali inizializzati pigramente. Richiede un oggetto richiesta ( flask.Request ) e restituisce il testo della risposta o qualsiasi insieme di valori che può essere trasformato in un oggetto Response utilizzando make_response .

Ciò è particolarmente importante se si definiscono più funzioni in un singolo file e funzioni diverse utilizzano variabili diverse. A meno che non si utilizzi l'inizializzazione lenta, è possibile che si sprechino risorse su variabili inizializzate ma mai utilizzate.

Riduci gli avviamenti a freddo impostando un numero minimo di istanze

Per impostazione predefinita, Cloud Functions ridimensiona il numero di istanze in base al numero di richieste in entrata. Puoi modificare questo comportamento predefinito impostando un numero minimo di istanze che Cloud Functions deve tenere pronte per servire le richieste. L'impostazione di un numero minimo di istanze riduce gli avvii a freddo dell'applicazione. Ti consigliamo di impostare un numero minimo di istanze se la tua applicazione è sensibile alla latenza.

Vedi Controllare il comportamento di ridimensionamento per ulteriori informazioni su queste opzioni di runtime.

Risorse addizionali

Scopri di più sull'ottimizzazione delle prestazioni nel video "Google Cloud Performance Atlas" Cloud Functions Cold Boot Time .