Optimizar las herramientas de redes

La simplicidad de Cloud Functions te permite desarrollar código rápidamente y ejecutarlo en un entorno sin servidores. A una escala moderada, el costo de ejecutar funciones es bajo, y puedes considerar que optimizar tu código no es una prioridad importante. Sin embargo, a medida que tu implementación aumenta su escala, optimizar tu código se vuelve cada vez más relevante.

En este documento, se describe cómo optimizar las Herramientas de redes para tus funciones. Algunos de los beneficios de optimizar las Herramientas de redes son los siguientes:

  • Reduce el tiempo de CPU que se usa para establecer conexiones nuevas en cada llamada a función.
  • Reduce la probabilidad de agotar las cuotas de DNS o de conexión.

Cómo mantener conexiones continuas

Esta sección muestra ejemplos sobre cómo mantener conexiones continuas en una función. No hacerlo puede causar que agotes tu cuota de conexión rápidamente.

En esta sección, se abordan los siguientes casos:

  • HTTP/S
  • API de Google

Solicitudes HTTP(S)

El fragmento de código optimizado de más abajo muestra cómo mantener la coherencia de las conexiones en lugar de crear una conexión nueva para cada invocación de función:

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

Esta función de HTTP usa un grupo de conexiones para realizar solicitudes HTTP. Toma un objeto de solicitud (flask.Request) y muestra el texto de la respuesta, o cualquier conjunto de valores que se pueda convertir en un objeto Response con make_response.

Acceso a las API de Google

En el siguiente ejemplo, se usa Cloud Pub/Sub, pero este enfoque también sirve para otras bibliotecas cliente, como Cloud Natural Language o Cloud Spanner. Ten en cuenta que las mejoras de rendimiento pueden depender de la implementación actual de algunas bibliotecas cliente en particular.

Si se crea un objeto de cliente de Pub/Sub se genera una conexión y dos consultas de DNS por invocación. Para evitar consultas de DNS y conexiones innecesarias, crea el objeto de cliente de Pub/Sub en alcance global, como se indica en el siguiente ejemplo:

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

Esta función de HTTP usa una instancia de biblioteca cliente almacenada en caché para reducir la cantidad de conexiones que se requieren por invocación de la función. Toma un objeto de solicitud (flask.Request) y muestra el texto de la respuesta, o cualquier conjunto de valores que se pueda convertir en un objeto Response con make_response.

La variable de entorno GCP_PROJECT se establece automáticamente en el entorno de ejecución de Python 3.7. En tiempos de ejecución posteriores, asegúrate de especificarlo en la implementación de la función. Consulta Configura las variables de entorno.

Conexiones salientes

Tiempos de espera de solicitud saliente

Hay un tiempo de espera después de 10 minutos de tiempo de inactividad de las solicitudes de tu función a la red de VPC. Para las solicitudes de tu función a Internet, hay un tiempo de espera después de 20 minutos de tiempo de inactividad.

Se restablece la conexión saliente

Las transmisiones de conexión de tu función a Internet y red de VPC pueden finalizarse y reemplazarse de forma ocasional cuando se reinicia o actualiza la infraestructura subyacente. Si tu aplicación vuelve a usar conexiones de larga duración, te recomendamos entonces que configures tu aplicación para restablecer las conexiones para evitar la reutilización de una conexión inactiva.

Prueba la carga de tu función

Para medir cuántas conexiones ejecuta en promedio tu función, solo debes implementarla como una función de HTTP y usar un marco de trabajo de prueba de rendimiento para invocarla con una cierta cantidad de QPS. Una opción posible es Artillery, que puedes invocar con una sola línea:

$ artillery quick -d 300 -r 30 URL

Este comando recupera la URL determinada a 30 QPS por 300 segundos.

Después de ejecutar la prueba, verifica el uso de tu cuota de conexión en la página de cuotas de la API Cloud Functions en Cloud Console. Si el uso se mantiene alrededor de 30 (o sus múltiplos), estás estableciendo una (o varias) conexiones en cada invocación. Después de optimizar el código, deberías ver que se ejecutan unas pocas conexiones (entre 10 y 30) solo al comienzo de la prueba.

También puedes comparar el costo de CPU antes y después de la optimización en el gráfico de cuota de CPU, en la misma página.