System Design Space
Граф знанийНастройки

Обновлено: 10 апреля 2026 г. в 18:40

Система бронирования отелей

средний

Публичное интервью на ArchDays 2022: календарь доступности, овербукинг и корректное бронирование под конкуренцией.

Бронирование отелей ломается не на карточке отеля, а в календаре доступности: один и тот же тип номера продаётся на диапазон дат, а ошибка быстро превращается в перепродажу.

Глава собирает в одну архитектуру инвентарь по датам, контролируемый овербукинг, подтверждение брони, отмены и рост от локального сервиса до шардированного хранения.

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

Календарь доступности

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

Окно овербукинга

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

Конкуренция за номер

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

Партиционирование по датам

Исторические даты быстро раздувают таблицы, и работа со старыми разделами становится отдельной эксплуатационной задачей.

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

Связанный кейс

Проектирование Airbnb

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

Читать обзор

Видеозапись интервью

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

Смотреть на YouTube

Постановка задачи

Система бронирования отелей

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

20 000 отелей

Порядок числа отелей в системе

1 000 000 номеров

Суммарное число номеров всех типов

Функциональные требования

Карточка отеля

Подробная информация об отеле, типах номеров и правилах проживания.

Создание брони

Выбор дат, типа номера и подтверждение бронирования онлайн.

Оплата

Интеграция с платёжной системой и фиксация результата оплаты.

Отмена брони

Возможность отменить бронь и вернуть номер в доступный инвентарь.

Ключевое бизнес-требование: допускаем продажу сверх базового лимита

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

Что не включаем в этот разбор

Авторизация и аутентификацияАдмин-панель отеляПолнотекстовый поиск по каталогуОтзывы и рейтинги

Оценка нагрузки

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

Средняя длина проживания5 ночей
Средняя заполняемость70%
Занятых номеров в день700 000
Новых бронирований в день~200 000
Средняя нагрузка~2.3 TPS

Важно: среднее значение выглядит скромно, но сезонные пики, праздники и промоакции могут дать всплеск в 10–100 раз выше. Поэтому проектировать нужно не только под обычный день, но и под короткие окна высокой конкуренции.

Базовый API

GET/v1/hotels/{hotel_id}

Получить карточку отеля и общую информацию о его предложении

GET/v1/hotels/{hotel_id}/rooms/{room_type_id}

Получить описание конкретного типа номера

POST/v1/reservations

Создать бронирование на выбранный интервал дат

{
  "hotel_id": "123",
  "room_type_id": "456",
  "start_date": "2024-03-15",
  "end_date": "2024-03-20",
  "guest_info": { ... }
}
DELETE/v1/reservations/{reservation_id}

Отменить бронирование

Что важно уточнить в API сразу

На пути бронирования и оплаты быстро становится важна : повторный запрос не должен создавать вторую бронь или повторно списывать деньги, если клиент перезапросил операцию после тайм-аута.

Эволюция модели данных

Главный вопрос здесь не в том, как хранить саму бронь, а как моделировать по типу номера и по каждой дате так, чтобы проверка и обновление оставались быстрыми.

Наивная схема: одна таблица бронирований

Reservation
├── id
├── hotel_id
├── room_type_id
├── room_id (конкретный номер)
├── start_date
├── end_date
├── status
└── guest_id

Проблема 1: проверка доступности требует COUNT по броням на каждую интересующую дату.

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

Проблема 3: при параллельном бронировании последнего номера легко получить гонку.

Практичнее: считать инвентарь по датам

RoomTypeInventory
├── hotel_id
├── room_type_id
├── date
├── total_rooms (всего номеров этого типа)
├── total_reserved (забронировано)
└── overbooking_limit (лимит овербукинга)

Constraint: total_reserved <= total_rooms * (1 + overbooking_limit)

Преимущество 1: доступность читается по одной записи на дату, а не по всему набору броней.

Преимущество 2: допустимая перепродажа становится частью модели, а не неявным правилом в сервисном коде.

