Кейс: сервис записи к врачу / бронирование слотов
↑ 8.4 Backend System Design · ← Предыдущая · → Следующая
Зачем это на собесе
Задача на конкурентность: два пользователя не должны занять один слот.
Объяснение
Требования: показать расписание врача, забронировать слот, отменить/перенести, напоминания, не допустить двойной брони, выдерживать пики (открытие записи), уметь работать по часовым поясам.
Модель:
schedule(id, doctor_id, date, start, end, capacity)
slot(id, doctor_id, start_time, end_time, status) -- free / held / booked
booking(id, slot_id, patient_id, status, created_at)
UNIQUE (slot_id) WHERE status IN ('held','booked') -- защита от двойной брониЗащита от двойного бронирования:
| Способ | Пример |
|---|---|
| Уникальный индекс | CREATE UNIQUE INDEX ... ON booking(slot_id) WHERE status <> 'cancelled'; нарушение = «слот занят» |
| Условное обновление | UPDATE slot SET status='booked' WHERE id=@id AND status='free' (проверить число затронутых строк) |
| Оптимистичная блокировка | версия строки |
SELECT ... FOR UPDATE | пессимистичная блокировка слота |
| Redis-лок на слот | доп. защита при частых конфликтах; не единственная |
Удержание слота (hold): пользователь выбирает слот → он «удерживается» на 5–10 минут (TTL), затем подтверждение/оплата; фоновая задача возвращает просроченные удержания.
sequenceDiagram participant U as Пациент participant A as API participant DB as БД U->>A: выбрать слот A->>DB: UPDATE slot SET status='held', held_until=now()+10m WHERE id=@id AND status='free' DB-->>A: 1 строка → удержан U->>A: подтвердить A->>DB: UPDATE ... SET status='booked' WHERE id=@id AND held_by=@u AND held_until>now() A-->>U: запись создана + событие BookingConfirmed
Прочее: расписание кэшируется (Redis) с инвалидацией по изменению; напоминания — планировщик и очередь уведомлений (см. Кейс: система уведомлений); аудит и персональные данные пациентов (шифрование, аудит доступа); часовые пояса хранятся как UTC + зона клиники; интеграция с МИС (см. FHIR).
Нюансы и подводные камни
- Проверка «слот свободен» отдельным SELECT перед INSERT — классическая гонка.
- Пики при открытии записи: очередь ожидания, rate limiting.
- Двусторонняя интеграция с внешней системой расписаний: конфликты и сверка.
- Отмена и перенос — тоже конкурентные операции.
- Права: пациент видит только свои записи.
Практика
- Реализуйте бронирование с уникальным индексом и проверьте гонку 100 параллельных запросов.
- Добавьте удержание слота с TTL и фоновую очистку.
- Опишите поведение при одновременных бронировании и отмене.
Вопросы с ответами
Как исключить двойную бронь?
Уникальное ограничение в БД и условное обновление статуса; кэши и блокировки — только дополнение.
Зачем удержание слота?
Даёт пользователю время завершить действие, не блокируя слот навсегда.