数据库垂直分片的好坏之处

banq

如何将单一数据库划分为专门的分片服务?

什么是垂直分片?
老实说,当我第一次听说“垂直分片”时,我认为这只是“拆分数据库”的一种花哨的说法。”在某种程度上,它是。但其中的微妙之处比我最初意识到的要多。

垂直分片就像整理你凌乱的车库。而不是有一个巨大的空间,工具,运动器材,节日装饰,和汽车零件都混合在一起,你创造专用区域。工具放在一个部分,运动用品放在另一个部分,季节性物品有自己的角落。

在数据库术语中,垂直分片意味着基于功能而不是数据量来拆分表。而不是一个庞大的数据库处理用户,订单,产品,支付,分析和支持票,你为每个业务领域创建单独的数据库。
这是我的想法:垂直分片是关于分离关注点,而不仅仅是分离数据。

真实世界的例子:电子商务平台转型
让我带你们看看我参与的一个真实的转变:我们有一个典型的电子商务庞然大物,它开始在自身的重量下屈服。

原名:The Monolithic Monster
一个包含47个表的PostgreSQL数据库:

  • 用户管理(用户、配置文件、首选项、会话)
  • 产品目录(产品,类别,库存,评论)
  • 订单处理(orders、order_items、付款、退款)
  • 内容管理(cms_pages,blog_posts,media_files)
  • 分析(user_events、conversion_tracking、reports)
  • 支持系统(工单、聊天记录、知识库)

痛点:

  • 部署花费了2个多小时,因为所有内容都是互连的
  • 添加一个新的产品功能需要与4个不同的团队协调
  • 数据库备份需要6个小时,并且经常失败
  • 销售活动期间的高峰流量导致整个平台瘫痪
  • 新的开发人员需要数周时间来理解数据库模式

之后:专用数据库域✨
我们将其分为6个重点数据库:
1.身份服务数据库

  • 表:用户、配置文件、身份验证、权限
  • 目的:处理所有用户身份和访问管理
  • 团队:安全和身份团队

2.目录服务数据库

  • 表:产品、类别、库存、定价
  • 目的:产品信息和可用性
  • 团队:目录管理团队

3.订单服务数据库

  • 表:orders、order_items、shipping、fulfillment
  • 目的:订单生命周期管理
  • 团队:订单处理团队

4.支付服务数据库

  • 表:付款、交易、退款、账单
  • 目的:所有金融交易
  • 团队:支付团队

5.内容服务数据库

  • 表:cms_pages,blog_posts,media,marketing_content
  • 目的:网站内容和营销材料
  • 团队:营销&内容团队

6.分析服务数据库

  • 表:事件、指标、报告、user_behavior
  • 目的:商业智能和报告
  • 团队:数据和分析团队

为什么垂直分片实际上有效
团队自治

这是我们最大的胜利。每个团队现在可以:

  • 独立部署服务
  • 选择自己的数据库优化
  • 根据他们的具体需求进行扩展
  • 在不与其他5个团队协调的情况下进行架构更改

真实的例子:分析团队需要存储大量的事件数据。他们将数据库切换到TimescaleDB(针对时间序列数据进行了优化),而支付团队则坚持使用PostgreSQL来满足ACID合规性。每个团队都得到了他们需要的东西。

性能优化⚡
不同的业务领域具有截然不同的性能特征:
高读低写(目录):

  • 设置读取副本
  • 积极的缓存策略
  • 针对产品搜索查询进行优化

高写入,复杂的交易(付款):

  • 针对写入性能进行了优化
  • 强一致性保证
  • 详细的审计日志记录

海量数据(分析):

  • 用于报告的列存储
  • 数据压缩和归档
  • 单独的OLAP优化

安全边界
我们最终可以实现适当的数据安全性:

  • 通过严格的访问控制隔离支付数据
  • 用户个人数据与营销分析分离
  • 审计日志不能被应用程序团队篡改
  • 在数据库级别满足法规遵从性要求

️技术实施之旅
第一阶段:确定自然边界️
第一步是将我们现有的表映射到业务功能。我在白板上画了这个(是的,字面上),它看起来像一个烂摊子。

我问的问题:

  • 哪些表总是一起更新?
  • 哪些团队目前“拥有”哪些功能?
  • 我们业务中的自然数据流是什么?
  • 协调中存在的痛点在哪里?

“啊哈”时刻:一起改变的桌子属于一起。如果每个订单修改都涉及库存、运输和通知,那么这些都是同一个分片的候选对象。

阶段2:设置分布式协调
这就是ZooKeeper发挥作用的地方。老实说,一开始我被ZooKeeper吓到了。它看起来像是一个复杂的,企业化的东西,对我们的需求来说是多余的。我错了

ZooKeeper实际上为我们做了什么:
把ZooKeeper想象成管弦乐队的指挥。每个数据库碎片就像一个不同的仪器部分。指挥(ZooKeeper)不播放音乐,但它让每个人保持同步,并告诉他们何时开始,停止或改变克里思。

