Повторный вызов асинхронных функций

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

Почему функции, управляемые событиями, не выполняются

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

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

  • Функция содержит ошибку, и во время выполнения возникает исключение.
  • Функция не может подключиться к конечной точке сервиса или превышает время ожидания при попытке это сделать.
  • Функция намеренно вызывает исключение (например, когда параметр не проходит проверку).
  • Функция Node.js возвращает отклоненный объект Promise или передает в функцию обратного вызова значение, отличное от null.

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

Семантика повторных попыток

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

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

Как настроить повторные попытки в коде функции

С помощью Cloud Functions for Firebase можно включить повторные попытки в коде для функции. Чтобы сделать это для фонового события, например создания нового документа Firestore, задайте для параметра failurePolicy (1-е поколение) или retry (2-е поколение) значение true:

первого поколения;

exports.docCreated = functions
  .runWith({
    // retry on failure
    failurePolicy: true,
  })
  .firestore.document("my-collection/{docId}")
  .onCreate((change, context) => {
    /* ... */
  });

второго поколения;

const { onDocumentCreated } = require("firebase-functions/firestore");

exports.docCreated = onDocumentCreated(
  {
    // retry on failure
    retry: true,
  },
  "my-collection/{docId}",
  (event) => {
    /* ... */
  },
);

Если задать значение true, как показано ниже, функция будет повторять попытки при сбое.

Окно повторных попыток

Для функций второго поколения окно повторных попыток истекает через 24 часа. Для функций первого поколения срок действия составляет 7 дней. Cloud Functions повторно вызывает недавно созданные функции, управляемые событиями, используя экспоненциальную выдержку с увеличивающимся интервалом от 10 до 600 секунд. Это правило применяется к новым функциям при первом развертывании. Он не применяется к существующим функциям, которые были развернуты до вступления в силу изменений, описанных в этом примечании к выпуску, даже если вы повторно развернете функции.

Рекомендации

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

Как использовать повторные попытки для обработки временных ошибок

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

Укажите условие завершения, чтобы избежать бесконечных циклов повторных попыток

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

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

Например, этот фрагмент кода отбрасывает все события, которые произошли более 10 секунд назад:

const eventAgeMs = Date.now() - Date.parse(event.timestamp);
const eventMaxAgeMs = 10000;
if (eventAgeMs > eventMaxAgeMs) {
  console.log(`Dropping event ${event} with age[ms]: ${eventAgeMs}`);
  callback();
  return;
}

Как использовать catch с Promises

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

Вот пример того, как это можно сделать:

return doFooAsync().catch((err) => {
    if (isFatal(err)) {
        console.error(`Fatal error ${err}`);
    }
    return Promise.reject(err);
});

Как сделать идемпотентными функции на основе событий с возможностью повтора

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

  • Многие внешние API (например, Stripe) позволяют указать ключ идемпотентности в качестве параметра. Если вы используете такой API, то в качестве ключа идемпотентности следует использовать идентификатор события.
  • Идемпотентность хорошо сочетается с доставкой хотя бы один раз, поскольку позволяет безопасно повторять попытки. Поэтому при написании надежного кода рекомендуется сочетать идемпотентность с повторными попытками.
  • Убедитесь, что ваш код идемпотентен. Пример:
    • Убедитесь, что мутации могут происходить более одного раза без изменения результата.
    • Запрашивайте состояние базы данных в транзакции до изменения состояния.
    • Убедитесь, что все побочные эффекты идемпотентны.
  • Выполните проверку транзакции вне функции, независимо от кода. Например, можно сохранить состояние, чтобы зарегистрировать, что определенный идентификатор события уже был обработан.
  • Обрабатывайте дублирующиеся вызовы функций вне полосы пропускания. Например, можно создать отдельный процесс очистки, который будет удалять дублирующиеся вызовы функций.

Как настроить правила повторных попыток

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

  • Сократить период повторных попыток с семи дней до 10 минут.
  • Изменить минимальное и максимальное время ожидания для стратегии повторных попыток с экспоненциальным увеличением времени ожидания.
  • Измените стратегию повторных попыток, чтобы они выполнялись немедленно.
  • Настройте тему недоставленных сообщений.
  • Укажите максимальное и минимальное количество попыток доставки.

Чтобы настроить правила повторных попыток:

  1. Напишите HTTP-функцию.
  2. Используйте Pub/Sub API, чтобы создать подписку Pub/Sub, указав URL функции в качестве цели.

Подробную информацию о настройке Pub/Sub можно найти в Pub/Subдокументации по обработке ошибок.