Раздел 9 · Backend

Serverless и два кейса системного дизайна

senior~35 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Serverless и два кейса системного дизайна

Этот урок закрывает распределённую серию практикой. Сначала разберём serverless: что это, где он выигрывает и какие лимиты прячутся за маркетингом. Потом достанем шпаргалку нефункциональных требований, способ начать любую задачу системного дизайна с чисел, а не с квадратиков. И прогоним через неё два полных кейса: гарантию доставки email и систему рейтинга под нагрузкой. Оба кейса регулярно встречаются и на собеседованиях, и в проде.

Serverless: сервер есть, заботы нет

FaaS, ядро serverless: ты пишешь функцию, платформа запускает её по событию, масштабирует от нуля до тысяч копий и считает деньги за миллисекунды работы. Сервер никуда не делся, просто он не твой.

Модель исполнения

Функция просыпается от триггера: HTTP-запрос через gateway, сообщение в очереди, событие хранилища (файл загружен в S3), таймер по расписанию. Один вызов обрабатывает одно событие; параллельная нагрузка, это не потоки внутри твоего процесса, а платформа, поднимающая нужное число копий окружения. Из этого следует непривычное: у функции нет «между запросами». Нет фонового воркера, нет прогретого кеша в памяти процесса, который живёт неделями, нет демона, слушающего сокет.

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

Cold start и прогрев

Отсюда главная особенность задержек. Первый вызов собирает всё с нуля: запустить microVM, загрузить рантайм, загрузить твой код, выполнить инициализацию модулей. Это cold start, и для Node с тяжёлым бандлом он легко стоит 500 миллисекунд и больше. Второй вызов попадает в уже тёплое окружение и стартует за единицы миллисекунд.

Что с этим делают. Во-первых, худеют: меньше зависимостей, бандл через esbuild, ленивые импорты тяжёлого. Во-вторых, выбирают рантайм с быстрым стартом. В-третьих, платят: provisioned concurrency держит N окружений тёплыми постоянно, превращая cold start из случайности в статью бюджета. Заметь иронию: это уже наполовину обычный сервер, просто управляемый платформой.

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

Лимиты

У всего есть потолок, и он ближе, чем кажется: максимальное время работы вызова (минуты, не часы), память, размер запроса и ответа, размер бандла. Самый коварный лимит косвенный: соединения с базой. Тысяча параллельных вызовов, это тысяча процессов, каждый со своим коннектом, и Postgres с его сотней соединений по умолчанию ложится не от нагрузки, а от подключений. Лечится внешним пулом (PgBouncer, RDS Proxy) или serverless-драйверами, которые ходят в базу по HTTP.

Где выигрывает и где проигрывает

Выигрывает там, где нагрузка неровная или редкая: спайки (тот самый старт продаж), задачи по расписанию, webhooks, glue-код между сервисами, обработка загруженных файлов. Масштабирование от нуля и оплата за миллисекунды здесь честно экономят деньги и нервы.

Проигрывает на противоположном профиле. Стабильно высокий RPS: постоянно работающая Lambda дороже постоянно работающей VM в разы. Долгоживущие соединения: WebSocket и функция, умирающая через 15 минут, несовместимы по природе. Жёсткий p99: пока есть cold start, хвост задержек не твой. И вечный фоновый риск: vendor lock. Рецепт против последнего ты уже знаешь из урока про стрелки внутрь: хендлер делаем тонким адаптером, ядро не знает слова Lambda, и переезд на сервер сводится к замене одного слоя.

Шпаргалка НФТ: дизайн начинается с чисел

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

  1. Масштаб и нагрузка: пользователи в день, действия, пики, размеры файлов.
  2. Соотношение чтений и записей.
  3. Задержка: целевой p95 на ключевые операции.
  4. Консистентность и доступность: выбор C/A по операциям, как в уроке про CAP.
  5. SLA доступности.
  6. Объём данных.
  7. Безопасность: аутентификация, авторизация, аудит, персональные данные.
  8. Надёжность: что можно терять, что нельзя.
  9. Специфичные требования: идемпотентность, защита от злоупотреблений.
  10. Наблюдаемость: метрики, логи, трейсы.

Считаем афишу

Прикинем числа для афиши мероприятий с продажей билетов, она уже появлялась в уроке про CAP.

Чтения. 1M DAU, каждый делает в среднем 20 запросов на чтение в день (список мероприятий, карточки, места, цены):

1M пользователей x 20 запросов = 20M запросов в сутки
В сутках 86 400 секунд, для прикидок округляем до 100 000
20 000 000 / 100 000 = 200 RPS на чтение в среднем
Пиковый коэффициент x10: 2 000 RPS на чтение в пике

Удобный трюк: считать в степенях десятки. 2 x 10^7 запросов на 10^5 секунд, это 2 x 10^2, те же 200 RPS, но без калькулятора.

Записи. Покупает 1 из 100 пользователей в день:

1 000 000 / 100 = 10 000 покупок в сутки
10 000 / 100 000 секунд = 0.1 RPS в среднем
Старт продаж популярного концерта: 20 000 попыток за 10 минут
20 000 / 600 = 33 RPS на покупку в пике

Соотношение чтений к записям получилось около 2000 к 1. Это сразу решения: реплики на чтение, кеш, denormalized view. А одна покупка, это не один запрос: создание заказа и брони, колбэк платёжного провайдера, выпуск билетов.

Дальше по шпаргалке: p95 на карточку мероприятия до 250 мс; доступность 99.9%; бронь живёт 10 минут и её можно потерять, оплаченный заказ терять нельзя; повторная обработка платёжного колбэка не должна создавать дубли; защита от массовой скупки.

дальше после входа

Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.

Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.