Базы данных
System Design: система инвентаря
Amazon Prime Day 2023: за первые 30 секунд продажи PlayStation 5 - 200,000 запросов на покупку одного товара которого на складе 50,000 единиц. Без правильного locking: либо oversell (продали 200K при 50K), либо undersell (заблокировали продажу из-за ложного lock). Amazon справился через reservation pattern + atomic decrements.
- **Shopify**: optimistic locking с version column для inventory, queue-based для flash sales с высоким contention
- **Ticketmaster**: reservation pattern с 8-минутным TTL при покупке популярных билетов
- **Booking.com**: двухфазное резервирование - hold на 15 мин при заполнении формы, confirm после оплаты
Проблемы инвентаря при высоком concurrency
Inventory management при высоком concurrency - одна из сложнейших задач в e-commerce. Amazon Prime Day 2023: 375 миллионов товаров продано за 48 часов. Главная проблема - overselling: продать товар которого нет на складе. Вторая - underselling: заблокировать продажу из-за ложной недоступности. Оба сценария стоят денег.
Ticketmaster в момент старта продаж билетов на популярный концерт получает 500,000+ одновременных запросов на один event. Без правильного locking возможен oversell - продать одно сиденье нескольким людям. Это не только финансовые потери, но и репутационный ущерб.
Почему SELECT + проверка + UPDATE - небезопасная последовательность для decrement inventory?
Optimistic и Pessimistic Locking
Pessimistic locking (SELECT FOR UPDATE) - блокировать строку при чтении, другие транзакции ждут. Гарантирует consistency, но создаёт contention при высоком concurrency. Optimistic locking - не блокировать при чтении, при записи проверить version; если version изменилась - retry. Оптимистичный подход лучше при редких конфликтах.
Shopify использует optimistic locking с version column для большинства inventory операций - conflict rate низкий, retry редки. Для flash sales (высокий conflict) Shopify переключается на queue-based подход: запросы ставятся в очередь, inventory decrements выполняются последовательно.
При каком сценарии pessimistic locking лучше optimistic?
Distributed Counters
При very high throughput (миллионы updates/sec) даже атомарный UPDATE на одну строку становится bottleneck - все транзакции сериализуются на одной строке. Distributed counters решают это через partitioned shards: вместо одного счётчика - N счётчиков, итого = сумма. Eventual consistency допустима если точность не критична.
Facebook реализовал Tao - distributed данные cache с counters для Social Graph. Лайки и просмотры - sharded counters в Redis, точность +/- несколько единиц в пределах секунды. Это acceptable tradeoff: не критично видеть 999,999 вместо 1,000,000 лайков мгновенно.
Зачем sharded counter записывает в случайный shard, а не всегда в один фиксированный?
Reservation Pattern
Reservation pattern - двухфазный подход к управлению инвентарём: RESERVE (зарезервировать товар на время оплаты) + CONFIRM или RELEASE (подтвердить или освободить). Это позволяет показать пользователю что товар 'его' пока он вводит данные карты, не рискуя oversell и не блокируя слишком долго.
Amazon использует reservation pattern для Prime Day: товар резервируется при добавлении в корзину на 30 минут. Booking.com резервирует отель на время заполнения формы (15 мин). Ticketmaster - 8 минут на покупку билета. Время резервирования - trade-off между UX и inventory utilization.
Для inventory достаточно простого SELECT ... UPDATE - база данных сама обеспечит consistency
Нужна явная стратегия: atomic UPDATE...WHERE quantity > 0 для simple decrement, reservation pattern для двухфазных операций, distributed counters для метрик
БД обеспечивает atomicity отдельной операции, но не бизнес-логику между операциями. TOCTOU race condition, overselling и long-running locks - это проблемы на уровне приложения, которые решаются через правильный design паттерн, не просто через 'использовать транзакции'.
Что произойдёт с зарезервированным товаром если пользователь бросил корзину и не оплатил?
Итоги
- **Atomic UPDATE** - UPDATE...WHERE quantity > 0 без промежуточного SELECT; rowsAffected = 0 означает отсутствие товара
- **Optimistic vs Pessimistic** - version column для нормального трафика, SELECT FOR UPDATE для flash sales с высоким conflict
- **Reservation pattern** - двухфазный подход с TTL: RESERVE (на время оплаты) -> CONFIRM или auto-RELEASE по expires_at
Связанные темы
Inventory management опирается на фундаментальные концепции concurrency:
- ACID транзакции — Atomicity гарантирует что decrement inventory и create order - либо оба, либо ни одного
- Redis данные — Distributed counters для view_count и cart_count реализуются через Redis INCRBY с sharding
- Платёжная система — Reservation pattern в inventory координируется с payment idempotency - confirm reservation только после successful payment
Вопросы для размышления
- Reservation expires в 15 минут, но пользователь успел дойти до checkout и вводит данные карты. Как продлить reservation без раздражающего 'товар снова заблокирован'?
- E-commerce хочет показывать 'Осталось 3 штуки!' в реальном времени. Как реализовать это с acceptable latency и без overselling?
- Distributed counter через Redis shards даёт eventual consistency. Для каких данных в inventory это приемлемо, а для каких - нет?