Shopify перенесла резервирование запасов в MySQL
Shopify перенесла свою систему резервирования запасов из Redis в базу данных MySQL, которая уже содержала учетную книгу запасов.
Shopify перенесла свою систему резервирования запасов из Redis в базу данных MySQL, которая уже содержала учетную книгу запасов. Ограниченный пул индивидуально блокируемых строк единиц позволяет параллельным оформлениям заказа использовать SKIP LOCKED для выбора различных подходящих единиц. Дизайн также зависит от границ транзакций, макета первичного ключа, правил пополнения и наблюдения за временем удержания соединения при оформлении заказа.
Читать письменное издание (на английском) ↗
Что освещается в этом видео
- Старая модель счетчика количества Redis справлялась с параллелизмом, но очистка резервирований и обновление реестра MySQL не могли использовать одну локальную атомарную транзакцию.
- Замена использует одну строку на доступную единицу в пуле, ограниченном 1000 на комбинацию товара/местоположения. Пустой пул может вызвать встроенное пополнение с параллельными запросами, ожидающими за блокировкой пополнения.
- Составной первичный ключ: (shop_id, inventory_item_id, inventory_group_id, id). Shopify уменьшила накладные расходы на блокировку в своем прототипе и использовала READ COMMITTED, чтобы избежать блокировок диапазона, которые блокировали пополнение.
- Резерв удаляет строки пула перед вставкой записей резервирования. Коммит освобождает блокировки базы данных, пока сохраненное удержание сохраняется во время оплаты; успешная оплата заявляет права на реестр и удаляет резервирование в последующей атомарной транзакции.
- MySQL документирует SKIP LOCKED как несогласованное представление, которое опускает заблокированные строки. Оно не устанавливает полный подсчет запасов или справедливую очередь, применяется только к блокировкам на уровне строк и небезопасно для репликации на основе операторов.
- Пример источника включает expires_at, но Shopify не документирует алгоритм очистки истечения срока действия MySQL. Ветка выпуска/истечения срока действия видео является объяснительным требованием жизненного цикла.
- Время удержания соединения в другом коде оформления заказа было последним узким местом пропускной способности. Shopify теневым образом записывала обе системы, сравнивала результаты и постепенно переключалась с запасным вариантом аварийного переключения Redis.
Переведенная стенограмма
Переведено с оригинального английского повествования. Доступные аудио и субтитры контролируются YouTube.
Зачем переносить резервирование в MySQL?
0:00 Оформление заказа требует, чтобы Redis оставался быстрым. Shopify перенесла резервирование запасов в MySQL, используя одну строку на единицу в ограниченном пуле. Почему выбрали MySQL? Как безопасно пропускать блокировки? Это The Daily Diff, взгляд изнутри. И почему быстрые запросы все еще упирались в потолок? Shopify — это коммерческая платформа для онлайн- и офлайн-продаж. Redis — это хранилище данных в оперативной памяти.
0:21 MySQL — это реляционная база данных, и Shopify уже вела свою учетную книгу запасов там. Резервирование кратковременно удерживает товар, пока покупатель платит.
Почему два хранилища были рискованными?
0:29 Их модель Redis уменьшала счетчик товаров. Подтверждение оплаченного заказа означало обновление реестра MySQL и очистку Redis. Эти отдельные записи могли привести к двойной продаже товара или его недоступности, когда он должен быть продаваемым. Старой модели также не хватало осведомленности о местоположении. Замена должна выбирать товар из места, которое может выполнить заказ. Склад на другом континенте — отличная запись в базе данных и ужасное обещание доставки. Более ранние попытки MySQL использовали строку количества, поэтому конкурирующие оформления заказов выстраивались в очередь у той же
Что становится блокируемой единицей?
0:56 блокировки. Представьте себе бархатную веревку вокруг ячейки электронной таблицы. Добавление большего количества рабочих только удлиняет очередь. Shopify изменила блокируемый объект. Каждая доступная единица получает свою собственную строку. Блокирующее чтение пропускает единицы, которые удерживает другая транзакция, и выбирает другие подходящие единицы. Различные рабочие могут получать различные строки, в то время как горячий счетчик остается в стороне.
Что происходит, когда пул пустеет?
1:15 Этот пул ограничен тысячей строк на товар и местоположение. Пополнение осуществляется из реестра. Если он опустеет, путь резервирования пополняет его встроенно, с конкурирующими запросами, ожидающими за блокировкой пополнения. Пустой пул не означает пустой склад. Резерв удаляет выбранные строки пула, затем вставляет записи резервирования в транзакцию. Коммит освобождает блокировки базы данных.
1:35 Откат отменяет изменения. Резервирование сохраняется во время обработки платежа как сохраненное состояние. Успешный платеж заявляет права на реестр и атомарно удаляет резервирование. Блокировкам базы данных никогда не нужно нянчиться с формой оплаты. Их составной первичный ключ начинается с магазина, товара, группы, затем идентификатора единицы. Сопоставление поиска уменьшило блокировку индекса в их прототипе. Они также используют "read committed", чтобы избежать блокировок диапазона, которые блокировали пополнение
1:57 пула, и согласованный порядок таблиц, чтобы предотвратить циклические ожидания. Опубликованный пример записывает время истечения срока действия. Заброшенные платежи в конечном итоге требуют освобождения товара, иначе корзина покупок становится арендодателем. В публикации Shopify алгоритм этой очистки не указан, поэтому эта диаграмма показывает требование жизненного цикла. Вот в чем подвох.
Что упускает SKIP LOCKED?
2:14 Skip locked исключает заблокированные строки, поэтому руководство называет его результат несогласованным представлением. Он не обеспечивает ни полного подсчета запасов, ни справедливой очереди. Держите решение о доступности и правила пополнения рядом.
Где был настоящий потолок?
2:26 И этот потолок? Другой код оформления заказа слишком долго удерживал соединения с базой данных. Shopify помечала вызывающие стороны и измеряла время удержания соединения, затем очистила путь оформления заказа и пересмотрела параллелизм потоков. Быстрые запросы все еще могут стоять в очереди за пределами запроса.
Почему я бы отправил этот дизайн?
2:38 Они теневым образом записывали обе системы с авторитетным Redis, сравнивали результаты, затем постепенно переключались с аварийным выключателем. Мой вердикт — SHIP IT. Я бы отправил общую границу транзакций и этот откатываемый запуск, со всем путем оформления заказа, оснащенным инструментами. Есть вопрос по этому поводу? Напишите его в комментариях. И это все на сегодня.
2:54 Я Нико из Axrisi. Объединяйте ответственно.
Источники
- We replaced Redis with MySQL for inventory reservations—and it scaledShopify Engineering — Emilie Noel
- Simplified reservation SQL embedded in Shopify's articleShopify Engineering / CourtneySymons on GitHub Gist
- MySQL 8.0 — Locking ReadsOracle / MySQL Reference Manual
- MySQL 8.0 — Transaction Isolation LevelsOracle / MySQL Reference Manual
- What Is Shopify and How Does It Work?Shopify
- Redis quick startsRedis documentation
- What is MySQL?Oracle / MySQL Reference Manual



