从双库游离到 ACID:Redis 预留机制的数据陷阱
2025 年黑色星期五,Shopify 创下每分钟 510 万美元的销售额纪录,同比增幅达 11%。在这个承载全美超过 14% 电商交易量的平台上,处理核心库存预留的核心组件是一套经过架构重构的 MySQL 关系型数据库。
在早期架构中,Shopify 采用 Redis 维护库存预留计数值,利用 DECR 与 INCR 指令处理高并发扣减,真实的库存账本保存在 MySQL 中。这种架构将预留状态与持久化账本强行拆分到两个存储介质,导致系统无法获得跨库的 ACID(Atomicity, Consistency, Isolation, Durability)事务保障。当大促期间发生突发抢购时,Redis 扣减成功但 MySQL 写入失败的情况频发,造成超卖或少卖问题。
为了保证数据的最终一致性,团队过去不得不编写复杂的异步补偿逻辑。这些补偿代码不仅增加了运维成本,还在峰值流量下引入了不可预测的延迟。数据源游离带来的运维代价超出了专用缓存带来的性能收益。
图:Redis 到 MySQL 的架构迁移示意图。来源:Shopify Engineering
行级互斥与有界池:现代 MySQL 承载高并发的设计
Shopify 工程团队引入了 one row per unit(一行记录对应一个可售单元)的模型设计。预留操作直接在 MySQL 事务中执行 SELECT ... FOR UPDATE SKIP LOCKED 查询,跳过已被其他事务锁定的记录并安全锁住可用行。
这种设计将库存预留与实际账本更新统一在同一个数据库事务内部。利用现代 MySQL 的行级互斥特性,系统直接消除了跨存储数据不一致的隐患。
为了防止热点商品的记录行无限膨胀,系统引入了有界池(bounded pool)机制,限制每个商品与履约点组合在可售表中的上限为 1000 行。当池内可用单元耗尽时,系统触发内联补货逻辑从主账本划拨库存,并采用单锁机制防止并发请求引发惊群效应(thundering herd)。
受 37signals 数据库负载分配方案的启发,Shopify 将热点区域划分为独立小池并配合跳过锁机制。这种技术路线展示了关系型数据库在高并发互斥场景下的高吞吐能力。
图:「水位线 vs 竞赛」库存 meme 图。来源:Shopify Engineering
锁粒度与隔离级别优化:消灭死锁与索引锁定开销
在将互斥压力下沉至数据库引擎的过程中,传统的数据库设计会迅速暴露锁竞争问题。Shopify 团队首先将自增主键重构为复合主键 (shop_id, inventory_item_id, inventory_group_id, id)。自增主键会让 InnoDB 引擎在修改时同时锁定二级索引和聚簇索引,引发双重锁等待;复合主键成功将单次预留的锁开销降到了单行级别。
团队将数据库事务隔离级别从默认的 REPEATABLE READ 调整为 READ COMMITTED。这一改变避开了间隙锁(Gap Lock)以及伪记录 supremum 对补货事务的无谓阻塞,降低了高并发死锁概率。
在写路径的交互设计上,团队统一了全局锁顺序。预留阶段先对 reservation_units 执行 DELETE 再向 reserved_quantities 执行 INSERT,而结账锁定阶段仅针对 reserved_quantities 进行修改。通过规范化锁申请路径与使用 UNION ALL 批量化网络往返,数据库死锁异常被成功消除。
假象与真凶:连接池占用才是吞吐上限的决定者
当压测流量推至峰值时,数据库集群出现了线程排队、CPU 使用率尖峰以及 ProxySQL 连接池耗尽的情况。常规经验往往会将此类现象归咎于数据库 CPU 算力不足或查询效率低下,但性能监控指标显示查询的 P90 延迟依然保持在毫秒级低位,单条 SQL 性能已优化完毕。
为了寻找真正的瓶颈,团队在应用层 SQL 中插入了注释标签 /* conn_tag:checkout_completion */。ProxySQL 借助这些标签解析并统计不同业务进程对数据库连接的持有时长。
观测数据揭示了性能排队的真实原因:结账完成路径中的非库存业务代码在持有一条 MySQL 连接的同时,同步发起了耗时较长的外部调用。数据库连接在等待外部响应的过程中处于无意义的占用状态,导致连接池迅速枯竭并引爆上游排队。
团队对结账路径进行了重构,解耦了外部调用与数据库事务连接。这项清理直接让主库读取量下降 50%,事务总量减少 33%。结合对多年前保守设置的 InnoDB 线程并发数(innodb_thread_concurrency)的重新调优,系统性能得到了全面释放。在 2025 年黑五极速抢购期间,写节点的 CPU 使用率全程保持在 50% 以下,读节点使用率低于 16%,留出了充足的安全冗余。
影子模式与回退机制:大型系统无缝平滑切库
在生产环境进行底层存储引擎的替换,需要极高等级的风控手段。Shopify 团队采用了影子模式(shadow mode)进行渐进式迁移。系统在后台进行双写,此时 Redis 仍然作为权威数据源(source of truth),MySQL 在影子链路中接收真实流量并实时比对预留结果与性能指标。
在影子模式平稳运行并完成数据校验后,团队将权威数据源安全切换至 MySQL。迁移过程保留了可随时启用的紧急开关(kill switch),确保在出现未知异常时能秒级回退至旧架构。
整套切换过程以 Pod 为单位按区域逐步推进,优先从低流量 Pod 开始验证。这种控制风险的发布节奏,保证了全美电商峰值期间业务的绝对平稳。
架构反思:数据库能力演进重塑选型边界
Shopify 的架构重构证明,在现代 MySQL 具备 SKIP LOCKED 特性后,关系型数据库完全能够接管过去被认为属于 Redis 等专用缓存的高吞吐互斥工作负载。高并发架构设计的瓶颈往往集中在连接管理与长事务对资源的无效占用上。
盲目引入专用缓存中间件看似降低了单次查询的延迟,却付出了数据一致性破裂与系统复杂度飙升的代价。「先观察再优化」的度量方法,远比「先选型再硬扛」的经验主义更能直击复杂工程问题的本质。
参考链接:
- Shopify Engineering: We replaced Redis with MySQL for inventory reservations—and it scaled
- 37signals Database Load Allocation Patterns