Повторить асинхронные функции

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

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

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

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

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

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

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

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

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

Настройте повторные попытки из кода вашей функции.

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

1-го поколения

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

2-го поколения

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 секунд. Эта политика применяется к новым функциям при их первом развертывании. Она не применяется задним числом к ​​существующим функциям, которые были впервые развернуты до вступления в силу изменений, описанных в этом примечании к выпуску , даже если вы повторно развертываете эти функции.

Передовые методы

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

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

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

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

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

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

Например, этот фрагмент кода отбрасывает все события старше 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 с промисами.

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

Вот пример того, что вам следует сделать:

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

Сделайте повторяемые событийно-ориентированные функции идемпотентными.

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

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

Настройте политику повторных попыток.

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

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

Для настройки политики повторных попыток:

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

Для получения более подробной информации о настройке Pub/Sub напрямую см. документацию Pub/Sub по обработке сбоев .