Как проверить, что место свободно для бронирования?
Что проверяют
Не столько сравнение интервалов, сколько понимание гонки: два пользователя одновременно видят место свободным и оба его бронируют. Интервьюер ждёт, что вы перенесёте гарантию в базу. Следом спрашивают, что будет при нескольких экземплярах сервиса и почему synchronized не спасает.
Как отвечать
Начните с логики пересечения. Интервалы удобно хранить полуоткрытыми [start, end), тогда две брони конфликтуют, если newStart < existingEnd && existingStart < newEnd. Время храните в Instant или OffsetDateTime, а не строками: сравнение строк ломается на разных форматах и часовых поясах.
Дальше главное: схема «сначала посмотрел, потом записал» без защиты даёт двойную бронь. Варианты:
- ограничение в базе: для мест на событие это уникальный индекс
(event_id, seat_id), вторая вставка упадёт с нарушением уникальности. Для интервалов в PostgreSQL есть exclusion constraintEXCLUDE USING gist (room_id WITH =, tstzrange(start_at, end_at) WITH &&), ему нужно расширениеbtree_gist; - пессимистическая блокировка:
SELECT ... FOR UPDATEпо строке ресурса, в Spring Data JPA через@Lock(LockModeType.PESSIMISTIC_WRITE), параллельные брони ресурса встают в очередь; - оптимистическая блокировка: поле
@Version, при конфликтеOptimisticLockExceptionи повтор или понятная ошибка пользователю. Подходит, когда конфликты редкие; - временный холд: место резервируется до оплаты, например на 15 минут, записью со сроком жизни в базе или ключом в Redis через
SET key value NX EX 900.
Упомяните индекс по (resource_id, start_at) для быстрой проверки и ключ идемпотентности на создание брони, чтобы повтор запроса после таймаута не создал вторую запись.
Где спотыкаются
Ставят synchronized на метод. Это работает только в одном процессе, а за балансировщиком несколько экземпляров, и гонка остаётся.
Надеются на уровень изоляции по умолчанию. В PostgreSQL это READ COMMITTED, он не мешает двум транзакциям вставить пересекающиеся брони, а SERIALIZABLE потребует обрабатывать ошибки сериализации повтором.
Путают границы интервала: бронь до 12:00 и бронь с 12:00 должны уживаться, а при сравнении через <= они конфликтуют.