Преимущество 3: счётчик можно обновлять атомарно условным UPDATE.

Обработка конкурентности

На критическом пути подтверждения брони важна : если два пользователя одновременно пытаются купить последний номер, система должна гарантировать, что подтверждение получит только допустимое число клиентов.

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

Пессимистическая блокировка

BEGIN;
SELECT * FROM room_inventory 
WHERE hotel_id = ? AND room_type_id = ? AND date = ?
FOR UPDATE;

-- Проверяем лимит и обновляем счётчик
UPDATE room_inventory SET total_reserved = total_reserved + 1 ...

COMMIT;

Плюсы

Простая логика и очень прямое соблюдение ограничений

Минусы

Блокировка строки, ниже пропускная способность, риск взаимной блокировки

Оптимистическая блокировка

-- Isolation Level: REPEATABLE READ
BEGIN;

UPDATE room_inventory 
SET total_reserved = total_reserved + 1
WHERE hotel_id = ? 
  AND room_type_id = ? 
  AND date = ?
  AND total_reserved < total_rooms * (1 + overbooking_limit);

-- Если affected_rows = 0, лимит уже исчерпан
COMMIT;

Плюсы

Выше пропускная способность, нет явной блокировки, атомарный UPDATE

Минусы

Нужны повторы операции и аккуратная обработка конфликтов

Ограничение на уровне базы данных

ALTER TABLE room_inventory 
ADD CONSTRAINT check_overbooking 
CHECK (total_reserved <= total_rooms * (1 + overbooking_limit));

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

Что разумно выбрать здесь

Для средней нагрузки порядка 2.3 TPS и пиков до 100 TPS практичнее выглядит оптимистическая блокировка: она держит хороший запас по производительности и не требует сложной схемы распределённых локов. Пессимистический вариант стоит обсуждать, если конкуренция за конкретный инвентарь становится действительно экстремальной.

Стратегии масштабирования

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

Разбиение по времени

Таблицу инвентаря удобно делить по месяцам или кварталам, потому что бронирование естественно опирается на дату проживания.

  • Исторические периоды легче архивировать и удалять
  • Запросы работают с более узким диапазоном данных
  • Проще планировать обслуживание старых разделов

Разделение по hotel_id

Когда один кластер перестаёт справляться, данные можно раскладывать по шардам на основе идентификатора отеля.

  • Проще масштабировать запись и счётчики инвентаря
  • Нагрузка отдельных сетей отелей перестаёт мешать друг другу
  • Схема распределения может опираться на согласованное хеширование

Ключевые выводы из интервью

1

Начинайте с уточнения инвариантов

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

2

Показывайте эволюцию модели

Сильный ответ редко начинается с идеальной схемы. Гораздо полезнее показать, почему наивная таблица броней перестаёт работать и как вы переходите к модели инвентаря по датам.

3

Объясняйте выбор конкурентной стратегии

Оптимистическая и пессимистическая блокировка отличаются не «правильностью», а инженерным между простотой, пиковым поведением и ценой конфликтов.

4

Масштабирование должно продолжать логику модели

Партиционирование по времени, счётчики инвентаря и схема распределения по отелям должны вытекать из календарной природы задачи, а не добавляться как абстрактный список «что ещё можно сделать».

Связанные главы

  • Airbnb - показывает соседний кейс с календарём доступности, динамическим ценообразованием и конкуренцией за ограниченный инвентарь.
  • Payment System — платёжная система - дополняет разбор темой транзакционной корректности, идемпотентности и безопасной обработки платёжных побочных эффектов.
  • Hacking the System Design Interview (краткий обзор) - помогает структурировать интервью-ответ и внятно проговаривать архитектурные компромиссы в системах бронирования.
  • Примеры задач по системному дизайну - ставит бронирование отелей в общий контекст кейсов и облегчает сравнение архитектур из разных доменов.

Чтобы отмечать прохождение, включи трекинг в Настройки