Советы и усилители; трюки

В этом документе описаны лучшие практики проектирования, реализации, тестирования и развертывания Cloud Functions .

Корректность

В этом разделе описаны общие рекомендации по проектированию и реализации Cloud Functions .

Напишите идемпотентные функции

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

Не запускать фоновые действия

Фоновая активность — это все, что происходит после завершения вашей функции. Вызов функции завершается, как только функция возвращает значение или иным образом сигнализирует о завершении, например, путем вызова аргумента callback в функциях, управляемых событиями Node.js. Любой код, запущенный после корректного завершения, не может получить доступ к ЦП и не будет выполнять никаких действий.

Кроме того, когда последующий вызов выполняется в той же среде, ваша фоновая активность возобновляется, мешая новому вызову. Это может привести к неожиданному поведению и ошибкам, которые трудно диагностировать. Доступ к сети после завершения функции обычно приводит к сбросу соединений (код ошибки ECONNRESET ).

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

Всегда удаляйте временные файлы

Локальное дисковое хранилище во временном каталоге представляет собой файловую систему в памяти. Файлы, которые вы записываете, занимают память, доступную вашей функции, и иногда сохраняются между вызовами. Невозможность явного удаления этих файлов может в конечном итоге привести к ошибке нехватки памяти и последующему холодному запуску.

Вы можете увидеть объем памяти, используемый отдельной функцией, выбрав ее в списке функций в консоли Google Cloud и выбрав график использования памяти .

Если вам нужен доступ к долгосрочному хранилищу, рассмотрите возможность подключения томов Cloud Run с томами Cloud Storage или NFS .

Вы можете снизить требования к памяти при обработке больших файлов с помощью конвейерной обработки. Например, вы можете обработать файл в Cloud Storage, создав поток чтения, пропустив его через потоковой процесс и записав выходной поток непосредственно в Cloud Storage.

Структура функций

Чтобы обеспечить единообразную установку одних и тех же зависимостей во всех средах, мы рекомендуем вам включить библиотеку Functions Framework в свой менеджер пакетов и привязать зависимость к определенной версии Functions Framework.

Для этого включите предпочитаемую версию в соответствующий файл блокировки (например, package-lock.json для Node.js или requirements.txt для Python).

Если Functions Framework не указан явно как зависимость, она будет автоматически добавлена ​​в процессе сборки с использованием последней доступной версии.

Инструменты

В этом разделе представлены рекомендации по использованию инструментов для реализации, тестирования и взаимодействия с Cloud Functions .

Местное развитие

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

Разработчики Firebase могут использовать эмулятор Cloud Functions Firebase CLI .

Используйте Sendgrid для отправки электронных писем

Cloud Functions не разрешает исходящие подключения через порт 25, поэтому вы не можете устанавливать незащищенные подключения к SMTP-серверу. Рекомендуемый способ отправки электронной почты — использовать стороннюю службу, например SendGrid . Другие варианты отправки электронной почты можно найти в руководстве по отправке электронной почты из экземпляра для Google Compute Engine.

Производительность

В этом разделе описаны лучшие практики оптимизации производительности.

Избегайте низкого параллелизма

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

Увеличение параллелизма помогает откладывать несколько запросов на каждый экземпляр, что упрощает обработку пиков нагрузки.

Используйте зависимости с умом

Поскольку функции не сохраняют состояние, среда выполнения часто инициализируется с нуля (во время так называемого холодного запуска ). Когда происходит холодный старт, оценивается глобальный контекст функции.

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

Используйте глобальные переменные для повторного использования объектов в будущих вызовах.

Нет никакой гарантии, что состояние функции сохранится для будущих вызовов. Однако Cloud Functions часто перезапускают среду выполнения предыдущего вызова. Если вы объявите переменную в глобальной области видимости, ее значение можно будет повторно использовать при последующих вызовах без необходимости повторного вычисления.

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

Node.js

console.log('Global scope');
const perInstance = heavyComputation();
const functions = require('firebase-functions');

exports.function = functions.https.onRequest((req, res) => {
  console.log('Function invocation');
  const perFunction = lightweightComputation();

  res.send(`Per instance: ${perInstance}, per function: ${perFunction}`);
});

Питон

import time

from firebase_functions import https_fn

# Placeholder
def heavy_computation():
  return time.time()

# Placeholder
def light_computation():
  return time.time()

# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()

@https_fn.on_request()
def scope_demo(request):

  # Per-function scope
  # This computation runs every time this function is called
  function_var = light_computation()
  return https_fn.Response(f"Instance: {instance_var}; function: {function_var}")
  

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

Особенно важно кэшировать сетевые подключения, ссылки на библиотеки и клиентские объекты API в глобальной области видимости. Примеры см. в разделе «Оптимизация сети» .

Уменьшите количество холодных запусков, установив минимальное количество экземпляров.

По умолчанию Cloud Functions масштабирует количество экземпляров в зависимости от количества входящих запросов. Вы можете изменить это поведение по умолчанию, задав минимальное количество экземпляров, которые облачные функции должны поддерживать в готовности к обслуживанию запросов. Установка минимального количества экземпляров снижает вероятность холодного запуска вашего приложения. Мы рекомендуем установить минимальное количество экземпляров и выполнить инициализацию во время загрузки, если ваше приложение чувствительно к задержке.

Дополнительные сведения об этих параметрах среды выполнения см. в разделе Поведение управления масштабированием .

Примечания о холодном запуске и инициализации

Глобальная инициализация происходит во время загрузки. Без него первому запросу пришлось бы завершить инициализацию и загрузить модули, что привело бы к более высокой задержке.

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

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

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

Пример предварительного разогрева асинхронной библиотеки node.js

Node.js с Firestore — это пример асинхронной библиотеки node.js. Чтобы воспользоваться преимуществом min_instances, следующий код завершает загрузку и инициализацию во время загрузки, блокируя загрузку модуля.

Используется TLA, что означает, что требуется ES6, используя расширение .mjs для кода node.js или добавляя type: module в файл package.json.

{
  "main": "main.js",
  "type": "module",
  "dependencies": {
    "@google-cloud/firestore": "^7.10.0",
    "@google-cloud/functions-framework": "^3.4.5"
  }
}

Node.js

import Firestore from '@google-cloud/firestore';
import * as functions from '@google-cloud/functions-framework';

const firestore = new Firestore({preferRest: true});

// Pre-warm firestore connection pool, and preload our global config
// document in cache. In order to ensure no other request comes in,
// block the module loading with a synchronous global request:
const config = await firestore.collection('collection').doc('config').get();

functions.http('fetch', (req, res) => {

// Do something with config and firestore client, which are now preloaded
// and will execute at lower latency.
});

Примеры глобальной инициализации

Node.js

const functions = require('firebase-functions');
let myCostlyVariable;

exports.function = functions.https.onRequest((req, res) => {
  doUsualWork();
  if(unlikelyCondition()){
      myCostlyVariable = myCostlyVariable || buildCostlyVariable();
  }
  res.status(200).send('OK');
});

Питон

from firebase_functions import https_fn

# Always initialized (at cold-start)
non_lazy_global = file_wide_computation()

# Declared at cold-start, but only initialized if/when the function executes
lazy_global = None

@https_fn.on_request()
def lazy_globals(request):

  global lazy_global, non_lazy_global

  # This value is initialized only if (and when) the function is called
  if not lazy_global:
      lazy_global = function_specific_computation()

  return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
  

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

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

Дополнительные ресурсы

Подробнее об оптимизации производительности можно узнать в видеоролике «Атлас производительности Google Cloud» Cloud Functions время холодной загрузки» .