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

Обновлено: 23 июня 2026 г. в 02:06

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

средний

Публичное интервью на 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 (забронировано)
└── controlled_oversell_limit (лимит контролируемой перепродажи)

Constraint: total_reserved <= total_rooms * (1 + controlled_oversell_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 (краткий обзор) - помогает структурировать интервью-ответ и внятно проговаривать архитектурные компромиссы в системах бронирования.
  • Примеры задач по системному дизайну - ставит бронирование отелей в общий контекст кейсов и облегчает сравнение архитектур из разных доменов.

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