mcpField
D
data-storage

data-storage

github.com/proyecto26数据与数据库113330 次安装1 天前更新
代码仓库

关于 data-storage

用于选择数据存储、数据模型和扩展策略的系统设计技能,帮助架构师根据访问模式与一致性需求匹配存储方案,并优先考虑简单方案,再引入副本或分片。

主要功能

  1. 01比较关系型与 NoSQL 存储
  2. 02设计键、查询与索引
  3. 03评估副本与数据库联邦
  4. 04规划分区与分片
  5. 05分析存储故障场景

适用场景

  1. 01为新系统选择存储方案
  2. 02根据查询设计键与索引
  3. 03判断何时需要副本或分片

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/proyecto26/system-design-skills --skill data-storage

安装后在 Claude Code 中输入/data-storage确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
deep-dive.md
references/providers/
aws.md
azure.md
gcp.md
generic.md

详细介绍

数据存储

选择记录的存放位置、键和查询方式,以及存储如何扩展到单台机器之外。存储层是后期最难更改的层:错误的数据模型或分片键会固化为扩展瓶颈,而副本配置错误则会在不知不觉中提供过期或丢失的数据。

何时使用

任何需要持久化状态的系统:选择 SQL 还是 NoSQL、设计架构及其访问路径、添加索引、拆分热点表、在节点间分布数据(分片/分区)、添加只读副本,或决定要反规范化哪些数据。当“存储数据”需要明确具体的键和查询形式时,就该参考这里。

何时不使用

在数据规模迫使你这么做之前,不要分片、添加副本或使用 NoSQL(YAGNI)。单个索引完善的关系型节点可以轻松处理约 1k QPS,以及数十 GB 到低 TB 的数据——大多数系统永远不会超出这个范围。分片会增加运维成本,并破坏联接/事务;只有在确实超出单节点的写入吞吐量或数据集容量时才添加分片(→ back-of-the-envelope)。缓存读取(→ caching)和添加只读副本都是比分片更便宜的优先选择。

先明确需求

选择存储或拓扑之前,请先回答以下问题——它们决定设计方案:

  • 数据形态与关系 — 扁平的键值对?需要联接的复杂关系?文档块?连接关系图?这决定选择 SQL 还是 NoSQL。
  • 访问模式 — 数据如何读写,而不只是存在哪里。按键点查、范围扫描、临时查询、聚合?根据必须支持的查询来设计存储。
  • 读写比例与规模 — 读写各自的 QPS,以及当前和保留期内的数据总量。(关于 QPS、存储和分片数量,参见 → back-of-the-envelope。)
  • 一致性需求 — 读取必须看到最新写入,还是最终一致性也可以?是否需要多记录事务?(CAP/一致性理论 → consistency-coordination。)
  • 延迟与持久性目标 — p99 读写预算,以及节点故障时系统能够承受丢失多少近期数据。

选项

**关系型(SQL — Postgres、MySQL):**严格的架构、联接、ACID 事务。适用场景:数据具有关系,数据完整性很重要,且查询多样或临时性强——在有数据指标证明不适用之前,这是安全的默认选择。

**文档型(MongoDB 等):**架构灵活,可按结构查询独立完整的类 JSON 文档。适用场景:记录整体读写,且架构不断演进;关系较少。

**键值型(Redis、DynamoDB、Riak):**按键执行 O(1) 的 get/put,不支持复杂查询。适用场景:访问完全基于已知键,且需要极高吞吐量。

**宽列型(Cassandra、Bigtable、HBase):**按分区键组织行,列稀疏,并按顺序保存键以支持范围扫描。适用场景:写入量巨大,且表可以根据少数已知查询模式设计。

**图型(Neo4j):**节点和边。适用场景:核心查询需要遍历多对多关系(社交图谱、推荐)。

扩展方式(适用于任何存储):

  • 索引 — 添加辅助结构,让查询无需再扫描数据。这是首要手段。
  • 只读副本(主从模式) — 将写入复制到负责处理读取的从节点。 适用场景:读取占主导,且可以接受轻微的数据过期。
  • 联邦化 — 按功能拆分数据库(用户/产品/论坛)。适用场景:各功能域独立扩展,且很少需要联接。
  • 分片/分区 — 使用分片键将一张逻辑表拆分到多个节点。适用场景:超出单节点的写入能力或数据集容量。(本技能负责此项。)
  • 反规范化 — 存储冗余副本以跳过联接。适用场景:读取量远超写入量,且联接是瓶颈。

