“计算和存储在同一台机器上”——数据库 40 年来都基于这个假设设计。扩容必须一起扩,缩容必须一起缩:存储满了但 CPU 还闲着,或者 CPU 打满但磁盘还空着一半。云时代这个假设正在瓦解。Aurora 把 redo log 直接写到分布式存储层,数据库实例不再刷脏页;Neon 把存储层做成可按时间回溯的 Page Server,计算节点无状态、随时增减。
计算存储分离不是”把数据库搬到云上”,而是重新设计数据库的存储架构——让存储和计算各自按需扩展。
一、为什么需要计算存储分离
1.1 传统架构的困境
传统数据库(MySQL/PostgreSQL)采用计算存储耦合架构:每个数据库实例同时拥有计算资源(CPU/内存)和存储资源(本地磁盘)。
| 问题 | 传统架构 | 根因 |
|---|---|---|
| 存储浪费 | 3 节点存 3 份完整数据 | 每个节点独立存储 |
| 扩缩容慢 | 增加副本需全量拷贝 | 数据随计算节点绑定 |
| 故障恢复慢 | 需要从 Checkpoint 重放 WAL | 本地存储不可用时数据丢失 |
| 只读副本延迟 | 异步复制,秒级延迟 | 复制整个数据页 |
| 升级停机 | 滚动升级期间服务不可用 | 计算和存储耦合升级 |
1.2 云原生存储的核心思想
计算存储分离的核心思想:将数据持久化交给共享存储层,计算节点变成无状态。
| 维度 | 传统架构 | 计算存储分离 |
|---|---|---|
| 数据副本 | 每个计算节点一份 | 共享存储层一份(存储层内部冗余) |
| 扩容 | 拷贝数据,分钟级 | 启动计算节点,秒级 |
| 故障恢复 | 重放日志,分钟级 | 新节点连接存储,秒级 |
| 只读副本 | 异步复制,有延迟 | 共享读取,无延迟 |
| 升级 | 滚动升级,可能停机 | 计算节点独立升级 |
二、Aurora:日志即数据库
2.1 架构总览
Amazon Aurora 是 AWS 的云原生关系数据库,核心创新是 “日志即数据库”(Log is Database)——计算节点只写 Redo Log 到共享存储,存储节点负责将日志重放为数据页。
2.2 Quorum 复制模型
Aurora 使用 Quorum 协议保证数据一致性:
| 参数 | 值 | 含义 |
|---|---|---|
| 总副本数 | 6 | 跨 3 个 AZ,每个 AZ 2 副本 |
| 写入 Quorum (Vw) | 4 | 至少 4 个副本确认写入 |
| 读取 Quorum (Vr) | 3 | 至少从 3 个副本读取 |
| Vw + Vr > V | 4 + 3 > 6 | 保证读取总能看到最新写入 |
# Quorum 读写伪代码class AuroraQuorum: TOTAL_REPLICAS = 6 WRITE_QUORUM = 4 READ_QUORUM = 3
def write(self, log_record): """写入:至少 4 个副本确认""" acks = 0 for replica in self.replicas: if replica.append_log(log_record): acks += 1 if acks >= self.WRITE_QUORUM: return True # 写入成功 return False # 写入失败
def read(self, lsn): """读取:至少 3 个副本返回,取最高 LSN""" responses = [] for replica in self.replicas: resp = replica.read_lsn() responses.append(resp) if len(responses) >= self.READ_QUORUM: # 取最高 LSN 的响应 return max(responses, key=lambda r: r.lsn)2.3 日志即数据库的工作流程
传统数据库写入流程: 1. 写 Redo Log(WAL) 2. 写数据页到磁盘 3. 写 Undo Log 总 I/O:3 次写操作
Aurora 写入流程: 1. 写 Redo Log 到存储层(6 副本,4 确认) 2. 完成! 总 I/O:1 次写操作(日志)
存储节点后台操作: 1. 接收 Redo Log 2. 将日志重放为数据页(后台异步) 3. 定期生成 Checkpoint 4. 垃圾回收旧版本Aurora 的关键洞察:数据库只需要写日志,数据页可以由存储节点异步生成。这把网络 I/O 从”写数据页”(大)减少到”写日志”(小),通常减少 10-100 倍的网络流量。
2.4 只读副本
-- Aurora 只读副本-- 创建只读副本(秒级完成)-- AWS CLIaws rds create-db-instance-read-replica \ --db-instance-identifier aurora-reader-1 \ --source-db-instance-identifier aurora-cluster
-- 只读副本特点:-- 1. 共享同一存储卷,无需数据拷贝-- 2. 延迟通常 < 20ms-- 3. 最多 15 个只读副本-- 4. 自动负载均衡(Reader Endpoint)
-- 连接写入节点mysql -h aurora-cluster.cluster-xxxxx.rds.amazonaws.com
-- 连接只读副本(自动路由)mysql -h aurora-cluster.cluster-ro-xxxxx.rds.amazonaws.com| 特性 | 传统只读副本 | Aurora 只读副本 |
|---|---|---|
| 创建时间 | 小时级(全量拷贝) | 分钟级(共享存储) |
| 复制延迟 | 秒级 | 毫秒级 |
| 存储开销 | 每副本一份完整数据 | 共享存储,零额外开销 |
| 最大副本数 | 通常 5 个 | 15 个 |
三、Neon:Serverless PostgreSQL
3.1 架构总览
Neon 是一个开源的 Serverless PostgreSQL 实现,采用计算存储分离架构,核心创新是 Page Server + WAL Service + Safekeeper 的三层架构。
3.2 核心组件
| 组件 | 作用 | 类比 |
|---|---|---|
| Compute Node | 无状态 PostgreSQL,处理 SQL 查询 | Aurora 的计算节点 |
| Safekeeper | 持久化 WAL,保证不丢失 | Aurora 的存储节点(日志部分) |
| Page Server | 物化数据页,响应计算节点的页面请求 | Aurora 的存储节点(数据页部分) |
| S3 | 归档 WAL 和 Checkpoint | Aurora 的 S3 后端 |
3.3 分支:Neon 的杀手级特性
# Neon 分支:秒级创建数据库分支# 类似 Git 的分支概念,但用于数据库
# 创建分支(基于当前时间点)neon branch create my-branch --parent main
# 创建分支(基于特定时间点)neon branch create my-branch --parent main --timestamp "2026-06-15T10:00:00Z"
# 创建分支(基于特定 LSN)neon branch create my-branch --parent main --lsn 0/1A2B3C4D
# 列出所有分支neon branch list
# 删除分支neon branch delete my-branch# Neon 分支的工作原理# 1. 分支共享父分支的存储(Copy-on-Write)# 2. 新写入的数据独立存储# 3. 读取时:先查自己的数据,再查父分支的数据
class NeonBranch: def __init__(self, parent_lsn, page_server): self.parent_lsn = parent_lsn # 分支点的 LSN self.page_server = page_server self.own_wal = [] # 分支自己的 WAL
def read_page(self, page_id): """读取页面:先查自己的 WAL,再查父分支""" # 1. 在自己的 WAL 中查找 for record in reversed(self.own_wal): if record.page_id == page_id: return record.data
# 2. 在父分支中查找(LSN <= parent_lsn) return self.page_server.get_page(page_id, self.parent_lsn)
def write_page(self, page_id, data): """写入页面:追加到自己的 WAL""" self.own_wal.append(WalRecord(page_id, data))Neon 的分支功能对开发流程有革命性影响:每个 PR 可以有独立的数据库分支,测试完成后删除。无需维护多套测试数据库,也无需担心测试数据污染。
3.4 Serverless 自动伸缩
# Neon Serverless 配置compute: # 自动挂起:无流量时缩容到零 auto_suspend: 300 # 5 分钟无活动后挂起
# 自动恢复:有连接时自动启动 auto_resume: true
# 计算规格范围 min_cu: 0.25 # 最小 0.25 Compute Unit max_cu: 8 # 最大 8 Compute Unit
# 冷启动时间 cold_start: ~500ms # 从挂起到可用| 场景 | 传统 RDS | Neon Serverless |
|---|---|---|
| 开发/测试 | 24/7 运行,持续计费 | 无流量时自动挂起,零计费 |
| 流量波动 | 按峰值配置,资源浪费 | 自动伸缩,按需付费 |
| 多租户 | 每租户一个实例 | 每租户一个分支,共享存储 |
| 冷启动 | 分钟级 | 亚秒级 |
四、共享存储模型
4.1 存储后端对比
| 存储类型 | 代表 | 延迟 | 吞吐 | 适用场景 |
|---|---|---|---|---|
| 本地 SSD | NVMe SSD | 10-100 μs | 3-7 GB/s | 单机数据库 |
| 网络块存储 | AWS EBS gp3 | 1-5 ms | 1 GB/s | 传统云数据库 |
| 网络块存储(高性能) | AWS EBS io2 | 0.5-2 ms | 4 GB/s | 高性能数据库 |
| 对象存储 | AWS S3 | 10-100 ms | 5 GB/s | 归档、备份 |
| 内存缓存 | ElastiCache | 0.1-1 ms | 10+ GB/s | 热数据缓存 |
4.2 本地缓存策略
计算存储分离架构中,计算节点需要本地缓存来弥补共享存储的延迟:
// Aurora 本地缓存策略type AuroraBufferPool struct { shared *SharedStorage // 共享存储层 local *LRUCache // 本地 Buffer Pool cacheSize int // 缓存大小}
func (bp *AuroraBufferPool) ReadPage(pageID PageID) (*Page, error) { // 1. 查本地缓存 if page, ok := bp.local.Get(pageID); ok { return page, nil // 缓存命中,微秒级 }
// 2. 缓存未命中,从共享存储读取 page, err := bp.shared.ReadPage(pageID) if err != nil { return nil, err // 毫秒级延迟 }
// 3. 放入本地缓存 bp.local.Put(pageID, page) return page, nil}
func (bp *AuroraBufferPool) WritePage(pageID PageID, data []byte) error { // 写入不需要立即写数据页! // 只写 Redo Log 到共享存储 // 数据页在本地缓存中修改,后续异步刷回 bp.local.Put(pageID, NewPage(data)) return bp.shared.AppendLog(pageID, data)}| 缓存层级 | 大小 | 延迟 | 命中率目标 |
|---|---|---|---|
| L1 CPU Cache | 32-64 MB | 1-10 ns | — |
| L2 Buffer Pool | 1-128 GB | 100-500 ns | > 99% |
| L3 本地 SSD 缓存 | 100 GB - 1 TB | 10-100 μs | > 95% |
| L4 共享存储 | PB 级 | 1-5 ms | — |
4.3 缓存一致性
缓存一致性是计算存储分离的核心挑战。只读节点的本地缓存可能包含过期数据,需要通过 LSN 比较来检测和刷新。Aurora 使用”读修复”机制:读取时检查 LSN,如果本地版本过旧则从共享存储获取最新版本。
五、成本分析
5.1 存储成本对比
| 成本项 | 传统 RDS | Aurora | Neon |
|---|---|---|---|
| 计算节点 | 1 写 + N 读 | 1 写 + N 读 | 按需 CU |
| 存储费用 | N × 本地磁盘 | 1 × 共享存储 | 1 × 共享存储 |
| I/O 费用 | 包含在实例费 | 按 I/O 请求计费 | 包含在存储费 |
| 网络费用 | 复制流量 | 日志流量(小) | WAL 流量(小) |
| 备份费用 | 快照存储 | 自动增量备份 | S3 归档 |
5.2 总体拥有成本(TCO)
场景:3 节点 MySQL 集群,1TB 数据
传统 RDS(3 × db.r5.xlarge): 计算:3 × $0.34/h = $734/月 存储:3 × 1TB × $0.115/GB = $345/月 总计:$1,079/月
Aurora(1 写 + 2 读): 计算:3 × $0.34/h = $734/月 存储:1 × 1TB × $0.10/GB = $100/月 I/O:~$50/月(估算) 总计:$884/月(节省 18%)
Neon Serverless: 计算:按需 ~$200/月(开发/测试场景) 存储:1TB × $0.12/GB = $120/月 总计:$320/月(节省 70%)成本节省因场景而异。生产环境(7×24 高负载)的节省较少,开发/测试环境(间歇性负载)的节省显著。Serverless 的优势在于按需付费,而不是绝对成本更低。
六、性能影响
6.1 延迟分析
| 操作 | 本地存储 | 共享存储 | 增量 |
|---|---|---|---|
| 顺序写(WAL) | 0.1-0.5 ms | 1-3 ms | 2-6x |
| 随机读(点查) | 0.05-0.2 ms | 0.5-2 ms | 5-10x |
| 范围扫描 | 0.01 ms/行 | 0.05 ms/行 | 5x |
| 事务提交 | 0.5-2 ms | 2-5 ms | 2-4x |
6.2 延迟优化策略
-- Aurora 性能优化-- 1. 增大 Buffer Pool 提高缓存命中率-- 参数:innodb_buffer_pool_size(建议 75% 可用内存)
-- 2. 使用 Aurora Global Database 减少跨区域延迟aws rds create-global-cluster \ --global-cluster-identifier aurora-global \ --engine aurora-mysql
-- 3. 使用 Aurora Parallel Query 加速分析查询SET aurora_parallel_query=ON;
SELECT /*+ PQ */ COUNT(*), SUM(amount)FROM ordersWHERE created_at >= '2026-01-01';# Neon 性能优化# 1. 预热计算节点(减少冷启动)neon compute start --project-id my-project
# 2. 调整 Page Server 缓存大小# pageserver 配置[pageserver]page_cache_size = "4GB" # 页面缓存max_file_descriptors = 8192 # 文件描述符eviction_policy = "lru" # 淘汰策略
# 3. Safekeeper WAL 持久化配置[safekeeper]wal_keep_size = "1GB" # 保留的 WAL 大小backup_parallelism = 4 # S3 上传并行度七、Aurora vs Neon 对比
| 维度 | Aurora | Neon |
|---|---|---|
| 兼容性 | MySQL / PostgreSQL | PostgreSQL |
| 开源 | 否(AWS 专有) | 是(Apache 2.0) |
| 存储模型 | 6 副本 Quorum | 3 Safekeeper + S3 |
| 日志协议 | 自定义 Quorum | Paxos 变体 |
| 页面物化 | 存储节点后台重放 | Page Server 按需物化 |
| 分支 | 不支持 | 支持(杀手级特性) |
| Serverless | 支持(Aurora Serverless v2) | 原生支持 |
| 冷启动 | ~1 秒 | ~500ms |
| 最大存储 | 128 TB | 无限制(S3) |
| 只读副本 | 最多 15 个 | 无限制 |
| 部署 | AWS 专有 | 任何云/本地 |
八、总结
| 维度 | 传统 RDS | Aurora | Neon |
|---|---|---|---|
| 计算存储 | 耦合 | 分离 | 分离 |
| 写入模型 | WAL + 数据页 | 只写 WAL | 只写 WAL |
| 存储冗余 | 主从复制 | 6 副本 Quorum | 3 Safekeeper + S3 |
| 只读副本 | 异步复制 | 共享存储 | 共享存储 |
| 扩缩容 | 分钟级 | 秒级 | 秒级 |
| 分支 | 不支持 | 不支持 | 支持 |
| Serverless | 不支持 | 支持 | 原生支持 |
| 开源 | 部分 | 否 | 是 |
| 适用场景 | 传统 OLTP | 云上 OLTP | 开发/测试/OLTP |
计算存储分离不是银弹。它用网络延迟换取了弹性、可靠性和运维便利性。对于延迟极度敏感的场景(如高频交易),本地存储仍然是更好的选择。但对于绝大多数云上应用,计算存储分离带来的运维简化远超延迟的代价。
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






