土法炼钢兴趣小组的算法知识备份

【存储工程】存储与计算分离架构

文章导航

分类入口
storage
标签入口
#compute-storage-separation#shared-nothing#snowflake#aurora#tidb#data-lake#local-cache

目录

一台数据库服务器同时承担计算和存储两个职责,看似简单直接,但在弹性伸缩的场景下问题就暴露了:扩容时计算和存储只能一起加,缩容时也只能一起减。一个 OLAP 查询高峰需要 100 个 CPU 核,但数据只有 2 TB——按存算一体的做法,你不得不买 100 个核对应的存储容量,哪怕这些存储大部分时间是空的。反过来,一个冷数据归档场景需要 500 TB 存储但几乎不做计算,你也不得不配上与 500 TB 存储绑定的 CPU 和内存。

存算分离(Disaggregated Storage and Compute)的核心思路就是打破这个绑定:让计算节点和存储节点各自独立伸缩。计算密集就加计算节点,存储不够就加存储节点,两者互不影响。这个思路听起来简单,但工程实现涉及架构模式选择、网络带宽、缓存策略、一致性协议等一系列权衡。

本文从存算分离的工程动机出发,对比三种主流架构模式,然后分别拆解 Snowflake、Aurora、TiDB 三个系统的存算分离实践,最后讨论对象存储作为数据湖底座、本地缓存策略、网络带宽需求计算,以及存算一体和分离的性能对比。

适用范围 本文讨论的存算分离主要针对数据库和数据仓库场景,不涉及 HPC/科学计算领域的存算分离。涉及的系统版本:Snowflake(公开论文及文档,截至 2024 年)、Amazon Aurora(公开论文及文档)、TiDB 7.x。不同版本的架构细节可能有差异。


一、存算分离的工程动机

1.1 弹性伸缩的需求

传统存算一体(Shared-Nothing)架构下,数据按分片(Shard)分布在各个节点上,每个节点既负责存储自己的分片,也负责处理针对这些分片的查询。扩容意味着加节点、迁移分片、重新均衡数据;缩容同理。这个过程有两个问题:

第一,数据迁移代价高。一个 1 TB 的分片在千兆网络下迁移需要约 2.5 小时,在万兆网络下也需要约 15 分钟。在迁移期间,系统要么拒绝写入该分片,要么通过双写保证一致性,两者都增加复杂度。

第二,计算和存储的伸缩粒度不同。计算负载的波动通常以分钟级到小时级为周期——比如一个报表查询在每天早上 9 点达到峰值,下午就降下来。存储容量的增长则是天级到月级的趋势。把两者绑在一起,意味着计算的弹性被存储的数据迁移拖慢了。

存算分离之后,计算节点是无状态的(或只持有缓存),启动一个新计算节点只需要拉起进程、连接远端存储,不需要迁移任何数据。伸缩的时间从分钟级降到秒级。

1.2 成本优化

云环境下的成本差异最能说明问题。以 AWS 为例(2024 年 us-east-1 公开定价):

资源类型 规格 月成本(近似)
EC2 计算(c6i.xlarge) 4 vCPU / 8 GB 内存 约 124 美元
EBS gp3 存储 1 TB 约 80 美元
S3 标准存储 1 TB 约 23 美元
S3 低频访问 1 TB 约 12.5 美元

S3 的存储成本只有 EBS 的约 29%,低频访问更是只有 16%。如果一个系统有 100 TB 数据,其中 80% 是温冷数据,把温冷数据从 EBS 迁移到 S3 每月可节省约 (80 x 80) - (80 x 23) = 4560 美元。乘以 12 个月就是每年约 5.5 万美元。对于 PB 级存储集群,这个差异是百万美元级别的。

1.3 运维独立性

存算一体架构下,计算节点故障会导致该节点上的数据分片暂时不可用(除非有副本接管)。存算分离之后,计算节点故障只是丢失了缓存,存储层的数据完整性不受影响。重新拉起一个计算节点就能继续服务。

同样,存储层的扩容(比如给对象存储集群加磁盘)不需要重启任何计算节点。计算层的升级(比如更换查询引擎版本)也不需要动存储。两个团队可以按各自的节奏迭代。


二、架构模式对比

存算分离不是一种单一的架构,而是一个方向。具体实现至少有三种主流模式:共享无关(Shared-Nothing)、共享磁盘(Shared-Disk)、共享存储(Shared-Storage)。

2.1 三种模式的定义

Shared-Nothing(共享无关):每个节点有自己的 CPU、内存和磁盘。数据按分片分布在各节点上,节点之间通过网络通信但不共享存储。传统的分布式数据库(如 MySQL 分库分表、早期的 Greenplum)属于这种模式。严格来说,Shared-Nothing 是存算一体的架构,但它是存算分离演进的起点。

Shared-Disk(共享磁盘):多个计算节点共享同一个存储层。计算节点可以读写存储层的任意数据,不需要数据分片。传统的 Oracle RAC、IBM DB2 pureScale 属于这种模式。现代的 Aurora 也可以归入这一类,只是把共享的磁盘从 SAN 换成了分布式存储层。

Shared-Storage(共享存储):这里特指以对象存储(如 S3)或分布式文件系统为共享存储层的架构。和 Shared-Disk 的区别在于,Shared-Storage 通常假设存储层是高延迟、高吞吐的(对象存储的延迟在毫秒级,远高于本地 SSD 的微秒级),因此计算节点必须依赖本地缓存来弥补延迟差距。Snowflake 是这种模式的代表。

2.2 架构对比表

                    Shared-Nothing           Shared-Disk              Shared-Storage
                 ┌─────────────────┐   ┌─────────────────────┐   ┌──────────────────────┐
                 │  Node 1         │   │  Compute 1          │   │  Compute 1           │
                 │  ┌───┐ ┌─────┐ │   │  ┌───┐              │   │  ┌───┐ ┌──────────┐  │
                 │  │CPU│ │Disk │ │   │  │CPU│              │   │  │CPU│ │Local SSD │  │
                 │  └───┘ └─────┘ │   │  └───┘              │   │  └───┘ │(Cache)   │  │
                 ├─────────────────┤   ├─────────────────────┤   │       └──────────┘  │
                 │  Node 2         │   │  Compute 2          │   ├──────────────────────┤
                 │  ┌───┐ ┌─────┐ │   │  ┌───┐              │   │  Compute 2           │
                 │  │CPU│ │Disk │ │   │  │CPU│              │   │  ┌───┐ ┌──────────┐  │
                 │  └───┘ └─────┘ │   │  └───┘              │   │  │CPU│ │Local SSD │  │
                 ├─────────────────┤   ├─────────────────────┤   │  └───┘ │(Cache)   │  │
                 │  Node 3         │   │   共享存储层          │   │       └──────────┘  │
                 │  ┌───┐ ┌─────┐ │   │  ┌─────────────────┐ │   ├──────────────────────┤
                 │  │CPU│ │Disk │ │   │  │ SAN / 分布式存储 │ │   │   对象存储 (S3)       │
                 │  └───┘ └─────┘ │   │  └─────────────────┘ │   │  ┌──────────────────┐ │
                 └─────────────────┘   └─────────────────────┘   │  │ Bucket / Prefix  │ │
                                                                  │  └──────────────────┘ │
                                                                  └──────────────────────┘
