Оптимизация сети

Простота Cloud Functions позволяет быстро разрабатывать код и запускать его в бессерверной среде. При умеренном масштабе стоимость выполнения функций невелика, и оптимизация кода может не казаться приоритетной задачей. Однако по мере масштабирования развертывания оптимизация кода становится все более важной.

В этом документе рассказывается, как оптимизировать работу сети для функций. Оптимизация сети дает следующие преимущества:

  • Сокращение времени процессора, затрачиваемого на установление новых исходящих подключений при каждом вызове функции.
  • Снизить вероятность исчерпания квот на подключение или DNS.

Поддержание постоянных подключений

В этом разделе приведены примеры того, как поддерживать постоянные подключения в функции. В противном случае вы можете быстро исчерпать квоты на подключения.

В этом разделе рассматриваются следующие сценарии:

  • HTTPS
  • API Google

HTTP/S-запросы

В оптимизированном фрагменте кода ниже показано, как поддерживать постоянные подключения, а не создавать новое подключение при каждом вызове функции:

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

Эта HTTP-функция использует пул подключений для выполнения HTTP-запросов. Функция принимает объект запроса (flask.Request) и возвращает текст ответа или любой набор значений, которые можно преобразовать в объект Response с помощью make_response.

Как получить доступ к API Google

В примере ниже используется Cloud Pub/Sub, но этот подход также работает с другими клиентскими библиотеками, например Cloud Natural Language или Cloud Spanner. Обратите внимание, что повышение эффективности может зависеть от текущей реализации определенных клиентских библиотек.

При создании клиентского объекта издателя-подписчика на каждое обращение выполняется одно подключение и два DNS-запроса. Чтобы избежать ненужных подключений и DNS-запросов, создайте объект клиента издатель-подписчик в глобальной области действия, как показано в примере ниже:

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

Эта HTTP-функция использует кешированный экземпляр клиентской библиотеки, чтобы уменьшить количество подключений, необходимых для каждого вызова функции. Функция принимает объект запроса (flask.Request) и возвращает текст ответа или любой набор значений, которые можно преобразовать в объект Response с помощью make_response.

Переменная среды GCP_PROJECT автоматически задается в среде выполнения Python 3.7. В более поздних версиях среды выполнения обязательно указывайте ее при развертывании функции. Подробнее о настройке переменных среды…

Исходящие подключения

Время ожидания исходящих запросов

Для запросов из функции в сеть VPC действует тайм-аут, если они не выполняются в течение 10 минут. Для запросов из вашей функции в интернет действует тайм-аут после 20 минут бездействия.

Сброс исходящего подключения

Потоки подключения из функции к сети VPC и интернету могут иногда завершаться и заменяться при перезапуске или обновлении базовой инфраструктуры. Если ваше приложение повторно использует долгоживущие подключения, рекомендуем настроить его так, чтобы оно восстанавливало подключения, чтобы избежать повторного использования недействительного подключения.

Нагрузочное тестирование функции

Чтобы узнать, сколько подключений в среднем выполняет ваша функция, разверните ее как функцию HTTP и используйте фреймворк для тестирования производительности, чтобы вызывать ее с определенным количеством запросов в секунду. Например, можно использовать Artillery, который вызывается одной строкой:

$ artillery quick -d 300 -r 30 URL

Эта команда получает указанный URL со скоростью 30 запросов в секунду в течение 300 секунд.

После выполнения теста проверьте использование квоты на подключение на странице квоты API Cloud Functions в консоли Google Cloud. Если значение постоянно находится в районе 30 (или кратно этому числу), вы устанавливаете одно (или несколько) подключений при каждом вызове. После оптимизации кода в начале теста должно быть установлено несколько подключений (10–30).

На этой же странице можно сравнить стоимость ЦП до и после оптимизации на графике квоты ЦП.