Бронирование отелей ломается не на карточке отеля, а в календаре доступности: один и тот же тип номера продаётся на диапазон дат, а ошибка быстро превращается в перепродажу.
Глава собирает в одну архитектуру инвентарь по датам, контролируемый овербукинг, подтверждение брони, отмены и рост от локального сервиса до шардированного хранения.
В интервью и инженерных обсуждениях этот кейс полезен тем, что заставляет говорить о корректности календарной модели и конкуренции за ограниченный инвентарь, а не прятаться за общими словами про маркетплейс.
Календарь доступности
Инвентарь здесь живёт не в одной цифре на товар, а в сетке дат, поэтому модель должна быть удобна для интервалов проживания, отмен и пересчёта лимитов.
Окно овербукинга
Допустимая перепродажа нужна бизнесу, но её пределы должны быть явной частью модели, а не скрытой логикой в одном сервисе.
Конкуренция за номер
Самая болезненная гонка происходит на последнем номере нужного типа, поэтому путь подтверждения брони нужно проектировать под конфликты с самого начала.
Партиционирование по датам
Исторические даты быстро раздувают таблицы, и работа со старыми разделами становится отдельной эксплуатационной задачей.
Сложность системы бронирования отелей не в каталоге, а в календарной модели доступности по диапазонам дат — это хорошо видно в публичном интервью на ArchDays 2022. Как только бизнес допускает продажу сверх базового лимита, цена ошибки растёт: архитектура должна удержать корректность счётчиков, подтверждение брони и понятные правила отмены, иначе двойная продажа последнего номера превращается в инцидент с живым гостем на ресепшене.
Связанный кейс
Проектирование Airbnb
Похожая задача с календарём доступности, динамическим ценообразованием и конкуренцией за ограниченный инвентарь.
Видеозапись интервью
Полная запись публичного разбора доступна на YouTube. Её полезно посмотреть до чтения статьи, чтобы увидеть порядок уточняющих вопросов и то, как решение постепенно усложняется по мере прояснения требований.
Смотреть на YouTubeКак защищается инвентарь бронирований
В бронировании отеля главный инвариант живёт в календаре: система должна корректно удерживать, подтверждать, отменять и пересчитывать доступность по датам.
Постановка задачи
Система бронирования отелей
Нужно спроектировать сервис для российского рынка, который показывает доступные варианты размещения, создаёт бронь на выбранный интервал дат и проводит пользователя через оплату и отмену.
Порядок числа отелей в системе
Суммарное число номеров всех типов
Функциональные требования
Карточка отеля
Подробная информация об отеле, типах номеров и правилах проживания.
Создание брони
Выбор дат, типа номера и подтверждение бронирования онлайн.
Оплата
Интеграция с платёжной системой и фиксация результата оплаты.
Отмена брони
Возможность отменить бронь и вернуть номер в доступный инвентарь.
Ключевое бизнес-требование: допускаем продажу сверх базового лимита
Бизнес сознательно допускает : часть гостей отменяет бронь или не заезжает, поэтому небольшой запас повышает загрузку отеля. Архитектура должна поддерживать этот сценарий как явное правило модели, а не как случайную побочную настройку.
Что не включаем в этот разбор
Оценка нагрузки
Сначала полезно сделать нагрузки: она быстро показывает, где действительно нужен запас по масштабу, а где не стоит преждевременно усложнять архитектуру.
Важно: среднее значение выглядит скромно, но сезонные пики, праздники и промоакции могут дать всплеск в 10–100 раз выше. Проектировать под обычный день недостаточно — ломается система именно в эти короткие окна высокой конкуренции, когда за один и тот же номер борются десятки запросов.
Базовый API
/v1/hotels/{hotel_id}Получить карточку отеля и общую информацию о его предложении
/v1/hotels/{hotel_id}/rooms/{room_type_id}Получить описание конкретного типа номера
/v1/reservationsСоздать бронирование на выбранный интервал дат
{
"hotel_id": "123",
"room_type_id": "456",
"start_date": "2024-03-15",
"end_date": "2024-03-20",
"guest_info": { ... }
}/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
Когда один кластер перестаёт справляться, данные можно раскладывать по шардам на основе идентификатора отеля.
- Проще масштабировать запись и счётчики инвентаря
- Нагрузка отдельных сетей отелей перестаёт мешать друг другу
- Схема распределения может опираться на согласованное хеширование
Ключевые выводы из интервью
Начинайте с уточнения инвариантов
Если не выяснить историю с перепродажей и диапазонами дат в самом начале, можно построить аккуратную, но неверную архитектуру.
Показывайте эволюцию модели
Сильный ответ редко начинается с идеальной схемы. Гораздо полезнее показать, почему наивная таблица броней перестаёт работать и как вы переходите к модели инвентаря по датам.
Объясняйте выбор конкурентной стратегии
Оптимистическая и пессимистическая блокировка отличаются не «правильностью», а инженерным между простотой, пиковым поведением и ценой конфликтов.
Масштабирование должно продолжать логику модели
Партиционирование по времени, счётчики инвентаря и схема распределения по отелям должны вытекать из календарной природы задачи, а не добавляться как абстрактный список «что ещё можно сделать».
Связанные главы
- Airbnb - показывает соседний кейс с календарём доступности, динамическим ценообразованием и конкуренцией за ограниченный инвентарь.
- Payment System — платёжная система - дополняет разбор темой транзакционной корректности, идемпотентности и безопасной обработки платёжных побочных эффектов.
- Hacking the System Design Interview (краткий обзор) - помогает структурировать интервью-ответ и внятно проговаривать архитектурные компромиссы в системах бронирования.
- Примеры задач по системному дизайну - ставит бронирование отелей в общий контекст кейсов и облегчает сравнение архитектур из разных доменов.
