将 Etsy 的数据库分片迁移到 Vitess
Source: Etsy Engineering
Source: …
Etsy的分片 MySQL 架构
Etsy 自 2010 年左右起就维护了一个分片的 MySQL 架构。该数据库集群包含了 Etsy 大部分在线数据,由 约 1,000 张表分布在约 1,000 个分片 组成。过去 16 年里,它显著增长:
- 总容量: > 425 TB 数据
- 吞吐量: ≈ 每秒 170 万请求
ORM 的工作方式
- Etsy 的工程师通过专有的 对象关系映射(ORM) 访问 MySQL 数据。
- 每个 MySQL 表都有对应的模型。
- 当表被分片时,其行会在不同的数据库(分片)之间进行划分。
- 每个分片拥有 相同的表结构,并包含所有行的一个独立子集。
Shardifier ID
- 分片表需要模型上一个唯一的 id 字段,称为 “shardifier id”,用于决定每条记录所在的分片。
- Shardifier ID 的设计目标是 将相关数据放在同一分片,以最小化查询所需的数据库数量(例如,同一家店铺或同一用户的所有记录都位于同一分片)。
- 大多数模型使用
shop_id或user_id作为 shardifier ID,但 使用了超过 30 种不同的选项。
传统索引数据库
- 在采用 Vitess 之前,ORM 将记录 → 分片的映射存储在 单一、未分片的 “索引” 数据库 中。
- 当创建记录时,ORM 随机选择一个分片,将映射存入索引库,随后再通过该映射进行路由。
查询流程:
- ORM 查询索引库以获取分片映射。
- ORM 将实际查询发送到相应的分片。
分片架构的优势
- 可扩展性 – 横向扩展到大量分片。
- 弹性 – 单个分片故障只影响约 1/1000 的流量。
缺点
- 手动、复杂的扩容 – 添加新分片需要数月时间。
- 单点故障 – 索引数据库若宕机可能导致整站不可用。
- 开发者摩擦 – 产品开发者必须理解并管理分片,这往往令人困惑且繁琐。
随着 Etsy 与索引数据库的规模不断扩大,索引库不可用导致的事故频率上升,这些脆弱性因此成为亟待解决的高优先级问题。
Source: …
介绍 Vitess
在 2018 年,我们在架构中加入了 Vitess(一个用于扩展、部署和管理大型 MySQL 集群的开源抽象层)。
迁移步骤
- 初始集成 – ORM 仍然告诉 Vitess 查询哪个分片,这让我们能够在将分片逻辑迁入 Vitess 之前验证新组件。
- 探索 vindexes – Vitess 的 “vindexes” 在 Vitess 内部定义分片策略,类似于我们之前的 shardifier‑id → 分片映射。
我们首先 通过创建一个可以直接使用 Vitess vindexes 的新分片集群,对几个未分片的支付表进行了扩展。该试点的成功为将我们现有的内部分片基础设施迁移到 Vitess vindexes 铺平了道路。
选择 Vindex
Vitess 附带了许多 vindex。我们重点关注那些 通过算法计算分片 的 vindex(例如 hash vindex),以消除对外部查找存储的依赖。
- ORM 的分片映射是 随机的,而不是算法决定的。
- 使用内置的算法型 vindex 将需要 重新分片所有数据,这是一项可能需要数年时间的手动工作。
我们的做法
- 编写自定义 vindex,将我们现有的分片逻辑迁移到 Vitess 中,从而在不迁移数据的情况下测试 vindex。
- 修改 ORM 的分片分配算法,使其与 Vitess hash vindex 的算法保持一致。
- 完成此更改后,新的分片映射不再需要通过 index‑DB 查找。
- 将已有映射存储在只读 SQLite 数据库中:
- 低延迟读取,体积小,可复制到每台 Vitess 服务器上,避免外部 DB 的延迟。
- 构建自定义的 SQLite 查找 vindex,从 SQLite 中读取分片信息。
- 创建混合 vindex,根据阈值在两个 vindex 之间切换:
- SQLite vindex 用于 ID ≤ 阈值(旧记录)。
- Hash vindex 用于 ID > 阈值(新记录)。
阈值设为我们更改分片分配算法后 首次创建的 ID。
将 Vindexes 引入我们的环境
在自定义 vindexes 到位后,我们可以 在不移动任何数据的情况下添加 Vitess vindexes。下一步是验证 所有现有查询(为旧版 ORM 编写的)在 Vitess 管理的分片下仍能成功执行并返回相同的结果。
兼容性问题
- Vitess 要求在 SQL 查询的
WHERE子句中出现分片标识符(shardifier ID),以便将查询路由到正确的分片。 - 旧版 ORM 并不需要 这一点;开发者通常会将分片标识符单独提供,而不是写在 SQL 语句中。
- 因此,许多现有查询 在
WHERE子句中未包含分片标识符,导致可能的路由失败。
我们通过以下方式解决了这些问题:
- 审计 所有生产查询,找出缺少分片标识符的情况。
- 更新 ORM 辅助工具,在可能的情况下自动注入所需的
WHERE子句。 - 提供迁移指南,帮助开发者调整自定义查询。
完成这些步骤后,系统即可安全地迁移到 Vitess 管理的分片,同时保持与旧架构的功能等价。
Scaling Etsy Payments with Vitess – Part 3
Incremental Migration with Vindexes
ORM 包含了十多年积累的查询,使用了多种不同的方式访问 MySQL,这使得审计每一条查询耗时巨大。为模型构建足够的上下文以测试更改也很困难,因为每个表的设计和用途差异很大。
鉴于此,我们决定采用增量方式,一次针对一个表引入 vindexes。这让我们能够:
- 在更小的范围内进行测试,确保每种数据访问模式在 Vitess 上都能成功。
- 监控 vindexes 在特定查询集合下的表现。
- 逐步暴露不兼容问题,降低大规模宕机的风险。
因为需要对数百个表重复迁移,我们把 清晰且可重复的流程 放在首位。Etsy 的实验框架让我们能够通过逐步提升使用 Vitess vindexes 的流量比例来渐进式推进每个表的改动。这使我们能够:
- 对比使用 vindexes 与 ORM 分片路由时的查询性能。
- 在出现任何问题时快速降至 0 % 流量。
从小范围开始并拥有快速回滚路径,帮助我们降低了未提前测试所有查询的风险。
Working Through Challenges: Database Transactions
在早期我们遇到了数据库事务的挑战。
- 内部分片路由: ORM 将每个分片视为独立的数据库,直接查询特定分片。
- Vitess 管理的分片: ORM 将分片当作单一数据库来查询;Vitess 只是在后台将它们呈现为独立的数据库。
这种差异导致两种方式会 创建不同的数据库连接,从而破坏了原子性保证(原子性是基于连接的)。为避免数据完整性问题,我们要求 在同一事务中写入的表必须同时迁移到 vindexes,以确保它们使用同一个连接。
理论上这很简单,但在实践中影响重大。少数表——代表最复杂、最关键的数据模型,如 receipts、listings 和 transactions——占据了大量流量。在为其中一张表准备 vindexes 时,我们发现 27 个模型(占表的 3 %)产生了三分之一的数据库流量,且全部通过事务相互关联。
尽管我们采用逐表策略,这些高风险的改动仍紧密耦合,于是我们 跨公司协作,同步推进这 27 个模型。
Reaping the Benefits: Cross‑Shard Queries
Vitess vindexes 的一个重要优势是能够跨分片查询。默认情况下,任何 不包含 shardifier ID 的查询都会 “scatter”:Vitess 会并行发送到 所有分片,对结果进行排序后返回单一结果集。
- 示例: 某模型的查询时间从 约 2 秒降至约 20 毫秒,得益于跨分片查询。
然而,在 Etsy 的规模下,若不小心将代价高的查询 scatter 到全部 1,000 个分片,可能会导致问题。为防止这种情况,我们:
- 在我们的环境中 默认禁用 scatter 查询。
- 为开发者提供一种方式,在需要时通过 ORM 明确允许 scatter。
Bulk Primary‑Key Lookups
利用 scatter 查询显著提升了前述 27 张表的批量主键查找。
- 旧方法: ORM 接收一组主键,按分片分批,然后对每个分片发起查询,最后合并结果。
- 新方法: Vitess 允许在单条语句中查询多个分片,因而我们可以 在一次查询中包含所有主键,消除按分片批处理的步骤。
这大幅减少了在多个模型上进行批量查找时发出的查询次数。
Figure: 在某模型迁移到 vindexes 期间的批量查找查询。
紫色线条表示…(此处保持原图说明格式)
注意 1 %、10 %、50 % 和 100 % 的流量使用 vindexes 发送到模型。
结论
五年、约 2,500 次拉取请求和约 6,000 次查询后,我们成功将 Etsy 的分片管理迁移到了 Vitess vindexes!
即使采用了精简的迁移流程,为 Etsy 这样规模和历史悠久的代码库更换数据库基础设施仍然是一个挑战。作为基础设施工程师,我们往往对正在修改的代码以及可能出现的故障缺乏足够的上下文。尽管如此,通过 Etsy 各团队的协作与细致测试,我们达成了目标:
- 扩展操作 不再是手动的,可以在几天而不是几个月内完成。
- 索引数据库 不再是我们分片集群的单点故障。
- 分片基础设施对开发者 隐藏,简化了数据建模和查询编写。
- 数据库性能 基本保持不变,使得变化对终端用户不可见。
- 我们 逐步 前进,必要时快速回滚,并在没有大规模数据迁移的情况下集成了 Vitess。
我们现在很高兴能够利用通过迁移到 vindexes 解锁的 Vitess 新特性,例如:
- 重新分片数据。
- 在分片之间重新平衡数据。
- 使用 Vitess 的 MoveTable 操作对之前未分片的表进行分片。
用几乎没有停机时间或对用户影响的方式替换如此关键且复杂的基础设施是一项极具挑战性的任务——但它也带来了巨大的回报。
极其令人欣慰。
Acknowledgements
该项目是 Data Access Platform 团队的共同成就:Jessica Chen、Samantha Drago‑Kramer、Hermes Garcia、Sam Kenny、David Leibovic、Kyle Leiby、Benjamin Mariscal、Juan Ortega、Adam Saponara、Wendy Sung 和 Stephanie Wu。感谢所有在工程方面为 vindex 项目做出贡献的人员。