Ottimizzazione del networking

La semplicità di Cloud Functions ti consente di sviluppare rapidamente il codice ed eseguirlo in un ambiente serverless. Su larga scala, il costo di esecuzione delle funzioni è ridotto e l'ottimizzazione del codice potrebbe non sembrare una priorità elevata. Tuttavia, man mano che il deployment viene eseguito su larga scala, l'ottimizzazione del codice diventa sempre più importante.

Questo documento descrive come ottimizzare la rete per le funzioni. Ecco alcuni dei vantaggi dell'ottimizzazione della rete:

  • Riduci il tempo della CPU impiegato per stabilire nuove connessioni in uscita a ogni chiamata di funzione.
  • Riduci la probabilità di esaurire le quote di connessione o DNS.

Mantenere le connessioni permanenti

Questa sezione fornisce esempi di come mantenere connessioni permanenti in una funzione. In caso contrario, le quote di connessione potrebbero esaurirsi rapidamente.

Questa sezione illustra i seguenti scenari:

  • HTTP/S
  • API di Google

Richieste HTTP/S

Lo snippet di codice ottimizzato di seguito mostra come mantenere le connessioni permanenti instead of creating a new connection upon every function invocation:

Node.js

const http = require('http');
const functions = require('firebase-functions');

// Setting the `keepAlive` option to `true` keeps
// connections open between function invocations
const agent = new http.Agent({keepAlive: true});

exports.function = functions.https.onRequest((request, response) => {
    req = http.request({
        host: '',
        port: 80,
        path: '',
        method: 'GET',
        agent: agent, // Holds the connection open after the first invocation
    }, res => {
        let rawData = '';
        res.setEncoding('utf8');
        res.on('data', chunk => { rawData += chunk; });
        res.on('end', () => {
            response.status(200).send(`Data: ${rawData}`);
        });
    });
    req.on('error', e => {
        response.status(500).send(`Error: ${e.message}`);
    });
    req.end();
});

Python

from firebase_functions import https_fn
import requests

# Create a global HTTP session (which provides connection pooling)
session = requests.Session()

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

    # The URL to send the request to
    url = "http://example.com"

    # Process the request
    response = session.get(url)
    response.raise_for_status()
    return https_fn.Response("Success!")
    

Questa funzione HTTP utilizza un pool di connessioni per effettuare richieste HTTP. Prende un oggetto request (flask.Request) e restituisce il testo della risposta o qualsiasi insieme di valori che può essere trasformato in un oggetto Response utilizzando make_response.

Accesso alle API di Google

L'esempio seguente utilizza Cloud Pub/Sub, ma questo approccio funziona anche per altre librerie client, ad esempio Cloud Natural Language o Cloud Spanner. Tieni presente che i miglioramenti del rendimento possono dipendere dall'implementazione corrente di determinate librerie client.

La creazione di un oggetto client Pub/Sub genera una connessione e due query DNS per chiamata. Per evitare connessioni e query DNS non necessarie, crea l'oggetto client Pub/Sub a livello globale come mostrato nell'esempio seguente:

node.js

const PubSub = require('@google-cloud/pubsub');
const functions = require('firebase-functions');
const pubsub = PubSub();

exports.function = functions.https.onRequest((req, res) => {
    const topic = pubsub.topic('');

    topic.publish('Test message', err => {
        if (err) {
            res.status(500).send(`Error publishing the message: ${err}`);
        } else {
            res.status(200).send('1 message published');
        }
    });
});

Python

import os

from firebase_functions import https_fn
from google.cloud import pubsub_v1

# from firebase_functions import https_fn
# Create a global Pub/Sub client to avoid unneeded network activity
pubsub = pubsub_v1.PublisherClient()

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

    project = os.getenv("GCP_PROJECT")
    request_json = request.get_json()

    topic_name = request_json["topic"]
    topic_path = pubsub.topic_path(project, topic_name)

    # Process the request
    data = b"Test message"
    pubsub.publish(topic_path, data=data)

    return https_fn.Response("1 message published")
    

Questa funzione HTTP utilizza un'istanza della libreria client memorizzata nella cache per ridurre il numero di connessioni richieste per ogni chiamata della funzione. Prende un oggetto request (flask.Request) e restituisce il testo della risposta o qualsiasi insieme di valori che può essere trasformato in un oggetto Response utilizzando make_response.

La variabile di ambiente GCP_PROJECT viene impostata automaticamente nel runtime di Python 3.7. Nelle versioni successive del runtime, assicurati di specificarlo al momento del deployment della funzione. Consulta Configurare le variabili di ambiente.

Connessioni in uscita

Timeout delle richieste in uscita

Dopo 10 minuti di inattività si verifica un timeout per le richieste dalla funzione alla rete VPC. Per le richieste dalla tua funzione a internet, è previsto un timeout dopo 20 minuti di inattività.

Reimpostazione delle connessioni in uscita

Gli stream di connessione dalla tua funzione alla rete VPC e a internet possono essere occasionalmente interrotti e sostituiti quando l'infrastruttura di base viene riavviata o aggiornata. Se la tua applicazione riutilizza connessioni di lunga durata, ti consigliamo di configurarla in modo da ristabilire le connessioni per evitare il riutilizzo di una connessione non attiva.

Test di carico della funzione

Per misurare quante connessioni vengono eseguite in media dalla funzione, è sufficiente eseguirne il deployment come funzione HTTP e utilizzare un framework di test delle prestazioni per invocarla a determinati QPS. Una possibile scelta è Artillery, che puoi invocare con una singola riga:

$ artillery quick -d 300 -r 30 URL

Questo comando recupera l'URL specificato a 30 QPS per 300 secondi.

Dopo aver eseguito il test, controlla l'utilizzo della quota di connessione nella pagina Quota API Cloud Functions in Cloud Console. Se l'utilizzo è costantemente intorno a 30 (o un multiplo), stai stabilendo una (o più) connessioni in ogni chiamata. Dopo aver ottimizzato il codice, dovresti notare alcune (10-30) connessioni solo all'inizio del test.

Puoi anche confrontare il costo della CPU prima e dopo l'ottimizzazione nel grafico della quota di CPU nella stessa pagina.