Базы данных

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 это приемлемо, а для каких - нет?

Связанные уроки

  • dist-07-transactions
System Design: система инвентаря

0

1

Войти