**多态持久化:**使用上述多种存储,各自承担最适合的工作(例如,Postgres 存储订单,Redis 存储会话,搜索索引存储全文)。代价是需要运维并协调多个存储。

权衡

选项解决的问题带来的问题何时更换
关系型/SQL联接、ACID、临时查询、完整性单节点写入上限;架构迁移;难以横向扩展写入量/数据量超出单个节点的承载能力,或架构确实灵活多变 → NoSQL/分片
文档型架构灵活;读取完整对象不支持联接;跨文档一致性需手动维护;查询引擎较弱数据变为关系型,或需要跨文档事务 → SQL
键值型极高吞吐量;运维简单只能按键访问;不支持范围查询/二级查询需要按键以外的查询 → 文档型/宽列型
宽列型写入密集型扩展;按排序键范围扫描必须预先确定查询;键设定后难以更改;默认最终一致访问模式未知/多样 → 关系型
图型以低成本深度遍历关系工具生态小众;难以分片;不适合批量扫描关系较浅 → 关系型/文档型
索引将扫描转为查找写入变慢;存储占用增加;索引膨胀写放大带来的影响超过读取收益
只读副本分担读取;提供冗余复制延迟 → 读取到陈旧数据;需要处理故障转移/提升逻辑无法接受陈旧数据 → 从主节点读取 / consistency-coordination
联邦按领域扩展;缩小工作集跨领域联接会失效;需要应用路由逻辑单个领域仍无法容纳 → 对该领域分片
分片横向扩展写入和存储跨分片联接/事务难以处理;重新分片麻烦;热点分片单个节点可以承载,或热点分片影响过大 → 合并/重新设定键
反规范化消除读取路径上昂贵的联接数据重复;写入时扇出;一致性漂移写入负载使扇出成为新的瓶颈 → 规范化/缓存

压力下的行为

负载和故障会在存储层被放大,最终引发服务中断。

  • 热点分片 / 热门键: 分布不均的分片键会将不成比例的流量发送到 一个节点(某个爆红用户、单个租户),而其他节点却空闲。集群看起来 负载不高,但一个分片已濒临崩溃。缓解措施: 使用基数高且分布均匀 的分片键;隔离热点键或对其进行子分区。
  • 复制延迟: 写入突增时,从节点会落后于主节点,导致读取到陈旧数据, 而故障转移还可能提升一个丢失了近期写入的从节点。缓解措施: 为实现 读己之写,从主节点读取;对延迟设置上限并配置告警。
  • 连接耗尽: 每个数据库连接都会消耗内存和一个后端进程/线程;流量 激增(或重试风暴)会打开超出数据库处理能力的连接数,导致所有查询 都变慢或出错。前置连接池(有界连接池)才能让数据库保持运行——没有 它,数据库就会成为在负载下故障的单点故障。
  • 写入风暴 / 锁争用: 热点行或单个序列/计数器会使写入串行化;索引 更新和锁等待不断累积。
  • 高压下重新分片: 在系统已过载时进行再平衡,会移动海量数据,可能 使集群不堪重负。应在临界点到来前规划容量。

监控: 复制延迟、各分片的 QPS 和大小(分布偏斜)、连接池饱和度/等待 时间、慢查询率、锁等待,以及磁盘/IOPS 余量。

如何应用

  1. 明确输入 — 确定数据形状、访问模式、读写比例、一致性需求以及延迟/持久性目标(参见 先明确需求)。不了解访问模式,就无法选定适用的数据存储。
  2. 根据权衡表选择数据存储 — 将数据形状与选项匹配;除非某个数字或关系模式排除了关系型数据库,否则默认选择关系型数据库。说明每个候选方案会牺牲什么,而不只说它能解决什么。
  3. 确定接口 — 在引入扩展机制前,为每种查询写明主键、分区键/排序键和二级索引(参见 接口草图)。键才是关键决策。
  4. 按成本从低到高设置扩展参数 — 依次使用索引、缓存(→ caching)、只读副本、联邦,最后才是分片。达到目标后就停止。
  5. 对方案进行压力测试 — 检查热点分片、复制延迟、连接耗尽和重新分片(参见 压力下的行为);确认键分布偏斜或写入突发不会压垮单个节点。
  6. 先估算规模,再选择提供商 — 根据 back-of-the-envelope 计算分片数量和每个节点的负载;如果只需一个节点,就不要分片。默认采用通用方案,仅在指定云平台时阅读对应的提供商文件。
  7. 在答案中展示权衡 — 提供 Decision | Solves | Worsens | Change it when… 表格,每个已做出的选择占一行(例如选项、数据存储及其故障策略);最后一列要写明具体触发条件(一个数字或可观察到的信号)。然后说明最先会出现什么故障。

