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

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

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

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

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

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

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

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

Cloud Functions обеспечивает как минимум одно выполнение событийно-управляемой функции для каждого события, генерируемого источником событий. Способ настройки повторных попыток зависит от способа создания функции:

  • Для функций, созданных в консоли Google Cloud или с помощью API администратора Cloud Run необходимо отдельно создавать и управлять триггерами событий. Триггеры имеют стандартные параметры повторных попыток, которые можно настроить в соответствии с потребностями вашей функции.
  • Функции, созданные с помощью API Cloud Functions v2, неявно создают необходимые триггеры событий, например, темы Pub/Sub или триггеры Eventarc . По умолчанию повторные попытки для этих триггеров отключены и могут быть включены повторно с помощью API Cloud Functions v2.

Функции, управляемые событиями, созданные с помощью Cloud Run

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

Функции, управляемые событиями, созданные с помощью API Cloud Functions v2

Функции, созданные с помощью API Cloud Functions v2 (например, с помощью gcloud CLI , REST API или Terraform), будут создавать и управлять триггерами событий от вашего имени. По умолчанию, если вызов функции завершается с ошибкой, функция не вызывается повторно, а событие отбрасывается. Если включить повторные попытки для функции, управляемой событиями, Cloud Functions будет повторять неудачный вызов функции до тех пор, пока она не завершится успешно или не истечёт время повторных попыток.

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

Включить или отключить повторные попытки

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

С помощью Cloud Functions for Firebase вы можете включить повторные попытки в коде функции. Чтобы сделать это для фоновой функции, например functions.foo.onBar(myHandler); используйте runWith и настройте политику обработки сбоев:

functions.runWith({failurePolicy: true}).foo.onBar(myHandler);

Установка 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 следует использовать идентификатор события в качестве ключа идемпотентности.
  • Идемпотентность хорошо работает при доставке по крайней мере один раз, поскольку обеспечивает безопасность повторных попыток. Поэтому общая рекомендация для написания надёжного кода — сочетать идемпотентность с повторными попытками.
  • Убедитесь, что ваш код внутренне идемпотентен. Например:
    • Убедитесь, что мутации могут происходить более одного раза без изменения результата.
    • Запросить состояние базы данных в транзакции перед изменением состояния.
    • Убедитесь, что все побочные эффекты сами по себе идемпотентны.
  • Реализуйте транзакционную проверку вне функции, независимо от кода. Например, сохраните где-нибудь состояние, фиксирующее, что заданный идентификатор события уже был обработан.
  • Решайте проблемы с дублирующими вызовами функций вне очереди. Например, используйте отдельный процесс очистки, который будет выполнять очистку после дублирующихся вызовов функций.

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

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

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

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

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

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