如何设计一个数据系统

2023-12-061 引用

在给定工作负载的情况下,选择一组数据模型 + 存储与执行结构 + 并发与分布式策略,对性能/成本/灵活性/复杂度做权衡,以逼近可靠性/可扩展性/可维护性的目标。

整个框架的四个层次:

flowchart TB
    why["目标 Why(可靠/扩展/维护)"]
    workload["工作负载 Workload(面对什么)"]
    how["手段 How(数据模型/执行路径/分布式策略)"]
    tradeoff["权衡 Tradeoff(正确/性能/灵活)"]
    why -- 约束 --> workload -- 驱动 --> how -- 暴露 --> tradeoff
  • 目标(Why)= 可靠性/可扩展性/可维护性
  • Workload(What kind)= 读写形态/数据分布/体量/SLO/正确容忍度
  • 手段(How)= 数据模型+执行结构+分布式策略(+演化策略)
  • 权衡(Where it hurts)= 正确性 vs 性能 / 读 vs 写 vs 空间 / 灵活性 vs 约束

下文按这四层依次展开。

三个目标(要什么)

  • 可靠性 Reliability:在硬件故障、软件 bug、人为错误下仍能正确工作
  • 可扩展性 Scalability:负载(数据量、读写吞吐、并发)增长时,性能可控地退化或线性扩展
  • 可维护性 Maintainability:让运维、演进、接手的人活下去(可观测、可演化、低心智负担)

三者不是"挑一个",是全都要。它们是约束,不是选项。

工作负载(面对什么)

不弄清工作负载,所有手段的选择都是空中楼阁。至少要回答:

  • 读写比与形态:读多 / 写多 / 均衡?点查、范围扫描、全表聚合、多表关联?
  • 数据分布:均匀 / 长尾 / 热点?key 空间是否连续?
  • 体量与增速:总数据量、单条大小、写入 QPS、年增长
  • 时序特征:突发(秒杀) vs 平稳;时间局部性(最近写入最常被读)
  • SLO:P50/P99 延迟、可用性目标(几个 9)、一致性窗口
  • 正确性容忍度:能丢多少(RPO)、能停多久(RTO)、能不一致多久?

Workload 决定了手段的"梯度方向":

Workload 特征 倾向的手段
读多写少 + 点查 哈希索引 / KV / 缓存 / 读副本
写多 + 顺序写入 LSM / 日志结构 / 批量提交 / append-only
范围扫描 / 排序读 B-Tree / 按 key 排序分区 / 列存
写多 + 强热点 哈希分区 + 随机后缀 / 本地聚合 / 限流
时序数据 + TTL TSDB / 按时间分区 / 冷热分层
强一致 + 跨分区事务 单主+ 2PC / Spanner 式 TrueTime
高可用 + 容忍不一致 多主 / 无主 + Quorum / CRDT

四层手段(怎么做)

把手段堆成列表会丢掉谁服务于谁,按职责分层才能看清依赖关系:上层定义做什么,下层支撑怎么做到

  1. 抽象层: 数据模型 + 查询语言 → 决定怎么用
  2. 执行层: 存储结构 + 索引 / 访问路径 → 决定多快
  3. 分布层: 复制 + 分区 → 决定多大 & 多稳
  4. 演化层: 编码格式 + schema 演进 → 决定能不能活久

1. 抽象层(对用户暴露)—— 决定怎么用

  • 数据模型:关系 / 文档 / 图 / KV / 列族 / 时序
  • 查询语言:声明式(SQL、Cypher、GraphQL)vs 命令式(KV API、MapReduce)
  • 模型与语言是一体的:模型限定了能自然表达的形状,语言限定了能高效问的问题

2. 执行层(性能核心)—— 决定多快

  • 存储结构:B-Tree(读优)、LSM(写优)、列存(OLAP / 压缩)、倒排(搜索)、堆 + 索引
  • 索引 / 访问路径:主键、二级索引、覆盖索引、多列索引;点查、范围扫描、全表扫描
  • 这一层直接决定读放大 / 写放大 / 空间放大三角

写优先的引擎(LSM / 日志结构)会把数据路径拆成两条:前台提交路径负责低延迟与持久性,后台整理路径异步把数据变成可查询、可归档的形态:

sequenceDiagram
    autonumber
    box rgb(225,240,255) 前台提交路径 commit path(同步 · 决定延迟与持久性)
        participant C as Client
        participant E as 存储引擎 (Leader)
        participant W as WAL / replicated log
        participant D as 本地 SSD
        participant F as Follower (副本)
    end
    box rgb(255,244,220) 后台整理路径 materialization path(异步 · 决定存储形态与成本)
        participant M as 内存状态 (MemTable / Buffer Pool)
        participant B as 后台任务 checkpoint / flush / compaction
        participant T as SSTable / segment / snapshot
        participant O as 对象存储 S3
    end

    Note over C,F: ① 提交路径:写入先过日志,返回前必须 durable
    C->>E: 写入请求 (put / txn commit)
    E->>W: append log record + fsync
    W->>D: 顺序写日志段
    W-->>E: 本地落盘确认(单机持久性)
    E->>F: 复制日志记录
    F-->>E: quorum ack(分布式持久性)
    E->>M: 更新内存状态(无 I/O,立即可读)
    E-->>C: 返回提交成功(commit ack)

    Note over M,O: ② 整理路径:提交之后才动,慢一点没关系
    B->>M: checkpoint / flush(周期触发)
    M->>T: 刷出不可变快照(SSTable / segment)
    B->>T: compaction(合并 / 排序 / 清理)
    B->>O: 上传快照到对象存储(廉价归档)
    B->>W: 截断已 checkpoint 的日志

两条路径的本质分工:提交路径只做顺序写(日志追加 + fsync,或复制到 quorum),把随机写和重活全甩给后台路径批量消化;写放大 / 空间放大因此发生在后台,不挡前台延迟。本地 SSD 与 quorum 满足其一即可保证 durable。

3. 分布层(规模 & 可用性)—— 决定多大 & 多稳

  • 复制:单主 / 多主 / 无主;同步 / 半同步 / 异步;副本拓扑(链式、星型、Quorum)
  • 分区:按范围 / 按哈希 / 一致性哈希;分区再平衡;二级索引的本地 vs 全局策略
  • 这一层把单机能力放大到集群,同时引入了 CAP / 网络分区 / 时钟漂移的新麻烦

4. 演化层(长期维护)—— 决定能不能活久

  • 编码格式:JSON / Protobuf / Avro / Thrift;自描述 vs 带 schema;紧凑度与可读性
  • Schema 演进:向前兼容 / 向后兼容;滚动升级、双写迁移、影子表;在线 DDL
  • 没有这一层,系统在第一年风光无限,第三年寸步难行

5. 总结(分层的实战价值):

  • 排障自上而下:用户喊慢 → 是查询语言写法、索引缺失,还是分区热点、网络复制延迟?
  • 设计自下而上:分布层选了无主复制 → 抽象层就别承诺线性一致

三条主轴(权衡)

零散的权衡条目能列十几条,但本质冲突只有三类。每条主轴都是一个反问:

轴一:正确性 vs 性能 —— 要不要等所有人都同意?

囊括:一致性 vs 延迟、隔离级别 vs 吞吐、同步 vs 异步复制。

  • 等 = 协调成本(锁、共识、跨节点 RTT)
  • 不等 = 不一致窗口、丢数据窗口、并发异常

表现:CAP / PACELC、Serializable → RU、同步 → 异步复制。 本质:协调(coordination)是正确性的代价,也是性能的敌人。

轴二:读 vs 写 vs 空间 —— 成本放在哪一侧?

囊括:读放大、写放大、空间放大三角。任一存储引擎只能优化两端,第三端必然恶化。

  • B-Tree:读放大低,写放大高
  • LSM:写放大低,读放大与空间放大高
  • 列存 + 压缩:空间小、扫描快,但点查与更新昂贵

本质:没有免费的索引,只有把痛点搬去别处。

轴三:灵活性 vs 约束 —— 约束提前还是延后?

囊括:强 schema vs 弱 schema、静态类型 vs 动态、预建索引 vs 即席查询、预聚合 vs 实时计算。

  • 提前约束(写时校验、强 schema、预建索引):写入慢、迁移痛,但读取/查询便宜、错误早暴露。
  • 延后约束(读时校验、schemaless、即席分析):写入自由,但每次读都要付解释代价、错误晚暴露。

本质:约束是现在花钱还是以后花钱,总账谁也省不了。

评论