应做和不应做

应做

  • 从查询出发:围绕访问路径设计数据存储,并在画任何架构图之前写出具体的键。
  • 默认使用单个索引完善的关系型节点;先充分利用索引、缓存和只读副本,再考虑分片。
  • 选择基数高、分布均匀的分片键;隔离已知热点键,或将其进一步划分为子分区。
  • 在数据库前部署有上限的连接池器,并针对复制延迟和各分片的数据偏斜设置告警。
  • 根据峰值写入 QPS 和数据集大小计算分片数量,取较大的结果。

不应做

  • 不要在数字表明有必要之前就采用 NoSQL、只读副本或分片(YAGNI)。
  • 不要在了解数据形状和一致性需求之前,因炒作而选择数据存储。
  • 不要以为只读副本能提供最新读取结果——复制延迟会导致读取到陈旧数据,故障转移还可能丢失近期写入。
  • 不要使用基数低或单调递增的键进行分片;这会造成热点分片和写入热点。
  • 不要等集群已经过载后再重新分片;应在到达容量瓶颈之前规划容量。

关键数字

不要重复表格中的内容——从 back-of-the-envelope 中提取数据。影响存储决策的数字包括:单个 RDBMS 节点约为 1k QPS;键值节点约为 10k QPS;10 GB 可放入 RAM,10 TB 需要分布式存储。使用这些数据计算 分片数量 = 峰值写入 QPS ÷ 单节点 QPS(还要按大小计算一次:总字节数 ÷ 单节点容量),然后取较大的结果。如果结果是一个节点,就不要分片。

接口草图

数据模型就是契约(GUIDE 故障模式 #8)——在写明键之前,“NoSQL box” 并不能决定任何事。请为每种实体明确以下内容:

  • 主键 — 唯一标识某行/项的内容,以及查找方式。
  • 分区键(分片键)+ 排序键 — 例如,宽列数据库/DynamoDB: PK = user_id(哈希,用于分散负载),SK = created_at(排序,用于支持范围扫描,例如“最新的 N 条帖子”)。PK 必须具有高基数且分布均匀。
  • 二级索引 — 必须支持的非键访问路径,以及每条路径对应的查询。
  • 关系型数据库 — 表、带类型的列、外键,以及支持每条查询的索引;注明有意进行反规范化的部分。

如果没有具体的键,也没有说明它服务于哪条查询,设计的其余部分就只能靠猜测。

选择提供商

默认使用上面的通用方案。如果用户指定了云服务,请阅读 references/providers/<provider>.md,了解托管服务映射、 配额和限制,以及提供商特有的权衡。如果该提供商没有对应文件,则采用通用方案。

图示

要直观展示数据层——带只读副本的主节点、带路由器的分片集群,或多语言拆分——请使用插件内的 architecture-diagram 技能。 在路由箭头上标出分片键,并明确标示复制方向; 此处不要嵌入 Mermaid。

相关构建模块

  • caching — 与此模块配合使用,用于分担读取负载,也是成本最低的首选措施; 在分片之前,可作为只读副本的替代方案。
  • consistency-coordination — 此模块负责的概念包括 CAP/一致性模型、 一致性哈希、仲裁,以及分布式事务/saga。如果无法接受过时读取或跨分片原子性问题,可与此模块 配合使用。
  • back-of-the-envelope — 为此模块提供依据:给出 QPS、存储量和 分片数量,帮助判断此处的每种措施是必要还是可省略。
  • scaling-evolution — 依赖此模块;随着系统增长,安排引入各项存储措施的时机。
  • system-design — 将工作流导向此模块的协调器。

参考资料

  • references/deep-dive.md — 分区方案(范围/哈希/目录)、 复制机制与冲突解决、索引内部实现(B-tree 与 LSM 对比)、 规范化与反规范化、连接池、重新分片。在详细设计数据层时阅读。
  • references/providers/{generic,aws,azure,gcp}.md — 服务映射、影响决策的 限制,以及各环境的常见问题。

评论

支持 Markdown