维度 Shared-Nothing Shared-Disk Shared-Storage
数据分布 分片到各节点 共享存储层,不分片 共享对象存储,按文件组织
计算伸缩速度 慢(需数据迁移) 快(秒级) 快(秒级)
存储伸缩速度 慢(需数据迁移) 中等(扩展存储层) 快(对象存储自动扩展)
存储成本 高(本地磁盘,含副本) 中(分布式存储) 低(对象存储)
读延迟 低(本地磁盘) 中(网络 + 分布式存储) 高(网络 + 对象存储,需缓存)
写延迟 低(本地磁盘)
缓存依赖度
故障影响 计算 + 数据都受影响 只影响计算 只影响计算
一致性复杂度 低(各节点独立) 高(需协调缓存一致性) 中(通常不可变文件)
代表系统 Greenplum、CockroachDB Oracle RAC、Aurora Snowflake、Databricks

2.3 选择的工程判断

这三种模式不是简单的”新比旧好”。选择取决于工作负载特征:


三、Snowflake 的存算分离实践

Snowflake 是存算分离架构在云数据仓库领域最成功的实践之一。它的核心思路可以用一句话概括:把数据全部放在 S3 上,用本地 SSD 做缓存,让计算节点(Virtual Warehouse)按需启停。

3.1 整体架构

Snowflake 的架构分为三层:

┌──────────────────────────────────────────────────────────────┐
│                    Cloud Services 层                         │
│  ┌──────────┐  ┌──────────────┐  ┌───────────┐  ┌────────┐  │
│  │查询优化器 │  │元数据管理     │  │访问控制    │  │事务管理│  │
│  └──────────┘  └──────────────┘  └───────────┘  └────────┘  │
├──────────────────────────────────────────────────────────────┤
│                    Virtual Warehouse 层(计算层)              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │
│  │ VW-1 (XS)   │  │ VW-2 (L)    │  │ VW-3 (XL)   │          │
│  │ ┌─────────┐ │  │ ┌─────────┐ │  │ ┌─────────┐ │          │
│  │ │EC2 节点 │ │  │ │EC2 节点 │ │  │ │EC2 节点 │ │          │
│  │ │本地 SSD │ │  │ │本地 SSD │ │  │ │本地 SSD │ │          │
│  │ └─────────┘ │  │ ├─────────┤ │  │ ├─────────┤ │          │
│  │             │  │ │EC2 节点 │ │  │ │EC2 节点 │ │          │
│  │             │  │ │本地 SSD │ │  │ │ ...     │ │          │
│  │             │  │ └─────────┘ │  │ └─────────┘ │          │
│  └─────────────┘  └─────────────┘  └─────────────┘          │
├──────────────────────────────────────────────────────────────┤
│                    Storage 层(S3)                           │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  数据文件(微分区,列式存储,PAX 格式,压缩加密)        │    │
│  └──────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────┘

Cloud Services 层:全局共享的服务层,负责查询解析与优化、元数据管理(哪些表、哪些分区、统计信息)、访问控制、事务管理。这一层本身也是多租户(Multi-Tenant)的,多个客户共享同一套 Cloud Services。

Virtual Warehouse(VW)层:计算层。每个 VW 是一组 EC2 实例,按 T-shirt 规格划分(XS/S/M/L/XL/2XL/3XL/4XL),每个尺寸对应固定数量的节点。VW 之间完全隔离——不同 VW 读取同一张表时,各自从 S3 拉取数据或从各自的本地缓存读取,互不干扰。

Storage 层:所有数据以不可变文件的形式存储在 S3 上。这个设计选择至关重要——文件不可变意味着不需要缓存一致性协议(Cache Coherence Protocol)。一个文件写入 S3 后就不会被修改,只会被删除并替换为新文件。

3.2 微分区

Snowflake 把每张表的数据切分成微分区(Micro-Partition),每个微分区是一个不可变的列式存储文件,大小在 50 MB 到 500 MB 之间(压缩后通常在 16 MB 左右)。

微分区的关键特性:

  1. 不可变:数据一旦写入微分区就不会修改。UPDATE 操作实际上是创建新的微分区、删除旧的微分区。
  2. 列式存储:每个微分区内部按列存储(PAX 格式),支持列裁剪(Column Pruning)。如果查询只需要 3 列,只读取这 3 列的数据。
  3. 自动聚簇:Snowflake 会根据查询模式自动对微分区做聚簇(Clustering),把相关数据放到同一个微分区,减少查询需要扫描的分区数。
  4. 分区裁剪:Cloud Services 层维护每个微分区的元数据,包括每列的最小值/最大值、空值计数等。查询时根据 WHERE 条件裁剪掉不需要扫描的分区。
-- 示例:分区裁剪的效果
-- 假设 orders 表有 10000 个微分区,按 order_date 自然排序
-- Cloud Services 层维护了每个分区的 order_date 最小值和最大值

SELECT SUM(amount)
FROM orders
WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';

-- 如果只有 300 个分区的 order_date 范围与查询条件重叠
-- 则只需要从 S3 读取 300 个分区,裁剪掉了 97% 的数据

3.3 Virtual Warehouse 的弹性

VW 的启停速度是 Snowflake 存算分离架构的核心优势之一。启动一个 VW 通常在 1 到 2 秒内完成(不含 EC2 实例的冷启动时间,因为 Snowflake 维护了预热实例池)。

几个关键的弹性特性:

3.4 本地缓存机制

VW 节点的本地 SSD 作为 S3 数据的缓存。缓存策略是基于文件的(File-Level Caching)——以微分区文件为单位缓存,不做行级或页级缓存。

查询执行路径:

1. Cloud Services 确定需要扫描的微分区列表
2. VW 节点检查本地 SSD 缓存
   ├── 缓存命中 → 直接从本地 SSD 读取(延迟 ~0.1ms)
   └── 缓存未命中 → 从 S3 读取(延迟 ~10-50ms)
                   └── 同时写入本地 SSD 缓存
