Проектирование для бессерверной модели
Что это такое
Проектировать для бессерверной модели — значит выбирать такую форму системы, которую эта модель поощряет: небольшие независимые единицы, запускаемые событиями, состояние в управляемых хранилищах, повторные попытки скорее принимаются, чем преодолеваются, и идентичности вместо секретов. Это меньше набор сервисов, чем набор привычек, и те же привычки упрощают эксплуатацию любой распределённой системы, поэтому им стоит учиться, даже если итоговое развёртывание идёт в контейнерах.
Почему это важно
Отказы бессерверной системы сосредоточены в швах. Функция, выполнившаяся дважды из-за повторной доставки сообщения, спишет деньги дважды и может отправить два письма. Шаг, у которого тайм-аут истёк после половины работы, оставляет несогласованное состояние. Веер вызовов на тысячу штук упирается в предел параллелизма, и излишек отбрасывается. Ничего экзотического тут нет: это обычные следствия системы, у которой нет одной машины, чтобы о ней рассуждать. Заложить защиту заранее гораздо дешевле, чем обнаружить эти проблемы под нагрузкой.
Ключевые идеи
Повторные попытки делают каждый обработчик потенциальным дублем
Очередь может доставить сообщение больше одного раза, а платформа — повторить неудачный вызов. Поэтому любой обработчик, который что-то меняет, должен безопасно выполняться дважды: запишите, что работа сделана, с ключом по событию, до изменения или вместе с ним. Альтернатива — двойные списания, двойные письма и двойные строки, то есть именно те ошибки, которые подрывают доверие к проектированию на событиях.
id события -> запись идемпотентности -> если видели, остановиться
-> иначе сделать работу и записать id
Лучше оркестрованные шаги, чем одна длинная функция
Разбиение работы на шаги, каждый из которых быстро завершается, даёт повторным попыткам разумную гранулярность, делает частичный прогресс видимым и позволяет продолжить с места отказа, а не начать заново. Оркестровка на стороне платформы ведёт учёт шагов сама; цепочка «очередь плюс обработчики» делает это вручную. Правило проектирования в обоих случаях одно: результат каждого шага долговечен до начала следующего.
Пределы параллелизма — реальный потолок
Бессерверные платформы ограничивают число одновременных вызовов, и при достижении предела начинается троттлинг, а не постановка в очередь. Решение, которое предполагает неограниченный параллелизм, ломается именно тогда, когда спрос максимален. Там, где ожидается всплеск, поставьте впереди очередь, чтобы работа поглощалась со скоростью, которую выдерживают нижестоящие сервисы.
Холодные старты — строка в бюджете задержки
Стоимость инициализации платится на холодных вызовах, поэтому тяжёлый фреймворк или большое дерево зависимостей проявляются как хвостовая задержка. Небольшой обработчик, урезанные зависимости и прогрев там, где платформа это позволяет, снижают её. Цель — не устранить холодные старты, а удержать их в пределах задержки, которую выдерживает вызывающий.
Наблюдаемость обязательна, когда нет хоста
Машины, куда можно зайти, нет, поэтому доказательства приходят из структурных логов, метрик и трассировок, которые испускает сам код. Чтобы связать запрос через функцию, очередь и обращение к базе, нужен идентификатор, передаваемый по всему пути. Инструментировать код заранее намного легче, чем восстанавливать картину отказа постфактум.
Модель — это спектр, а не два полюса
Между виртуальной машиной и функцией на запрос стоят контейнеры на управляемых платформах и функции с зарезервированной ёмкостью. Каждый вариант обменивает часть эффективности масштабирования на предсказуемость. Выбор под каждую нагрузку отдельно, а не единый ответ на всё хозяйство, сохраняет преимущества в цене без лишних ограничений.
Практика
Сделайте обработчик безопасным для повторного запуска
Возьмите обработчик, который пишет строку и отправляет письмо. Добавьте запись идемпотентности с ключом по id события и опишите, что произойдёт, когда то же событие придёт второй раз.
Готово, когда: вторая доставка не делает записей и не отправляет письма, и вы можете указать запись, которая это доказала.
Поглотите всплеск очередью
Опишите функцию, которая веером обращается к нижестоящему сервису с пределом параллелизма. Вставьте очередь и потребителя, чтобы всплеск рассасывался с устойчивой скоростью.
Готово, когда: вы можете назвать предельное число одновременных вызовов и сказать, что происходит с сообщением, которое падает снова и снова.
Частые ошибки
- Считать, что обработчик выполняется ровно один раз — повторные попытки дублируют побочные эффекты; исправить: сделайте обработчик идемпотентным по id события.
- Помещать весь процесс в один вызов — при отказе он начинается с начала, а тайм-аут уже близко; исправить: разбейте на долговечные шаги.
- Пускать веер без очереди — предел параллелизма срабатывает ровно на пике нагрузки; исправить: буферизуйте работу и рассасывайте её с безопасной скоростью.
- Игнорировать цену холодного старта — хвостовая задержка растёт вместе с размером зависимостей; исправить: держите обработчик небольшим и измеряйте холодный путь.
- Отлаживать по временным печатям в лог — хоста для осмотра нет; исправить: испускайте структурные логи и передавайте идентификатор корреляции.
Что читать дальше
- Функции и управляемые среды выполнения (06-cloud-devops/05-serverless-computing/01-functions-and-managed-runtimes) — строительные блоки, из которых собирается это проектирование.
- Бессерверные вычисления (06-cloud-devops/05-serverless-computing) — модель и её компромиссы.
- Мониторинг и наблюдаемость (Prometheus / Grafana) (06-cloud-devops/18-monitoring-observability-prometheus-grafana) — метрики, логи и трассировки, единственный взгляд внутрь бессерверной системы.