Состояние корзины в e-commerce checkout
Предпосылки: Клиенты и соединения, HASH.
В процессе оформления заказа корзина содержит несколько связанных полей: список товаров, адрес доставки, выбранный способ оплаты, промокод, этап checkout. Фронтенд обновляет поля по одному — пользователь выбрал доставку, затем ввёл промокод, затем подтвердил оплату.
Хранить каждое поле как отдельный STRING-ключ (cart:42:items, cart:42:address, cart:42:promo) — дорого по памяти (~70 байт overhead на ключ) и неудобно: чтение всей корзины требует нескольких GET. JSON-строка в одном STRING-ключе требует прочитать всё, десериализовать, изменить одно поле и записать обратно — это read-modify-write, а значит гонка между процессами без блокировки.
HASH позволяет обновлять отдельные поля атомарно (HSET), читать всё за один HGETALL, и платить overhead один раз за весь объект:
class CheckoutCart
def initialize(redis_pool, order_id)
@redis_pool = redis_pool
@key = "cart:#{order_id}"
end
def set_shipping(address, method)
@redis_pool.with do |r|
r.hset(@key, "shipping_address", address.to_json, "shipping_method", method)
end
end
def apply_promo(code)
@redis_pool.with do |r|
r.hset(@key, "promo_code", code)
end
end
def advance_step(step)
@redis_pool.with do |r|
r.hset(@key, "step", step)
end
end
def snapshot
@redis_pool.with do |r|
r.hgetall(@key)
end
end
def finalize!(ttl: 3600)
@redis_pool.with do |r|
r.expire(@key, ttl)
end
end
endДва Puma-процесса могут одновременно вызвать set_shipping и apply_promo — каждый пишет в своё поле, конфликта нет. С JSON в STRING второй процесс перезаписал бы изменения первого. HINCRBY работает для числовых полей (количество товаров) без read-modify-write.