3. 列裁剪:只读取查询需要的列
4. 在节点本地执行过滤、聚合等操作
5. 结果汇总返回

缓存的一致性依赖微分区的不可变性——如果一个微分区文件在 S3 上存在,它的内容永远不会变。如果表发生了 DML 操作(INSERT/UPDATE/DELETE),旧的微分区被删除、新的微分区被创建,缓存中的旧文件自然失效。Cloud Services 层通过版本化的表元数据来判断哪些缓存文件仍然有效。


四、Aurora 的存算分离

Amazon Aurora 走了一条不同于 Snowflake 的存算分离路线。它不是把存储放到对象存储上,而是构建了一个专用的分布式存储层,核心设计理念是”日志即数据库”(The Log Is the Database)。

4.1 架构概览

┌─────────────────────────────────────────────────────┐
│                  计算层                               │
│  ┌──────────────┐              ┌──────────────┐     │
│  │ Writer 实例   │              │ Reader 实例   │     │
│  │ (Primary)    │              │ (Read Replica)│     │
│  │ ┌──────────┐ │              │ ┌──────────┐ │     │
│  │ │Buffer Pool│ │  ── 日志流 →  │ │Buffer Pool│ │     │
│  │ └──────────┘ │              │ └──────────┘ │     │
│  └──────┬───────┘              └──────┬───────┘     │
│         │ 只写 Redo Log                │ 只读         │
├─────────┼──────────────────────────────┼─────────────┤
│         ▼          存储层               ▼             │
│  ┌────────────────────────────────────────────────┐  │
│  │  分布式存储服务                                   │  │
│  │  ┌──────┐  ┌──────┐  ┌──────┐                 │  │
│  │  │ PG 1 │  │ PG 2 │  │ PG 3 │  ...            │  │
│  │  │6 副本 │  │6 副本 │  │6 副本 │                 │  │
│  │  │3 AZ  │  │3 AZ  │  │3 AZ  │                 │  │
│  │  └──────┘  └──────┘  └──────┘                 │  │
│  └────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

4.2 日志即数据库

传统 MySQL 的写入路径是:先写 Redo Log 到本地磁盘,再把脏页刷到数据文件。在主从复制场景下,还要把 Binlog 发送给从库,从库重放 Binlog 后再写自己的 Redo Log 和数据文件。整个路径有大量的 I/O 放大。

Aurora 的做法是:计算层只往存储层发送 Redo Log 记录,不发送数据页。存储层在后台异步地把 Redo Log 应用到数据页上(即”日志应用”操作)。这把计算层的写 I/O 减少到只剩日志写入,消除了数据页的网络传输。

根据 Aurora 论文(Verbitski et al., SIGMOD 2017)的数据,这种设计把网络 I/O 减少了约 7/8。传统 MySQL 写一个事务可能需要传输 Redo Log、Binlog、数据页、Double Write Buffer 等多份数据,而 Aurora 只需要传输 Redo Log。

传统 MySQL 主从复制的网络写入路径:
  ┌──────────┐
  │ Primary  │──── Redo Log ──────→ 本地磁盘
  │          │──── Binlog ────────→ 本地磁盘
  │          │──── 数据页 ─────────→ 本地磁盘 (Double Write)
  │          │──── Binlog ────────→ Replica(网络)
  └──────────┘
  Replica 收到 Binlog 后:
  ┌──────────┐
  │ Replica  │──── Relay Log ─────→ 本地磁盘
  │          │──── Redo Log ──────→ 本地磁盘
  │          │──── 数据页 ─────────→ 本地磁盘
  └──────────┘

Aurora 的网络写入路径:
  ┌──────────┐
  │ Primary  │──── Redo Log ──────→ 存储层(网络,6 副本)
  └──────────┘
  存储层在后台完成:
  ┌──────────┐
  │ Storage  │──── 日志应用 ──────→ 生成数据页(本地操作)
  └──────────┘

4.3 六副本 Quorum 协议

Aurora 的存储层把数据切分为 10 GB 大小的保护组(Protection Group),每个保护组有 6 个副本,分布在 3 个可用区(Availability Zone,AZ),每个 AZ 放 2 个副本。

Quorum 协议的参数设定为:

这个参数设定满足 Quorum 条件:W + R > N(4 + 3 > 6),保证读操作一定能读到最新写入的数据。

设计目标是容忍以下故障场景:

  1. 单 AZ 故障 + 单节点故障:一个 AZ 挂掉丢失 2 个副本,再加上另一个 AZ 的 1 个节点故障,总共丢失 3 个副本。剩余 3 个副本仍然满足 Read Quorum = 3,可以继续读取。写入需要 4/6 确认,此时只剩 3 个副本不满足写 Quorum,写入会暂停直到故障恢复。
  2. 单 AZ 故障:丢失 2 个副本,剩余 4 个副本同时满足 Write Quorum = 4 和 Read Quorum = 3,读写均不受影响。
保护组的副本分布:

  AZ-1          AZ-2          AZ-3
┌────────┐  ┌────────┐  ┌────────┐
│副本 1   │  │副本 3   │  │副本 5   │
│副本 2   │  │副本 4   │  │副本 6   │
└────────┘  └────────┘  └────────┘

写 Quorum = 4:任意写入必须获得 4 个副本确认
读 Quorum = 3:任意读取从 3 个副本获取数据

场景分析:
  AZ-1 整体故障(丢失副本 1、2)→ 剩余 4 副本 → 读写正常
  AZ-1 故障 + 副本 3 故障(丢失 3 副本)→ 剩余 3 副本 → 只读
  任意 2 个副本故障(不跨 AZ 全挂)→ 剩余 4 副本 → 读写正常

4.4 读副本的实现

Aurora 的读副本和传统 MySQL 从库的区别在于:读副本不需要重放完整的事务日志。Primary 实例把 Redo Log 流式发送给读副本,读副本只需要把日志应用到自己的 Buffer Pool 中的缓存页上。如果某个被修改的页不在读副本的 Buffer Pool 中,读副本直接丢弃这条日志——反正下次需要读取该页时,可以从存储层读取最新版本。

这种设计让读副本的延迟非常低。根据 Aurora 论文,读副本的复制延迟通常在 10 到 20 毫秒。相比之下,传统 MySQL 从库的复制延迟在高写入负载下可能达到秒级甚至分钟级。

4.5 存储层的后台操作

Aurora 的存储层不仅仅是一个”能存 Redo Log 的磁盘”,它还承担了大量的后台任务:

