Optimiser le réseautage

La simplicité de Cloud Functions vous permet de développer rapidement du code et de l'exécuter dans un environnement sans serveur. À échelle modérée, le coût d’exécution des fonctions est faible et l’optimisation de votre code peut ne pas sembler une priorité élevée. Toutefois, à mesure que votre déploiement évolue, l’optimisation de votre code devient de plus en plus importante.

Ce document décrit comment optimiser la mise en réseau pour vos fonctions. Certains des avantages de l’optimisation du réseau sont les suivants :

  • Réduisez le temps CPU consacré à l’établissement de nouvelles connexions à chaque appel de fonction.
  • Réduisez la probabilité de manquer de connexion ou de quotas DNS.

Maintenir des connexions persistantes

Cette section donne des exemples sur la façon de maintenir des connexions persistantes dans une fonction. Ne pas le faire peut entraîner un épuisement rapide des quotas de connexion.

Les scénarios suivants sont couverts dans cette section :

  • HTTP/S
  • API Google

Requêtes HTTP/S

L'extrait de code optimisé ci-dessous montre comment maintenir des connexions persistantes au lieu de créer une nouvelle connexion à chaque appel de fonction :

Noeud.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!")
    

Cette fonction HTTP utilise un pool de connexions pour effectuer des requêtes HTTP. Il prend un objet de requête ( flask.Request ) et renvoie le texte de réponse, ou tout ensemble de valeurs pouvant être transformé en objet Response à l'aide make_response .

Accéder aux API Google

L'exemple ci-dessous utilise Cloud Pub/Sub , mais cette approche fonctionne également pour d'autres bibliothèques clientes, par exemple Cloud Natural Language ou Cloud Spanner . Notez que les améliorations de performances peuvent dépendre de l'implémentation actuelle de bibliothèques clientes particulières.

La création d'un objet client Pub/Sub entraîne une connexion et deux requêtes DNS par appel. Pour éviter les connexions et requêtes DNS inutiles, créez l'objet client Pub/Sub dans une portée globale, comme indiqué dans l'exemple ci-dessous :

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

Cette fonction HTTP utilise une instance de bibliothèque client mise en cache pour réduire le nombre de connexions requises par appel de fonction. Il prend un objet de requête ( flask.Request ) et renvoie le texte de réponse, ou tout ensemble de valeurs pouvant être transformé en objet Response à l'aide make_response .

La variable d'environnement GCP_PROJECT est définie automatiquement dans le runtime Python 3.7. Dans les environnements d'exécution ultérieurs, assurez-vous de le spécifier lors du déploiement de la fonction. Voir Configurer les variables d'environnement .

Test de charge de votre fonction

Pour mesurer le nombre de connexions que votre fonction effectue en moyenne, déployez-la simplement en tant que fonction HTTP et utilisez un framework de tests de performances pour l'invoquer à certains QPS. Un choix possible est Artillerie , que vous pouvez invoquer avec une seule ligne :

$ artillery quick -d 300 -r 30 URL

Cette commande récupère l'URL donnée à 30 QPS pendant 300 secondes.

Après avoir effectué le test, vérifiez l'utilisation de votre quota de connexion sur la page Quota de l'API Cloud Functions dans Cloud Console. Si l'utilisation est constamment autour de 30 (ou son multiple), vous établissez une (ou plusieurs) connexions à chaque appel. Après avoir optimisé votre code, vous devriez voir quelques (10 à 30) connexions se produire uniquement au début du test.

Vous pouvez également comparer le coût du processeur avant et après l'optimisation sur le graphique du quota de processeur sur la même page.