Представьте: намечается крупная распродажа, маркетологи запустили хайповый дроп, и на сайт одновременно залетают десятки тысяч покупателей. Сервера плавятся, Redis захлебывается в попытках удержать баланс между скоростью и точностью, а разработчики судорожно тушат пожары в распределенных транзакциях. Знакомо? Инженеры Shopify решили эту классическую боль радикально — они просто выбросили Redis из критического пути резервирования товаров и доверили всё «скучной» MySQL. И, как ни странно, победили.
Обсуждение на Reddit и в инженерных кругах статьи от команды Shopify произвело эффект разорвавшейся бомбы: один из крупнейших e-commerce гигантов мира полностью перенес систему резервирования запасов (oversell protection) с Redis на MySQL. Пост об этом мгновенно набрал тысячи апвоутов, заставив NoSQL-евангелистов судорожно вспоминать, как вообще пишутся эти ваши JOIN. Разработчики задаются вопросом: как классическая реляционная БД смогла обойти in-memory хранилище на сверхвысоких нагрузках?
Давайте разберем технические детали этого перехода, архитектурные проблемы распределенных систем и новые возможности MySQL 8, которые сделали это возможным. А начнем с того, как проверенная годами архитектура превратилась в ночной кошмар для дежурных инженеров.
Архитектурный тупик: почему связка Redis + MySQL дала трещину
До перехода система резервирования товаров в Shopify была разделена на два контура:
- Redis (быстрый буфер): отвечал за защиту от оверселлинга (oversell protection). Когда тысячи пользователей одновременно пытались купить последний экземпляр хайпового товара во время флеш-сейла, Redis быстро распределял «аллокации» благодаря своей однопоточной природе и атомарным операциям.
- MySQL (источник правды): хранинил постоянный реестр инвентаря (inventory ledger). Сюда записывались окончательные финансовые и складские транзакции после того, как платеж пользователя был успешно обработан.
На первый взгляд, классическая и рабочая схема. Но дьявол крылся в деталях, а именно — в шаге подтверждения заказа (claim step). Когда платеж проходил успешно, системе требовалось перманентно списать товар в MySQL и одновременно удалить или обновить временный резерв в Redis.
Это классическая проблема двойной записи (Dual-Write Problem) в распределенных системах. Невозможно гарантировать атомарность обновления двух независимых баз данных без тяжелых распределенных транзакций (2PC), которые критически снижают производительность.
Если MySQL обновлялся, а Redis падал (или наоборот), возникал рассинхрон. Инженерам Shopify приходилось писать сложные механизмы примирения данных (reconciliation) — занятие по уровню боли сравнимое с ручным разрешением merge-конфликтов в легаси-коде в пятницу вечером — запускать фоновые воркеры для сверки и тратить огромные ресурсы на поддержание консистентности. Операционная сложность поддержки этой системы росла лавинообразно.
Но почему вообще возникла эта сложная двухконтурная схема? Почему нельзя было сразу доверить всё надежной реляционной базе? Ответ кроется в фундаментальном ограничении классического SQL, о которое спотыкался каждый, кто проектировал высоконагруженный интернет-магазин.
Главная проблема SQL-баз: конкуренция за строки (Row Contention)
Обычно таблица инвентаря выглядит так: одна строка на каждый товар с колонкой количества (quantity). При покупке выполняется запрос:
UPDATE inventory_items
SET quantity = quantity - 1
WHERE id = 123 AND quantity > 0;Во время крупных распродаж тысячи транзакций пытаются обновить одну и ту же строку одновременно. СУБД вынуждена накладывать эксклюзивную блокировку (exclusive lock) на эту строку. В результате:
- Транзакции выстраиваются в очередь.
- Время отклика (latency) растет экспоненциально.
- Пул соединений к базе данных мгновенно забивается.
- Система падает под лавиной таймаутов (Lock Wait Timeout).
Именно из-за этой проблемы (row contention) разработчики годами избегали реляционных БД в сценариях с высокой конкуренцией, отдавая предпочтение in-memory решениям, даже если это означало регулярные ночные созвоны из-за рассинхронизации данных.