这些操作都在存储层完成,不消耗计算层的 CPU 和内存。这也是存算分离带来的运维收益之一。


五、TiDB 的存算分离

TiDB 的存算分离路线和 Snowflake、Aurora 都不同。它不是一开始就按存算分离设计的,而是从一个 Shared-Nothing 架构逐步演进,通过 TiKV 和 TiFlash 的分离、以及 S3 冷存储的引入,实现了部分的存算分离。

5.1 TiDB 的基础架构

┌──────────────────────────────────────────────────────────────┐
│                        TiDB Server(计算层,无状态)           │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                   │
│  │ TiDB-1   │  │ TiDB-2   │  │ TiDB-3   │                   │
│  │ SQL 解析  │  │ SQL 解析  │  │ SQL 解析  │                   │
│  │ 查询优化  │  │ 查询优化  │  │ 查询优化  │                   │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘                   │
│       │             │             │                          │
├───────┼─────────────┼─────────────┼──────────────────────────┤
│       ▼             ▼             ▼                          │
│  ┌─────────────────────────────────────────────────────┐     │
│  │                   PD(调度层)                        │     │
│  │     时间戳分配 / Region 调度 / 负载均衡                │     │
│  └─────────────────────────────────────────────────────┘     │
│       │             │             │                          │
│       ▼             ▼             ▼                          │
│  ┌──────────────────────────┬────────────────────────┐       │
│  │     TiKV(行存储)        │    TiFlash(列存储)     │       │
│  │  ┌──────┐ ┌──────┐      │  ┌──────┐ ┌──────┐    │       │
│  │  │Store1│ │Store2│ ...  │  │Node1 │ │Node2 │ .. │       │
│  │  │Region│ │Region│      │  │列副本 │ │列副本 │    │       │
│  │  └──────┘ └──────┘      │  └──────┘ └──────┘    │       │
│  └──────────────────────────┴────────────────────────┘       │
└──────────────────────────────────────────────────────────────┘

TiDB 的存算分离体现在以下几个层面:

TiDB Server 无状态:TiDB Server 是纯计算层,负责 SQL 解析、查询优化、执行计划生成。它不存储任何用户数据,可以随时水平扩展。这一层已经实现了计算的弹性伸缩。

TiKV 是存算一体:TiKV 节点既存储数据(以 Region 为单位,每个 Region 默认 96 MB),也执行下推的计算(协处理器,Coprocessor)。TiKV 层本身是 Shared-Nothing 的。

TiFlash 作为列存副本:TiFlash 通过 Raft Learner 协议从 TiKV 异步复制数据,把行存格式转换为列存格式。OLAP 查询可以路由到 TiFlash,利用列存的扫描优势。TiDB 的优化器可以自动选择从 TiKV 还是 TiFlash 读取数据。

5.2 TiKV + TiFlash 的 HTAP 实践

TiDB 的 HTAP(Hybrid Transactional/Analytical Processing,混合事务分析处理)架构通过 TiKV 和 TiFlash 的配合实现:

-- 示例:配置 TiFlash 副本
-- 为 orders 表创建 1 个 TiFlash 副本
ALTER TABLE orders SET TIFLASH REPLICA 1;

-- TiDB 的优化器会根据查询特征自动选择引擎
-- 点查和短事务走 TiKV(行存)
SELECT * FROM orders WHERE order_id = 12345;

-- 大范围扫描和聚合走 TiFlash(列存)
SELECT DATE(order_date), SUM(amount), COUNT(*)
FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY DATE(order_date);

-- 也可以通过 Hint 强制指定引擎
SELECT /*+ READ_FROM_STORAGE(TIFLASH[orders]) */
    product_id, SUM(amount)
FROM orders
GROUP BY product_id;

TiFlash 的数据一致性通过 Raft Learner 保证。TiFlash 节点作为 Raft Group 的 Learner,接收 TiKV Leader 的日志但不参与投票。当 TiFlash 收到一条查询请求时,它会向 TiKV Leader 请求当前的 Raft Index,等待自己的日志应用到该 Index 后再返回结果,从而保证读到的数据不落后于事务提交点。

5.3 S3 冷存储

TiDB 从 7.0 版本开始支持将冷数据转储到 S3 等对象存储。这进一步推进了存算分离,把存储分为热层(TiKV 本地 SSD)和冷层(S3)。

配置示例:

-- 创建使用 S3 存储的 Placement Policy
-- 注意:以下为 TiDB 7.x 的配置方式,具体语法请参考对应版本文档
CREATE PLACEMENT POLICY cold_storage
    CONSTRAINTS="[+disk=s3]";

-- 对历史分区应用冷存储策略
ALTER TABLE orders PARTITION p202301
    PLACEMENT POLICY = cold_storage;

冷存储的核心权衡:

5.4 TiDB 存算分离的演进方向

TiDB 的存算分离是渐进式的,和 Snowflake、Aurora 一开始就按存算分离设计不同。这种演进路线有其现实原因:TiDB 最初的核心定位是 OLTP 数据库,而 OLTP 对延迟的要求决定了它不能简单地把存储层换成对象存储。

TiDB 的存算分离可以分为三个阶段来理解:

阶段一:TiDB Server 无状态化(已完成)
  TiDB Server 只做 SQL 解析和查询优化,不持有用户数据。
  计算层可以独立水平扩展。
  但 TiKV 层仍然是存算一体的 Shared-Nothing 架构。

阶段二:HTAP 分离——TiKV + TiFlash(已完成)
  OLTP 负载走 TiKV(行存),OLAP 负载走 TiFlash(列存)。
  读写路径分离,减少 OLTP 和 OLAP 之间的互相干扰。
  但 TiKV 节点仍然绑定了存储和计算。

阶段三:冷热分层——TiKV + S3(进行中)
  热数据保留在 TiKV 本地 SSD 上,冷数据下沉到 S3。
  降低大数据量场景下的存储成本。
  面向未来的方向是让 TiKV 的存储层可以完全托管到云存储上。

从工程实践的角度看,TiDB 的渐进路线比 Snowflake 的一步到位路线更适合 OLTP 场景。OLTP 的延迟敏感性意味着完全依赖远端存储是不可接受的,必须在本地保留热数据的副本。这本质上是在存算分离和访问延迟之间做权衡。


六、对象存储作为数据湖底座

6.1 从数据湖到湖仓一体

数据湖(Data Lake)的核心思想是把所有原始数据以文件形式存储在一个集中的、低成本的存储层上,不做预先的 Schema 定义。对象存储(如 S3、MinIO、阿里云 OSS)因为其无限扩展、低成本、高持久性的特性,成为数据湖的默认选择。