服务发现:
Identity Service: "I'm running on db-identity-01:5432"
Catalog Service: "I'm running on db-catalog-01:5432"
Order Service: "I'm running on db-orders-01:5432"
ZooKeeper maintains this registry so services can find each other

配置管理:
Feature flag: "new_checkout_flow" = enabled
Database connection limits: max_connections = 100
Cache expiration: product_cache_ttl = 300 seconds
All services get consistent configuration from ZooKeeper

领导人选举:
Multiple instances of Order Service running ZooKeeper helps elect which instance handles batch processing Prevents duplicate work and ensures high availability

阶段3:使用Binlog Magic进行数据迁移
最可怕的部分是在不停机的情况下迁移数据。这就是MySQL的binlog(二进制日志)成为我们救星的地方。

Binlog实际上是什么:想象一下,你对你的房子做的每一个改变都有一个详细的日记-每次你移动家具,粉刷墙壁或修理东西。这就是binlog为您的数据库所做的。它会记录每一次刷新、更新和刷新操作。

我们的迁移策略:
步骤1:设置并行数据库
Original monolith database (still serving traffic) + New identity database (empty, ready) + New catalog database (empty, ready) + etc.

步骤2:历史数据迁移
Copy existing user data → Identity database Copy existing product data → Catalog database Copy existing order data → Order database

第3步:实时同步
Read binlog from monolith database Parse each change event Route changes to appropriate new database: - User update → Identity database - Product change → Catalog database - Order creation → Order database

步骤4:应用程序切换
Week 1: 10% of reads go to new databases Week 3: 50% of reads go to new databases Week 6: 90% of reads go to new databases Week 8: 100% traffic on new databases, turn off monolith

为什么这样做:binlog方法意味着我们可以在零停机时间的情况下逐步迁移。如果出了什么差错,我们可以立即退回单体。binlog在转换期间保持所有内容同步。

跨分片的挑战和解决方案
分布式事务处理问题
场景:用户下订单,要求:

  1. 减少库存(目录数据库)
  2. 创建订单记录(订单数据库)
  3. 处理付款(付款数据库)
  4. 更新用户忠诚度积分(身份数据库)

如果任何步骤失败,我们需要回滚所有内容。但现在这些是独立的数据库!

我们的解决方案--Saga模式:
我们使用一系列补偿动作,而不是一个大事务:
1. Reserve inventory → Compensation: Release inventory
2. Create order → Compensation: Cancel order
3. Process payment → Compensation: Refund payment
4. Update loyalty points → Compensation: Deduct points If step 3 fails: - Execute compensation for step 2 (cancel order) - Execute compensation for step 1 (release inventory) - User sees "Payment failed, please try again"

交叉分片
问题:“显示所有高价值客户及其最近的订单”
这需要来自身份(客户信息)和订单(订单历史记录)数据库的数据。

我们尝试的解决方案:
1.应用程序级连接(我们最终使用的)
1. Query Identity DB: Get high-value customers 2. Query Order DB: Get orders for those customers 3. Combine results in application code
2.使用数据复制读取复制副本
Replicate customer data to Order database Replicate order summary to Identity database Each shard has the data it needs for cross-shard queries
3.专用报告数据库
ETL process copies relevant data from all shards Analytics team queries this separate database Slightly stale data, but fast complex queries

监控分布式混乱
我们监控每个分片的内容:
数据库运行状况:

  • 查询响应时间
  • 连接池使用情况
  • 磁盘空间和I/O
  • 锁定等待时间
  • 复制延迟(对于读副本)

业务范围:
  • 每分钟订单数(订单碎片)
  • 每分钟产品搜索次数(目录碎片)
  • 登录失败(身份碎片)
  • 支付成功率(支付分片)

跨分片协调:
  • ZooKeeper集群健康
  • 服务发现响应时间
  • 组态传播延迟
  • 领导人选举活动

实际上帮助的警报:
红色警报(凌晨3点叫醒我):
  • 任何碎片完全被
  • 付款处理失败> 1%
  • ZooKeeper集群丢失仲裁
  • 数据一致性检查失败

黄色警报(早上检查):
  • 10分钟响应时间95%
  • 连接池利用率> 80%
  • 不寻常的跨分片查询模式
  • Binlog复制延迟> 5分钟

艰难的道路(The Hard Way)
最初不要过度分片️

我的第一反应是创建12个不同的分片,因为“粒度越大越好”,对吧?错了我们最终得到:

  • 大量的跨分片查询
  • 复杂的部署协调
  • 不值得的运营开销

更有效的方法:基于清晰的业务边界,从4-6个分片开始。你以后可以再分开。

ZooKeeper不是魔术
我认为ZooKeeper可以解决我们所有的分布式协调问题。它帮助很大,但是:

  • 您仍然需要设计应用程序来处理故障
  • ZooKeeper的崩溃是一个单点故障
  • 配置管理的好坏取决于您的变更流程

Binlog复制存在问题⚠️
字符编码问题:我们的binlog有Latin1数据,当复制到UTF8数据库时,数据会中断。始终测试您的字符集。
大型事务:大量数据导入会产生大量binlog事件,导致复制过程崩溃。我们不得不实施。
模式更改:如果目标模式不同,binlog中的ALTER TABLE语句可能会中断复制。计划架构的更改要小心。

