Ottimizzazione della rete

La semplicità di Cloud Functions ti consente di sviluppare rapidamente codice ed eseguirlo in un ambiente serverless. Su scala moderata, il costo di esecuzione delle funzioni è basso e l'ottimizzazione del codice potrebbe non sembrare una priorità elevata. Man mano che la tua distribuzione aumenta, tuttavia, l'ottimizzazione del codice diventa sempre più importante.

Questo documento descrive come ottimizzare la rete per le proprie funzioni. Alcuni dei vantaggi derivanti dall'ottimizzazione della rete sono i seguenti:

  • Ridurre il tempo della CPU impiegato per stabilire nuove connessioni ad ogni chiamata di funzione.
  • Riduci la probabilità di rimanere senza connessione o senza quote DNS.

Mantenere connessioni persistenti

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

In questa sezione vengono trattati i seguenti scenari:

  • HTTP/S
  • API di Google

Richieste HTTP/S

Lo snippet di codice ottimizzato riportato di seguito mostra come mantenere connessioni persistenti invece di creare una nuova connessione ad ogni invocazione di funzione:

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

Pitone

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. 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 .

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 delle prestazioni possono dipendere dall'implementazione corrente di particolari librerie client.

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

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

Pitone

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 invocazione della funzione. 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 .

La variabile d'ambiente GCP_PROJECT viene impostata automaticamente nel runtime Python 3.7. Nei runtime successivi, assicurati di specificarlo nella distribuzione della funzione. Consulta Configurare le variabili di ambiente .

Test di carico della tua funzione

Per misurare quante connessioni esegue in media la tua funzione, distribuiscila semplicemente come funzione HTTP e utilizza un framework di test delle prestazioni per invocarla a determinati QPS. Una scelta possibile è Artillery , che puoi invocare con una sola 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 delle quote dell'API Cloud Functions in Cloud Console. Se l'utilizzo è costantemente intorno a 30 (o un suo multiplo), stai stabilendo una (o più) connessioni in ogni invocazione. Dopo aver ottimizzato il codice, dovresti vedere alcune (10-30) connessioni verificarsi solo all'inizio del test.

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