但早期数据湖有一个根本问题:缺乏事务支持。一个写入 S3 的 Parquet 文件如果写到一半失败了,读者可能读到不完整的数据。多个写者同时更新同一个目录的文件时,也没有原子性保证。

湖仓一体(Lakehouse)架构试图解决这个问题,在对象存储之上加一层表格式(Table Format)来提供事务语义。目前主流的三个表格式是 Delta Lake、Apache Iceberg 和 Apache Hudi。

6.2 表格式对比

特性 Delta Lake Apache Iceberg Apache Hudi
发起方 Databricks Netflix / Apple Uber
元数据管理 事务日志(JSON/Parquet) 快照(Manifest 文件) 时间线(Timeline)
ACID 事务 支持 支持 支持
Schema 演进 支持 支持(列级) 支持
时间旅行(Time Travel) 支持 支持 支持
分区演进 有限 支持(隐式分区) 有限
生态绑定度 较高(Spark / Databricks) 较低(多引擎兼容) 中等
存储格式 Parquet Parquet / ORC / Avro Parquet / Avro

6.3 Lakehouse 架构的存算分离

Lakehouse 架构天然就是存算分离的:数据以开放格式(Parquet / ORC)存储在对象存储上,计算引擎(Spark、Trino、Flink、DuckDB 等)按需启动并直接读取对象存储的数据。

┌───────────────────────────────────────────────────────┐
│                    计算引擎层                           │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐  │
│  │ Spark   │  │ Trino   │  │ Flink   │  │ DuckDB  │  │
│  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘  │
│       │            │            │            │        │
├───────┼────────────┼────────────┼────────────┼────────┤
│       ▼            ▼            ▼            ▼        │
│  ┌────────────────────────────────────────────────┐   │
│  │         表格式层(Iceberg / Delta / Hudi)       │   │
│  │         事务管理 / 元数据 / Schema 演进           │   │
│  └────────────────────┬───────────────────────────┘   │
│                       │                               │
├───────────────────────┼───────────────────────────────┤
│                       ▼                               │
│  ┌────────────────────────────────────────────────┐   │
│  │         对象存储层(S3 / MinIO / OSS)           │   │
│  │         Parquet / ORC 文件                      │   │
│  └────────────────────────────────────────────────┘   │
└───────────────────────────────────────────────────────┘

这种架构的核心优势是存储与计算引擎的解耦。同一份数据可以被 Spark 做批处理、被 Trino 做交互式查询、被 Flink 做流处理,而不需要在不同系统之间拷贝数据。数据只有一份,存储在对象存储上,成本最低。

6.4 对象存储的性能特征

对象存储作为数据湖底座,其性能特征决定了上层系统的设计约束:

指标 S3(典型值) 本地 NVMe SSD(典型值) 差距倍数
首字节延迟(GET) 10-50 ms 0.05-0.1 ms 100-500x
顺序读吞吐(单连接) 50-100 MB/s 3000-7000 MB/s 30-70x
顺序读吞吐(多连接并行) 数十 GB/s(聚合) 受限于单盘 可通过并行弥补
PUT 延迟 10-100 ms 0.01-0.05 ms 200-2000x
每秒请求数(单前缀) 5500 GET / 3500 PUT 数十万 IOPS 50-100x
存储成本 0.023 美元/GB/月 约 0.08-0.15 美元/GB/月 3-6x 更便宜

关键结论:对象存储的单次延迟远高于本地 SSD,但吞吐可以通过大量并行请求来弥补。这决定了存算分离系统必须采用大块读取 + 并行化 + 本地缓存的策略,而不是小块随机读写。

6.5 对象存储在存算分离中的工程约束

对象存储不是万能的。在实际工程中,使用对象存储作为数据湖底座需要处理以下约束:

最终一致性问题:S3 从 2020 年 12 月起对新对象的 PUT 操作提供强一致性保证(Read-after-Write Consistency)。但在此之前,S3 的 LIST 操作是最终一致的,可能遗漏刚写入的对象。这也是 Delta Lake、Iceberg 等表格式出现的原因之一——它们通过自己管理文件列表来避免依赖 S3 LIST 操作的一致性。

小文件问题:对象存储对每次请求都有固定的延迟开销(10-50 ms)。如果数据被拆分成大量小文件(每个文件几 KB 到几十 KB),读取 10000 个小文件的总延迟远高于读取一个等大的大文件。这就是为什么 Snowflake 的微分区大小在 50-500 MB,Iceberg 推荐的目标文件大小是 128-512 MB——都是为了摊薄每次请求的固定开销。

重命名不是原子操作:对象存储没有原子重命名(Atomic Rename)操作。很多传统文件系统依赖 rename 的原子性来实现写入的原子提交(先写临时文件,再 rename 为目标文件)。在对象存储上,表格式通过事务日志或元数据文件来实现提交的原子性,而不是依赖 rename。

没有目录概念:对象存储是扁平的键值存储,“目录”只是通过键的前缀模拟出来的。LIST 操作按前缀过滤,不是真正的目录遍历。这影响了文件组织方式和分区发现(Partition Discovery)机制的设计。


七、本地缓存策略

存算分离架构的性能关键在于本地缓存。计算节点的本地 SSD 或内存用于缓存从远端存储读取的数据,缓存命中率直接决定了查询延迟。

7.1 缓存的三个层次

┌───────────────────────────────────────────┐
│              计算节点                      │
│  ┌─────────────────────────────────────┐  │
│  │  L1: 内存缓存(Buffer Pool / Page) │  │
│  │  容量: 数十 GB                       │  │
│  │  延迟: ~0.0001 ms(100 ns)          │  │
│  ├─────────────────────────────────────┤  │
│  │  L2: 本地 SSD 缓存                  │  │
│  │  容量: 数百 GB ~ 数 TB               │  │
│  │  延迟: ~0.05-0.1 ms                 │  │
│  ├─────────────────────────────────────┤  │
│  │  L3: 远端存储(S3 / 分布式存储)      │  │
│  │  容量: 无限                          │  │
│  │  延迟: ~10-50 ms                    │  │
│  └─────────────────────────────────────┘  │
└───────────────────────────────────────────┘

7.2 读缓存策略

按需缓存(Demand Paging):最简单的策略。缓存未命中时从远端存储读取,读取后写入缓存。淘汰策略通常用 LRU(Least Recently Used,最近最少使用)或 LRU-K。

