Otimizando a rede

A simplicidade do Cloud Functions permite desenvolver código rapidamente e executá-lo em um ambiente sem servidor. Em escala moderada, o custo de execução de funções é baixo e a otimização do seu código pode não parecer uma alta prioridade. No entanto, à medida que sua implantação aumenta, a otimização do seu código se torna cada vez mais importante.

Este documento descreve como otimizar a rede para suas funções. Alguns dos benefícios da otimização da rede são os seguintes:

  • Reduza o tempo de CPU gasto no estabelecimento de novas conexões em cada chamada de função.
  • Reduza a probabilidade de ficar sem conexão ou cotas de DNS.

Mantendo conexões persistentes

Esta seção fornece exemplos de como manter conexões persistentes em uma função. Não fazer isso pode resultar no esgotamento rápido das cotas de conexão.

Os seguintes cenários são abordados nesta seção:

  • HTTP/S
  • APIs do Google

Solicitações HTTP/S

O trecho de código otimizado abaixo mostra como manter conexões persistentes em vez de criar uma nova conexão a cada invocação de função:

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

Pitão

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 função HTTP usa um pool de conexões para fazer solicitações HTTP. Ele pega um objeto de solicitação ( flask.Request ) e retorna o texto de resposta ou qualquer conjunto de valores que possa ser transformado em um objeto Response usando make_response .

Acessando APIs do Google

O exemplo abaixo usa o Cloud Pub/Sub , mas essa abordagem também funciona para outras bibliotecas de cliente, por exemplo, Cloud Natural Language ou Cloud Spanner . Observe que as melhorias de desempenho podem depender da implementação atual de bibliotecas clientes específicas.

A criação de um objeto cliente do Pub/Sub resulta em uma conexão e duas consultas DNS por invocação. Para evitar conexões e consultas DNS desnecessárias, crie o objeto cliente do Pub/Sub no escopo global, conforme mostrado no exemplo abaixo:

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

Pitão

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 função HTTP usa uma instância de biblioteca cliente armazenada em cache para reduzir o número de conexões necessárias por chamada de função. Ele pega um objeto de solicitação ( flask.Request ) e retorna o texto de resposta ou qualquer conjunto de valores que possa ser transformado em um objeto Response usando make_response .

A variável de ambiente GCP_PROJECT é definida automaticamente no tempo de execução do Python 3.7. Em tempos de execução posteriores, certifique-se de especificá-lo na implantação da função. Consulte Configurar variáveis ​​de ambiente .

Testando sua função com carga

Para medir quantas conexões sua função executa em média, basta implantá-la como uma função HTTP e usar uma estrutura de teste de desempenho para invocá-la em determinados QPS. Uma escolha possível é a Artilharia , que você pode invocar com uma única linha:

$ artillery quick -d 300 -r 30 URL

Este comando busca o URL fornecido a 30 QPS por 300 segundos.

Depois de realizar o teste, verifique o uso da sua cota de conexão na página de cota da API Cloud Functions no Console do Cloud. Se o uso for consistentemente em torno de 30 (ou múltiplos), você estará estabelecendo uma (ou várias) conexões em cada invocação. Depois de otimizar seu código, você verá algumas (10 a 30) conexões ocorrerem apenas no início do teste.

Você também pode comparar o custo da CPU antes e depois da otimização no gráfico de cota de CPU na mesma página.