跨分片路由总是更难
无论您如何规划分片边界,您都会发现需要来自多个分片的数据的查询。预算额外的时间为这些:

  • 报告和分析查询
  • 显示跨域数据的管理员仪表板
  • 合规和审计报告
  • 商业智能工作流

垂直分片何时有意义
你可能已经准备好垂直分片了,当:

  • 多个团队相互干扰,对数据库进行更改
  • 应用程序的不同部分具有非常不同的扩展需求
  • 您正在触及资源极限,无法再进行垂直扩展
  • 部署协调会减慢开发速度
  • 不同的域具有不同的法规遵从性或安全性要求

不要做垂直分片,如果:

  • 你仍然是一个小团队(20个开发人员)
  • 您的应用程序域没有明确分离
  • 您尚未首先优化现有数据库
  • 您没有适当的监控和警报
  • 操作复杂性超过了好处

最后的底线
垂直分片改变了我们的工程组织。我们从一个团队阻止其他四个团队进行每个数据库更改到每个团队独立移动。我们的部署频率提高了5倍,事件响应时间也大幅缩短。
但这不是一颗银子弹。操作复杂性是真实的,您将花费时间解决分布式系统问题,而不是构建特性。确保权衡是值得的,为您的具体情况。
对我来说,关键的见解是:垂直分片与组织边界和技术边界一样重要。当您的团队结构与数据架构相匹配时,一切都变得更加易于管理。

网友热评:
听起来更像是按域划分数据(有助于与模块化整体/微服务一起扩展)。

但是:
有人想用一种特别复杂的办法去存数据,还觉得自己聪明绝顶,但其实这办法简直是“搬起石头砸自己的脚”!

首先,他们没选像Google Cloud Spanner这种靠谱的数据库(可以想象成一个超级整洁的图书馆,啥都能查到,还不容易出错),也没用像Cassandra这种“随缘一致”的数据库(就像大家随便把笔记扔在一个大箱子里,过会儿再整理)。

他们非要搞个“杂交”方案,结果呢?啥好处都没捞着,反而把两种方法的缺点全背上了!

咋回事呢?他们用了一个叫“键值存储”的东西(你可以想成一个超级简陋的抽屉,啥都能塞,但找起来费劲)。这个东西本来就有点“随缘”,数据不一定啥时候能对上号。可他们还非要在这上面硬搞“分布式事务”(就像一群人在不同地方同时记账,还得保证账本一模一样)。这就得费老大劲去设计,还得自己写代码保证数据不出错。更离谱的是,这代码还得处理“回滚”(就是发现账记错了,得擦掉重来)。这不是自己给自己挖坑吗?

更惨的是,他们这个复杂的流程里,有些步骤肯定会被人偷懒砍掉(程序员嘛,总想少干点活)。结果呢?数据对不上号了!于是他们又得弄个“批处理作业”(就像半夜偷偷跑去补作业)来收拾残局。可这时候,玩家(用户)已经改了游戏数据,系统却还在用老数据算,咔嚓——整个系统就卡住了,像个死循环的bug!

咋解决?
他们又得再加一个“分布式日志系统”(就像给每一步操作都写日记,防止出错)。但这日记还得让所有改动都去查一遍,确保没啥半吊子数据。这不就是在自己造一个超级复杂的“分布式事务系统”吗?累不累啊!

更别提,他们还有四个数据库(身份、订单、支付、分析),每个都得知道玩家是谁。加个新玩家或者删个老玩家,得把这四个数据库全更新一遍!每个数据库还都关心不同的事儿,简直乱成一锅粥。为了让它们互相“聊天”,他们还得加个“消息队列”(就像个群聊,啥消息都往里扔)。结果呢?没人知道谁依赖谁,数据就像被扔进黑洞,改个格式都不知道会崩掉啥。整个系统乱得像一团麻,理都理不清!

这整个操作,简直就是在造一个“科技债务炸弹”!刚开始可能还能跑,时间一长,系统复杂得没人搞得懂,改都不敢改,动一下就崩。更别提还想把不同功能分开(比如把支付和订单分开管),完全没戏!

反观Google Cloud Spanner,简单粗暴,数据量再大也能轻松搞定,还保证数据绝对一致,啥麻烦都不用操心。就像你用一个超级智能的笔记本,写啥都能自动同步,永远不出错。

banq注:数据库切片就要按数据库操作来切片,数据库操作是CRUD,关键是读写分离,CQRS是第一步。
当需要在写操作中进一步分离时,就需要非常小心,因为写操作涉及事务概念,而事务是和DDD业务聚合是同一回事,如果不考虑业务聚合,只想从事务角度解决问题,必然遭遇CAP定理,这其中复杂性大概只有Google这样公司最后使用卫星原子时钟才能解决。也就是说,Saga之类分布式事务只是理论上行得通,实际上受到CAP定理限制,花费毕生精力打一场没有胜利的希望,何必呢?这就是科技债务炸弹的来源,你给整个系统埋伏了一个地雷,只是为了满足自己掉入认知陷阱后的执着。