Проектирование для бессерверной модели — Примеры

Пример 1 — Обработчик, переживающий вторую доставку

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

async function handle(event) {
  const order = JSON.parse(event.body);
  await charge(order.customer, order.total);
  await sendReceipt(order.customer, order.id);
}

Решение

async function handle(event) {
  const order = JSON.parse(event.body);
  const first = await seen.putIfAbsent(event.id, { at: event.time });
  if (!first) return { skipped: "повторное событие заказа" };
  await charge(order.customer, order.total, event.id);
  await sendReceipt(order.customer, order.id);
}

putIfAbsent записывает id события до любого побочного эффекта и возвращает false, если такой id уже есть, поэтому вторая доставка ничего не списывает и не отправляет чек, а доказательством служит сохранённый id. Ошибка — записывать работу после её выполнения: сбой между списанием и записью позволит повторной попытке списать снова. Ключом делайте id события, а не id заказа: один и тот же заказ может законно прийти как два разных события.

Пример 2 — Очередь перед ограниченным нижестоящим сервисом

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

загрузка -> функция -> 1000 параллельных вызовов -> сервис (предел 20)
                          ^ троттлинг, излишек отброшен

Решение

загрузка -> очередь -> функция-потребитель (не больше 20 в работе) -> сервис
                   3 попытки -> очередь недоставленных -> оповещение

всплеск из 1000: очередь поглощает его; сервис видит не больше 20 сразу
повторный отказ: сообщение покидает очередь и видно в DLQ

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