预取(Prefetch):在查询执行计划确定后,预测即将需要的数据块,提前从远端存储拉取到本地缓存。预取的关键是准确性——预取了不需要的数据浪费带宽和缓存空间,预取不到需要的数据又退化为按需缓存。

Snowflake 的预取策略基于查询计划。查询优化器知道需要扫描哪些微分区,可以在查询开始执行前就发起对这些分区的预取请求。由于微分区是不可变的且有明确的文件路径,预取的准确率接近 100%。

一致性哈希分配:在多节点的计算集群中,把不同的数据分区分配给不同的节点缓存,避免重复缓存同一份数据。Snowflake 使用一致性哈希(Consistent Hashing)把微分区分配给 VW 中的各个节点——每个节点负责缓存特定的微分区集合。查询时,如果某个微分区分配给了节点 A,那么需要读取该分区的扫描任务也会被调度到节点 A 上执行。

# 一致性哈希分配微分区到计算节点(简化示意)
import hashlib

def assign_partition_to_node(partition_id: str, node_list: list[str]) -> str:
    """将微分区分配到特定的计算节点,最大化缓存命中率"""
    hash_val = int(hashlib.md5(partition_id.encode()).hexdigest(), 16)
    node_index = hash_val % len(node_list)
    return node_list[node_index]

# 假设 VW 有 4 个节点
nodes = ["node-0", "node-1", "node-2", "node-3"]

# 分配微分区
partitions = ["orders/micro_part_0001.parquet",
              "orders/micro_part_0002.parquet",
              "orders/micro_part_0003.parquet",
              "orders/micro_part_0004.parquet",
              "orders/micro_part_0005.parquet"]

for p in partitions:
    assigned = assign_partition_to_node(p, nodes)
    print(f"{p} -> {assigned}")

# 当 VW 扩展到 8 个节点时,约 50% 的分区分配会改变
# 一致性哈希可以把这个比例降到更低,减少缓存失效

7.3 缓存淘汰策略

当本地缓存空间不足时,需要淘汰一部分缓存数据。常见的淘汰策略:

策略 原理 优点 缺点
LRU 淘汰最近最少使用的数据 实现简单 扫描查询会污染缓存
LRU-K 根据最近第 K 次访问时间淘汰 抗扫描污染 实现复杂度高
LFU 淘汰访问频率最低的数据 保护高频数据 历史频率会阻碍新数据
ARC 自适应平衡 LRU 和 LFU 自适应,无需调参 实现复杂
FIFO 按进入顺序淘汰 极简实现 不考虑访问模式

Snowflake 在实践中使用的是文件级别的 LRU 策略。由于微分区的粒度在 16 MB 左右(压缩后),缓存的管理粒度和淘汰粒度都比传统数据库的页级缓存(4 KB 到 16 KB)要粗得多,但这反而降低了元数据管理的开销。

7.4 分层存储

分层存储(Tiered Storage)把数据按访问频率分为多个层次:

热数据 ──→ 内存 / 本地 NVMe SSD
           访问频率高,延迟要求严格
           成本:最高

温数据 ──→ 本地 HDD / 近线存储
           访问频率中等,延迟容忍度中等
           成本:中等

冷数据 ──→ 对象存储(S3 标准)
           偶尔访问,延迟容忍度高
           成本:低

归档数据 ──→ 对象存储(S3 Glacier)
            极少访问,取回需要数小时
            成本:极低

分层的触发条件通常基于访问频率或时间。例如,30 天未访问的数据从热层降级到温层,90 天未访问的数据降级到冷层。TiDB 的冷存储和 Snowflake 的存储层本质上都是分层存储的应用。


八、网络带宽需求计算

存算分离架构把计算和存储之间的通信从本地总线(PCIe、NVMe)变成了网络通信。网络带宽成为系统吞吐量的上限。

8.1 带宽需求估算模型

一个简单的估算公式:

所需网络带宽 = (查询扫描数据量 x (1 - 缓存命中率)) / 查询执行时间目标

示例 1:OLAP 全表扫描
  表大小:1 TB(压缩后 300 GB)
  缓存命中率:0%(冷查询)
  执行时间目标:60 秒
  所需带宽 = 300 GB / 60 s = 5 GB/s = 40 Gbps

示例 2:OLAP 热数据查询
  表大小:1 TB(压缩后 300 GB)
  缓存命中率:80%
  执行时间目标:10 秒
  所需带宽 = 300 GB x 0.2 / 10 s = 6 GB/s = 48 Gbps
  (注意:虽然数据量少了,但执行时间目标也更短了)

示例 3:OLTP 高并发点查
  每秒查询数:10000 QPS
  每次查询读取数据量:8 KB(单页)
  缓存命中率:95%
  所需带宽 = 10000 x 8 KB x 0.05 = 4 MB/s ≈ 32 Mbps
  (OLTP 点查对带宽需求很低,瓶颈在延迟而非带宽)

8.2 云环境的网络带宽限制

不同 EC2 实例类型的网络带宽差异很大:

实例类型 vCPU 内存 网络带宽 月成本(近似)
c6i.xlarge 4 8 GB 最高 12.5 Gbps 约 124 美元
c6i.4xlarge 16 32 GB 最高 12.5 Gbps 约 496 美元
c6i.8xlarge 32 64 GB 12.5 Gbps 约 992 美元
c6i.16xlarge 64 128 GB 25 Gbps 约 1984 美元
c6i.24xlarge 96 192 GB 37.5 Gbps 约 2976 美元
c6i.metal 128 256 GB 50 Gbps 约 3968 美元

关键观察:对于中等规格的实例(如 c6i.4xlarge),网络带宽上限是 12.5 Gbps,约 1.56 GB/s。如果一个 OLAP 查询需要从 S3 读取 100 GB 的数据(缓存未命中),单个实例需要约 64 秒才能把数据拉完。这就是为什么 Snowflake 的 VW 由多个实例组成——通过并行化来分摊带宽需求。

8.3 S3 的带宽特性

S3 的带宽不是由单个连接决定的,而是由并行连接数和请求方式决定的:

实践中的带宽优化手段:

1. 并行化读取
   不要一次读一个文件,而是同时发起多个文件的读取请求。
   Snowflake 的每个计算节点会同时发起数十个并行 S3 GET 请求。

2. Range GET
   对于大文件(如 256 MB 的微分区),使用 HTTP Range 请求并行拉取
   不同的字节范围,然后在本地拼接。

3. 前缀打散
   S3 按前缀限流。把文件分散到多个前缀下可以提高总请求数上限。
   例如,用文件哈希的前几位作为前缀:
     s3://bucket/a1/data_001.parquet
     s3://bucket/b2/data_002.parquet
     s3://bucket/c3/data_003.parquet

