операции
Один остаток на шесть каналов: где это ломается на самом деле
Продавать через web + Telegram + ChatGPT + WhatsApp + voice с одним пулом стока звучит просто. Ломается всегда одинаково.
Мечта: одна таблица товаров, шесть каналов продажи, остаток «просто работает». Реальная проблема не в каналах. Реальная проблема - в единице консистентности.
Race condition, в который влетает каждый мульти-канальный магазин
Два покупателя, два канала, последняя единица.
- 00:00.000 - Покупатель A на сайте открывает PDP. Сервер читает
stock = 1. - 00:00.040 - Покупатель B в Telegram спрашивает бота «ещё в наличии?». Бот читает
stock = 1. Отвечает «да». - 00:00.180 - A нажимает «Купить».
- 00:00.220 - B говорит «беру». Бот вызывает
add_to_cart. - 00:00.310 - Чекаут A пишет
stock -= 1(теперь 0). - 00:00.350 - Чекаут B пишет
stock -= 1(теперь -1).
Овер-сейл, и одному из них уйдёт извинительное письмо.
Что работает: пессимистичная блокировка строки SKU
Каждый add-to-cart или checkout, который хочет потратить сток, берёт row-level lock на строку SKU, читает текущий счёт внутри блокировки, решает, пишет, отпускает. Сайт блокируется на 50мс пока Telegram-бот доделывает декремент; второй видит правду и отказывает. Оба покупателя получают корректный ответ.
В Prisma:
await prisma.$transaction(async (tx) => {
const product = await tx.product.findUnique({ where: { id }, select: { stock: true, trackStock: true } });
if (product?.trackStock && product.stock < qty) throw new Error("OUT_OF_STOCK");
await tx.product.update({ where: { id }, data: { stock: { decrement: qty } } });
});
SQLite сериализуется по file-lock, Postgres - по row-lock; в любом случае вторая транзакция ждёт коммит первой.
Что не работает: кеши
Read-replicas, Redis-кеши, денормализованные счётчики per-канал - всё это выигрывает в latency на чтении и теряет корректность на записи. Запись остатка должна быть авторитетной; чтение можно кешировать, но кеш надо синхронно инвалидировать на каждой записи, иначе он врёт следующему каналу.
Что ломается с резервированиями
Многие платформы пытаются «резервировать» сток на старте корзины и «коммитить» на чекауте. Работает, пока корзина не была брошена и резерв никто не очистил. Через три месяца ваш authoritative-сток и отображаемый сток разошлись на 40%. Лекарство - TTL на резерв: после N минут неактивности корзины резерв автоматически освобождается.
Что добавляют AI-каналы
Каждый AI-канал (ChatGPT, Telegram, voice) - это tool consumer, который говорит с магазином. Поверхность race condition умножается, но фикс не меняется. Tool-вызовы идут в тот же атомарный декремент. AI не знает, что он в гонке; база знает.
Мерчанты, у которых не бывает овер-сейла, относятся к остатку как к банковскому счёту: каждое снятие сначала блокирует, читает внутри блокировки, решает, пишет. Скучно, быстро, корректно.