4. 列裁剪
   列式存储格式(Parquet / ORC)支持只读取需要的列。
   一个 100 列的表只查 3 列,实际读取的数据量可能只有总数据量的 3%。

8.4 带宽不足时的表现

当网络带宽成为瓶颈时,存算分离系统的典型表现:

解决带宽瓶颈的工程手段:

  1. 提高缓存命中率(最有效)
  2. 增加计算节点数(分摊带宽需求)
  3. 使用更大规格的实例(更高的网络带宽上限)
  4. 优化查询,减少扫描数据量(分区裁剪、列裁剪、谓词下推)

8.5 网络延迟对事务性能的影响

带宽之外,延迟是存算分离在 OLTP 场景下的另一个关键约束。本地 NVMe SSD 的读延迟在 50 到 100 微秒,而 S3 的首字节延迟在 10 到 50 毫秒——差距在 100 到 1000 倍。

对于 OLTP 事务,一个典型的读写事务可能包含 5 到 10 次读操作。如果每次读操作都需要访问远端存储,事务延迟会从 0.5 毫秒(本地 SSD)膨胀到 50 到 500 毫秒(远端存储),完全不可接受。

这就是为什么 Aurora 选择了”Shared-Disk + 专用存储层”而非”Shared-Storage + 对象存储”的路线。Aurora 的存储层延迟在亚毫秒级别,比 S3 低两个数量级,但比本地 SSD 高一个数量级。Aurora 通过”日志即数据库”的设计,把写路径上的网络往返次数降到最低——只发送 Redo Log,不发送数据页。

对于 Snowflake 这种 OLAP 系统,单次查询扫描的数据量大(GB 到 TB 级),即使首次请求有 50 毫秒的延迟,分摊到整个查询的执行时间中影响很小。这就是为什么 Snowflake 可以直接使用 S3 作为存储层,而不需要一个低延迟的专用存储层。

延迟对不同工作负载的影响:

OLTP(每事务 5-10 次 I/O):
  本地 SSD:5 x 0.1ms = 0.5ms
  Aurora 存储层:5 x 0.5ms = 2.5ms(可接受)
  S3:5 x 30ms = 150ms(不可接受)

OLAP(单查询扫描 1000 个文件):
  本地 SSD:并行读取,受吞吐限制,约 10-30s
  S3:并行读取,首次延迟 30ms + 吞吐时间,约 30-60s
  延迟占比:30ms / 30s = 0.1%(可忽略)

九、存算一体 vs 分离的性能对比

9.1 对比框架

存算一体和分离的性能对比不能用一个简单的”谁快谁慢”来概括,需要区分不同的工作负载类型。

工作负载特征 存算一体的优势 存算分离的优势
低延迟点查(OLTP) 本地 SSD 延迟 ~0.1ms,确定性高 依赖缓存命中,未命中时延迟高 10-100x
高吞吐扫描(OLAP) 受限于单机存储容量 可并行从对象存储拉取,聚合吞吐更高
突发查询负载 扩容慢,需数据迁移 秒级扩容计算节点
多租户隔离 需物理隔离或复杂的资源管控 天然隔离(不同 VW)
混合读写(HTAP) 读写在同一节点,可能互相干扰 读写可以路由到不同计算集群
冷数据查询 本地磁盘空间有限 对象存储无限扩展

9.2 一个实际的性能对比场景

下面是一个假设场景的性能估算,用于说明存算一体和存算分离在不同条件下的表现差异。数据基于公开的系统特性做定性分析,不是实测数据。

场景:一个 10 TB 的订单表,执行一个聚合查询,扫描 1 个月的数据(约 1 TB,压缩后 300 GB),计算每天的订单总额。

方案 A:存算一体(4 节点 Shared-Nothing 集群)
  每个节点存储 2.5 TB(约 750 GB 压缩数据)
  本地 NVMe SSD 顺序读吞吐:3 GB/s
  需要扫描的数据分布在 4 个节点上:每个节点约 75 GB
  单节点扫描时间:75 GB / 3 GB/s = 25 秒
  并行扫描,整体时间:约 25 秒

方案 B:存算分离(4 节点计算集群 + S3 存储)
  情况 B1:缓存全部命中
    本地 SSD 缓存顺序读吞吐:约 2 GB/s(受缓存管理开销影响)
    每个节点扫描 75 GB:75 GB / 2 GB/s = 37.5 秒
    整体时间:约 38 秒

  情况 B2:缓存命中率 80%
    缓存读取:60 GB / 2 GB/s = 30 秒
    S3 读取:15 GB,每节点 32 个并行连接,每连接 80 MB/s
      聚合吞吐:32 x 80 MB/s = 2.56 GB/s
      S3 读取时间:15 GB / 2.56 GB/s ≈ 6 秒
    但 S3 读取和缓存读取可以交错进行
    整体时间:约 32-35 秒

  情况 B3:缓存完全未命中(冷查询)
    S3 读取:75 GB,每节点 32 个并行连接
    聚合吞吐:2.56 GB/s
    S3 读取时间:75 GB / 2.56 GB/s ≈ 29 秒
    加上 S3 首次请求延迟和元数据查找:约 30-35 秒
    但受限于实例网络带宽上限(12.5 Gbps ≈ 1.56 GB/s)
    实际时间:75 GB / 1.56 GB/s ≈ 48 秒

从这个估算可以看出:

  1. 存算一体在本地数据访问场景下有明显的延迟优势。
  2. 存算分离在缓存命中率高时,性能接近存算一体。
  3. 存算分离在冷查询场景下,性能受网络带宽制约,可能慢 50% 到 100%。
  4. 存算分离的核心优势不在于单查询性能,而在于弹性伸缩和成本优化。

9.3 什么时候选择存算分离

基于以上分析,存算分离在以下场景中收益最大:

  1. 负载波动大:白天高峰需要 64 核计算,夜间只需要 4 核。存算一体必须按峰值配置,存算分离可以按需伸缩。
  2. 数据量大但查询稀疏:100 TB 数据,每天只查几次。存算一体需要为 100 TB 数据配满计算资源,存算分离只需要在查询时启动计算节点。
  3. 多租户/多团队共享数据:不同团队需要用不同的计算资源查询同一份数据。存算分离天然支持多个计算集群挂载同一份存储。
  4. 冷热数据分层需求明确:大部分数据是冷数据,只有少量热数据需要低延迟访问。存算分离可以把冷数据放到低成本存储,热数据通过缓存保持低延迟。
  5. 云原生环境:云环境下按使用量付费的模型天然适合存算分离——计算不用时可以完全停止计费,存储按容量付费。

反过来,以下场景存算一体可能更合适:

  1. 延迟敏感的 OLTP:对 P99 延迟有严格要求(如 < 5ms),不能承受缓存未命中带来的延迟抖动。
  2. 数据量小但计算密集:数据只有几十 GB,但 QPS 很高。这种场景下数据完全可以放在本地 SSD 甚至内存中,网络通信是纯开销。
  3. 网络环境不可靠:在网络质量不稳定的环境中,存算分离的可用性依赖网络可用性。

9.4 混合架构的工程实践

实际生产环境中,很少有系统是纯粹的存算一体或纯粹的存算分离。更常见的是混合架构——根据数据的访问模式,在不同的层次上做不同程度的分离。

一个典型的混合架构设计:

┌───────────────────────────────────────────────────────────┐
│                      应用层                                │
│  ┌──────────────┐   ┌─────────────────┐                   │
│  │ OLTP 请求     │   │ OLAP / 报表请求  │                   │
│  └──────┬───────┘   └───────┬─────────┘                   │
│         │                   │                              │
├─────────┼───────────────────┼──────────────────────────────┤
│         ▼                   ▼                              │
│  ┌──────────────┐   ┌─────────────────┐                   │
│  │ OLTP 集群     │   │ OLAP 集群       │                   │
│  │ (存算一体)    │   │ (存算分离)       │                   │
│  │ 本地 SSD     │   │ 计算节点 + S3    │                   │
│  │ 低延迟       │   │ 弹性伸缩         │                   │
│  └──────┬───────┘   └───────┬─────────┘                   │
│         │                   │                              │
│         │     CDC / ETL     │                              │
│         └─────────→─────────┘                              │
│                                                            │
│  OLTP 集群的数据通过 CDC(Change Data Capture)              │
│  实时同步到 OLAP 集群的对象存储层                             │
└───────────────────────────────────────────────────────────┘

这种混合架构在实践中很常见:

混合架构的核心挑战是数据一致性。CDC 管道引入了同步延迟,OLAP 集群看到的数据总是比 OLTP 集群滞后几秒到几分钟。对于大多数分析场景,这个延迟是可以接受的。但如果业务要求 OLAP 查询和 OLTP 事务看到完全一致的数据,就需要引入更复杂的一致性机制,或者使用 TiDB 这种原生 HTAP 架构。


十、参考文献

论文

  1. Dageville, B., et al. (2016). The Snowflake Elastic Data Warehouse. SIGMOD 2016. Snowflake 的核心架构论文,详细描述了三层架构、微分区和 Virtual Warehouse 设计。

  2. Verbitski, A., et al. (2017). Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases. SIGMOD 2017. Aurora 的核心论文,提出”日志即数据库”的设计理念和 Quorum 协议。

  3. Verbitski, A., et al. (2018). Amazon Aurora: On Avoiding Distributed Consensus for I/Os, Commits, and Membership Changes. SIGMOD 2018. Aurora 的后续论文,深入讨论如何避免分布式共识的开销。

  4. Huang, D., et al. (2020). TiDB: A Raft-based HTAP Database. VLDB 2020. TiDB 的架构论文,描述了 TiKV + TiFlash 的 HTAP 设计。

  5. Armbrust, M., et al. (2021). Lakehouse: A New Generation of Open Platforms that Unify Data Warehousing and Advanced Analytics. CIDR 2021. Databricks 提出的 Lakehouse 架构论文。

官方文档

  1. Snowflake Documentation. Understanding Snowflake Table Structures. https://docs.snowflake.com/en/user-guide/tables-micro-partitions. 微分区的官方说明。

  2. Snowflake Documentation. Virtual Warehouses. https://docs.snowflake.com/en/user-guide/warehouses. VW 的配置和管理文档。

  3. Amazon Aurora Documentation. Amazon Aurora Storage and Reliability. https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.Overview.StorageReliability.html. Aurora 存储层的官方文档。

  4. TiDB Documentation. TiFlash Overview. https://docs.pingcap.com/tidb/stable/tiflash-overview. TiFlash 的官方架构说明。

  5. TiDB Documentation. TiDB Cloud Serverless. https://docs.pingcap.com/tidbcloud/select-cluster-tier#tidb-cloud-serverless. TiDB 云原生存算分离的实践。

技术博客与设计文档

  1. AWS Architecture Blog. Amazon Aurora Under the Hood: Quorum and Correlated Failure. https://aws.amazon.com/blogs/database/amazon-aurora-under-the-hood-quorum-and-correlated-failure/. Aurora Quorum 协议的工程解读。

  2. Snowflake Engineering Blog. How Snowflake Handles Caching. Snowflake 缓存策略的工程说明。

  3. Apache Iceberg Documentation. https://iceberg.apache.org/docs/latest/. Iceberg 表格式的官方文档。

  4. Delta Lake Documentation. https://docs.delta.io/latest/index.html. Delta Lake 表格式的官方文档。

书籍

  1. Kleppmann, M. (2017). Designing Data-Intensive Applications. O’Reilly. 数据密集型系统设计的经典教材,第六章对分区与复制有深入讨论。

  2. Hellerstein, J. M., Stonebraker, M., & Hamilton, J. (2007). Architecture of a Database System. Foundations and Trends in Databases, 1(2). 数据库系统架构的综述论文,对 Shared-Nothing 和 Shared-Disk 的历史演进有详细梳理。


上一篇: 多租户存储隔离 下一篇: 数据持久性工程

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-10-20 · storage

【存储工程】计算存储分离实践

深入剖析计算存储分离——Aurora日志即存储、TiDB的TiKV+TiFlash分层、Snowflake对象存储+缓存、ClickHouse存算分离模式的架构对比与选型指南

2025-09-18 · storage

【存储工程】数据湖存储格式:Delta Lake、Iceberg 与 Hudi

数据湖(Data Lake)的核心思想是把海量异构数据以开放格式存储在廉价的对象存储(Object Storage)上,用计算引擎按需查询。Apache Parquet 解决了列式编码(Columnar Encoding)问题,让分析查询的 I/O 效率提升了一个数量级。但 Parquet 只是一个文件格式,它不管事务…

2026-04-22 · db / storage

数据库内核实验索引

汇总本站数据库内核与存储引擎实验文章,重点覆盖从零实现 LSM-Tree 及其工程权衡。

2026-04-22 · storage

存储工程索引

汇总本站存储工程系列文章,覆盖 HDD、SSD、NVMe、持久内存、索引结构、压缩、分布式存储与对象存储。


By .