PolarDB:云原生数据库架构(论文笔记)
发布于:
论文:PolarDB: A Cloud-Native Database Designed for the Cloud(Wang et al., SIGMOD 2022)
PolarDB 是阿里云开发的云原生数据库系统,采用存储计算分离架构,实现了高性能、高可用和弹性扩展。本文深入解析 PolarDB 的架构设计、核心机制和工程实践。
1. PolarDB 的设计目标与挑战
1.1 云原生数据库的需求
传统数据库在云环境下面临的挑战:
- 弹性扩展:计算和存储需要独立扩展
- 高可用性:需要自动故障恢复,减少 RTO
- 成本优化:按需付费,避免资源浪费
- 性能要求:保持接近本地数据库的性能
传统架构 vs 云原生架构:
传统架构:
┌─────────────────┐
│ Database Node │
│ ┌───────────┐ │
│ │ Compute │ │
│ └───────────┘ │
│ ┌───────────┐ │
│ │ Storage │ │
│ └───────────┘ │
└─────────────────┘
问题:计算和存储耦合,难以独立扩展
云原生架构(PolarDB):
┌─────────────┐ ┌─────────────┐
│ Compute │────▶│ Storage │
│ Nodes │ │ Cluster │
└─────────────┘ └─────────────┘
优势:计算和存储分离,独立扩展
1.2 核心设计目标
PolarDB 的设计目标:
- 存储计算分离:计算节点和存储集群解耦,独立扩展
- 高性能:接近本地数据库的性能,延迟 < 1ms
- 高可用:自动故障恢复,RTO < 30s
- 弹性扩展:计算节点和存储容量独立扩展
- 兼容性:兼容 MySQL/PostgreSQL 协议
2. PolarDB 的架构设计
2.1 整体架构
PolarDB 采用三层架构:
┌─────────────────────────────────────────┐
│ Application Layer │
└─────────────────────────────────────────┘
│
┌─────────────────────────────────────────┐
│ Compute Layer (Read/Write Nodes) │
│ ┌──────────┐ ┌──────────┐ ┌────────┐│
│ │ Primary │ │ Replica1 │ │Replica2││
│ └──────────┘ └──────────┘ └────────┘│
└─────────────────────────────────────────┘
│
┌──────────┴──────────┐
│ PolarFS (RDMA) │
└──────────┬──────────┘
│
┌─────────────────────────────────────────┐
│ Storage Layer (PolarStore) │
│ ┌──────────┐ ┌──────────┐ ┌────────┐│
│ │ Chunk 1 │ │ Chunk 2 │ │Chunk 3 ││
│ └──────────┘ └──────────┘ └────────┘│
└─────────────────────────────────────────┘
架构组件:
- Compute Layer:读写节点,处理 SQL 查询和事务
- PolarFS:分布式文件系统,提供 RDMA 访问
- Storage Layer:存储集群,管理数据块和日志
2.2 存储计算分离的优势
传统架构的问题:
// 传统架构:计算和存储耦合
class TraditionalDB {
LocalStorage storage; // 本地存储
void query() {
// 查询需要访问本地存储
// 扩展时需要同时扩展计算和存储
}
};
PolarDB 的分离架构:
// PolarDB:存储计算分离
class PolarDB {
ComputeNode compute; // 计算节点
RemoteStorage storage; // 远程存储
void query() {
// 通过 PolarFS 访问远程存储
// 计算节点和存储可以独立扩展
}
};
分离架构的优势:
- 独立扩展:计算节点和存储容量独立扩展
- 资源共享:多个计算节点共享同一份存储
- 快速恢复:计算节点故障时,新节点可以快速接入
- 成本优化:按需扩展,避免资源浪费
3. PolarFS:分布式文件系统
3.1 PolarFS 的设计
PolarFS 是 PolarDB 的核心组件,提供高性能的分布式文件访问:
设计特点:
- RDMA 网络:使用 RDMA 实现低延迟访问(< 1ms)
- 日志结构:采用日志结构存储,优化写入性能
- 多副本:数据多副本存储,保证高可用
- 一致性:强一致性保证,支持事务语义
PolarFS 架构:
┌─────────────┐
│ Compute Node│
│ ┌────────┐ │
│ │PolarFS │ │
│ │ Client │ │
│ └────┬────┘ │
└───────┼──────┘
│ RDMA
┌───────┼──────┐
│ ┌────▼────┐ │
│ │PolarFS │ │
│ │ Metadata│ │
│ └────┬────┘ │
│ │ │
│ ┌────▼────┐ │
│ │ Chunk │ │
│ │ Servers │ │
│ └─────────┘ │
└──────────────┘
3.2 RDMA 优化
PolarFS 使用 RDMA 实现高性能网络访问:
RDMA vs 传统网络:
- 传统 TCP/IP:延迟 100-500μs,CPU 开销大
- RDMA:延迟 < 10μs,零拷贝,CPU 开销小
RDMA 的优势:
- 低延迟:绕过内核,直接访问内存
- 高吞吐:支持高带宽网络(100Gbps+)
- 低 CPU 开销:减少 CPU 参与,提高效率
RDMA 工作原理:
传统 TCP/IP 路径:
Application → Kernel → Network Stack → NIC → Network
问题:多次拷贝,内核参与,延迟高
RDMA 路径:
Application → RDMA NIC → Network → Remote Memory
优势:零拷贝,绕过内核,延迟低
实现细节:
// RDMA 访问示例
class PolarFSClient {
struct RDMAConnection {
ibv_qp* queue_pair; // 队列对
ibv_mr* memory_region; // 内存区域
uint64_t remote_addr; // 远程地址
uint32_t rkey; // 远程键
};
void read_block(uint64_t block_id, void* buffer) {
// 1. 准备 RDMA Read 操作
struct ibv_sge sge;
sge.addr = (uint64_t)buffer;
sge.length = BLOCK_SIZE;
sge.lkey = local_mr->lkey;
struct ibv_send_wr wr;
wr.sg_list = &sge;
wr.num_sge = 1;
wr.opcode = IBV_WR_RDMA_READ;
wr.send_flags = IBV_SEND_SIGNALED;
wr.wr.rdma.remote_addr = remote_addr + block_id * BLOCK_SIZE;
wr.wr.rdma.rkey = remote_rkey;
// 2. 提交到 RDMA 队列
struct ibv_send_wr* bad_wr;
ibv_post_send(queue_pair, &wr, &bad_wr);
// 3. 等待完成(轮询或事件驱动)
wait_for_completion();
// 零拷贝,数据直接写入 buffer
}
};
RDMA 性能分析:
延迟对比:
- TCP/IP:100-500μs(内核处理 + 网络延迟)
- RDMA:< 10μs(硬件直接访问)
吞吐对比:
- TCP/IP:受限于 CPU 处理能力
- RDMA:接近网络带宽上限(100Gbps+)
CPU 开销对比:
- TCP/IP:每个数据包需要 CPU 处理
- RDMA:CPU 只参与设置,数据传输由硬件完成
RDMA 的挑战与解决方案:
- 内存管理:
- 挑战:需要预先注册内存区域
- 解决:使用内存池,预先注册常用内存
- 错误处理:
- 挑战:RDMA 错误处理复杂
- 解决:完善的错误检测和恢复机制
- 网络分区:
- 挑战:网络分区时 RDMA 连接可能中断
- 解决:自动重连和故障转移
4. 存储层设计:PolarStore
4.1 数据组织
PolarStore 采用 Chunk 作为基本存储单位:
Chunk 设计:
- 大小:64MB 或 128MB
- 多副本:每个 Chunk 有 3 个副本
- 分布:Chunk 分布在不同的存储节点上
数据布局:
Chunk 1 (Primary) Chunk 2 (Primary) Chunk 3 (Primary)
│ │ │
├── Replica 1 ├── Replica 1 ├── Replica 1
├── Replica 2 ├── Replica 2 ├── Replica 2
└── Replica 3 └── Replica 3 └── Replica 3
4.2 日志结构存储
PolarStore 采用日志结构存储(Log-Structured Storage):
设计原理:
- 追加写:所有写入都是追加操作
- 顺序写入:提高写入性能
- 后台合并:定期合并和压缩数据
日志结构 vs 就地更新:
就地更新(传统方式):
Block 1: [Data A] → Update → [Data A']
问题:随机写入,性能差
日志结构(PolarStore):
Log: [Data A] → Append → [Data A'] [Data A]
优势:顺序写入,性能好
后台:定期合并,回收空间
4.3 一致性保证
PolarStore 保证强一致性:
多副本一致性:
- Primary-Replica:主副本负责写入,副本同步
- Quorum 写入:写入需要多数副本确认
- 读取一致性:读取时检查副本一致性
一致性协议:
Write Request:
1. Primary 接收写入请求
2. Primary 写入本地日志
3. Primary 同步到 Replicas(Quorum)
4. 等待多数确认
5. 返回成功
Read Request:
1. 从 Primary 或 Replica 读取
2. 检查数据版本
3. 返回最新数据
一致性保证的实现细节:
写入流程详细分析:
// PolarStore 写入流程伪代码
class PolarStore {
struct WriteRequest {
uint64_t chunk_id;
uint64_t offset;
void* data;
size_t size;
uint64_t version; // 版本号
};
Status Write(const WriteRequest& req) {
// 1. 选择 Primary Chunk
Chunk* primary = get_primary_chunk(req.chunk_id);
// 2. 写入 Primary 本地日志
LogEntry entry;
entry.version = req.version;
entry.data = req.data;
entry.size = req.size;
primary->append_log(entry);
// 3. 同步到 Replicas(Quorum)
vector<Chunk*> replicas = get_replicas(req.chunk_id);
int quorum = (replicas.size() + 1) / 2 + 1; // 多数派
int success_count = 1; // Primary 已写入
for (auto* replica : replicas) {
if (replica->replicate(entry)) {
success_count++;
if (success_count >= quorum) {
break; // 达到 Quorum,可以返回
}
}
}
// 4. 检查是否达到 Quorum
if (success_count < quorum) {
return Status::QuorumNotReached();
}
// 5. 提交到存储
primary->commit(entry.version);
// 6. 异步通知其他 Replicas 提交
async_commit_replicas(replicas, entry.version);
return Status::OK();
}
};
读取一致性保证:
// 读取一致性检查
class PolarStore {
Status Read(uint64_t chunk_id, uint64_t offset, void* buffer, size_t size) {
// 1. 选择读取源(Primary 或 Replica)
Chunk* source = select_read_source(chunk_id);
// 2. 读取数据
ReadResult result = source->read(offset, size);
// 3. 检查数据版本一致性
uint64_t expected_version = get_latest_version(chunk_id);
if (result.version < expected_version) {
// 数据过期,从 Primary 重新读取
source = get_primary_chunk(chunk_id);
result = source->read(offset, size);
}
// 4. 返回数据
memcpy(buffer, result.data, size);
return Status::OK();
}
};
一致性模型分析:
强一致性保证:
- 写入一致性:
- 所有写入必须达到 Quorum 才返回成功
- 保证多数副本已持久化数据
- 即使部分副本故障,数据也不会丢失
- 读取一致性:
- 读取时检查数据版本
- 如果数据过期,从 Primary 重新读取
- 保证读取到最新数据
- 版本管理:
- 每个写入操作都有唯一版本号
- 版本号单调递增
- 通过版本号判断数据新旧
一致性 vs 性能权衡:
- 强一致性:保证数据一致,但延迟较高(需要 Quorum)
- 最终一致性:延迟低,但可能读到旧数据
- PolarDB 选择:强一致性,因为数据库需要 ACID 保证
5. 计算层设计
5.1 读写节点
PolarDB 的计算层包含多个节点:
节点类型:
- Primary Node:主节点,处理所有写入和部分读取
- Read Replicas:只读副本,处理读请求
节点架构:
Primary Node:
┌─────────────────┐
│ SQL Parser │
│ Query Optimizer│
│ Transaction │
│ Buffer Pool │
│ PolarFS Client │
└─────────────────┘
Read Replica:
┌─────────────────┐
│ SQL Parser │
│ Query Optimizer│
│ Read Only │
│ Buffer Pool │
│ PolarFS Client │
└─────────────────┘
5.2 事务处理
PolarDB 支持完整的 ACID 事务:
事务流程:
1. Begin Transaction
└─> 分配事务 ID
2. Execute Operations
└─> 写入 Buffer Pool
└─> 记录 Redo Log
3. Commit
└─> 刷新 Redo Log 到 PolarFS
└─> 等待存储确认
└─> 返回成功
4. Rollback (if needed)
└─> 撤销 Buffer Pool 中的修改
Redo Log 处理:
- 本地 Buffer:Redo Log 先写入本地 Buffer
- 批量刷新:定期批量刷新到 PolarFS
- 同步保证:Commit 时强制刷新,保证持久性
5.3 Buffer Pool 管理
PolarDB 的 Buffer Pool 管理:
设计特点:
- 本地缓存:缓存热点数据,减少远程访问
- 预取策略:预测性预取,提高命中率
- 淘汰策略:LRU 或其他策略管理缓存
Buffer Pool 架构:
Buffer Pool:
┌─────────────┐
│ Hot Pages │ ← 频繁访问
├─────────────┤
│ Warm Pages │ ← 偶尔访问
├─────────────┤
│ Cold Pages │ ← 很少访问
└─────────────┘
│
▼
PolarFS
6. 高可用设计
6.1 故障检测与恢复
PolarDB 的高可用机制:
故障检测:
- 心跳机制:计算节点和存储节点定期发送心跳
- 超时检测:超时未收到心跳,判定为故障
- 快速切换:检测到故障后,快速切换到备用节点
故障恢复流程:
1. 故障检测
└─> 心跳超时
└─> 判定 Primary 故障
2. 选举新 Primary
└─> 从 Read Replicas 中选择
└─> 检查数据一致性
└─> 提升为新 Primary
3. 存储层切换
└─> 更新元数据
└─> 重新路由请求
4. 恢复完成
└─> RTO < 30s
6.2 数据一致性保证
故障恢复时的数据一致性:
一致性检查:
- LSN 检查:检查日志序列号(Log Sequence Number)
- 数据校验:校验数据完整性
- 回放日志:回放未提交的日志
恢复策略:
故障场景 1:Primary 故障
└─> Read Replica 提升为 Primary
└─> 检查 LSN,确保数据最新
└─> 继续服务
故障场景 2:存储节点故障
└─> 使用其他副本
└─> 后台重建故障副本
└─> 保证多副本一致性
7. 性能优化
7.1 写入优化
PolarDB 的写入优化策略:
批量写入:
- 日志合并:合并多个小事务的日志
- 批量刷新:批量刷新到 PolarFS
- 异步提交:异步提交,提高吞吐
写入流程优化:
传统方式:
Transaction 1 → Flush → Storage
Transaction 2 → Flush → Storage
Transaction 3 → Flush → Storage
问题:每次都要刷新,性能差
优化方式:
Transaction 1 ┐
Transaction 2 ├─> Batch Flush → Storage
Transaction 3 ┘
优势:批量刷新,提高吞吐
7.2 读取优化
PolarDB 的读取优化策略:
缓存策略:
- 多级缓存:本地 Buffer Pool + 存储层缓存
- 预取策略:预测性预取,减少延迟
- 并行读取:并行读取多个 Chunk
读取优化:
读取请求:
1. 检查本地 Buffer Pool
└─> 命中:直接返回
2. 未命中:从 PolarFS 读取
└─> 并行读取多个 Chunk
└─> 预取相邻数据
└─> 更新 Buffer Pool
3. 返回数据
7.3 网络优化
PolarDB 的网络优化:
RDMA 优化:
- 零拷贝:直接内存访问,减少拷贝
- 批量操作:批量 RDMA 操作,提高效率
- 连接池:复用 RDMA 连接,减少开销
8. 弹性扩展
8.1 计算节点扩展
PolarDB 支持计算节点的弹性扩展:
扩展流程:
1. 创建新的计算节点
└─> 启动 PolarDB 实例
└─> 连接到 PolarFS
2. 同步元数据
└─> 同步 Schema
└─> 同步事务状态
3. 加入集群
└─> 注册为 Read Replica
└─> 开始服务读请求
4. 扩展完成
└─> 新节点可以处理请求
8.2 存储容量扩展
PolarDB 支持存储容量的弹性扩展:
扩展机制:
- 动态添加 Chunk:添加新的 Chunk 到存储集群
- 数据重分布:后台重分布数据,平衡负载
- 在线扩展:扩展过程中不影响服务
扩展流程:
1. 添加存储节点
└─> 新节点加入集群
2. 创建新 Chunk
└─> 在新节点上创建 Chunk
└─> 设置多副本
3. 数据迁移(可选)
└─> 后台迁移数据
└─> 平衡负载
4. 扩展完成
└─> 存储容量增加
9. 兼容性与生态
9.1 MySQL/PostgreSQL 兼容
PolarDB 兼容 MySQL 和 PostgreSQL:
兼容性保证:
- 协议兼容:支持 MySQL/PostgreSQL 协议
- SQL 兼容:支持标准 SQL 和扩展语法
- 工具兼容:兼容现有数据库工具
迁移优势:
- 零代码修改:现有应用无需修改代码
- 平滑迁移:支持在线迁移
- 工具支持:兼容现有数据库工具链
9.2 云原生特性
PolarDB 的云原生特性:
- 按需付费:根据实际使用量付费
- 自动运维:自动备份、监控、告警
- 多租户:支持多租户隔离
- API 集成:提供丰富的 API 接口
10. 工程实践与经验
10.1 性能调优
PolarDB 的性能调优经验:
关键参数:
- Buffer Pool 大小:根据内存容量调整
- 并发连接数:根据负载调整
- 网络带宽:确保足够的网络带宽
调优建议:
- Buffer Pool:设置足够大的 Buffer Pool,提高缓存命中率
- 网络:使用高速网络(RDMA),减少延迟
- 并发:合理设置并发连接数,避免资源竞争
10.2 故障处理
PolarDB 的故障处理经验:
常见故障:
- 网络分区:使用 Quorum 机制保证一致性
- 节点故障:快速切换,保证服务可用
- 数据损坏:使用多副本恢复数据
故障预防:
- 监控告警:实时监控系统状态
- 定期演练:定期进行故障演练
- 自动化恢复:自动化故障恢复流程
11. PolarDB 的影响与意义
11.1 对云数据库的影响
PolarDB 的设计影响了云数据库的发展:
设计模式:
- 存储计算分离:成为云数据库的标准架构
- RDMA 网络:高性能网络在数据库中的应用
- 日志结构存储:优化写入性能
行业影响:
- AWS Aurora:类似的存储计算分离架构
- Azure Database:采用类似的设计理念
- 其他云数据库:参考 PolarDB 的设计
11.2 技术贡献
PolarDB 的技术贡献:
- 存储计算分离架构:证明了在云环境下的可行性
- RDMA 优化:展示了 RDMA 在数据库中的应用
- 高可用设计:实现了快速故障恢复
- 弹性扩展:实现了计算和存储的独立扩展
12. 性能基准测试
12.1 性能指标
PolarDB 的性能指标:
延迟指标:
- 写入延迟:P99 < 1ms(本地 Buffer Pool)
- 读取延迟:P99 < 1ms(缓存命中)
- 网络延迟:RDMA < 10μs
吞吐指标:
- 写入吞吐:单节点 100K+ TPS
- 读取吞吐:单节点 500K+ QPS
- 网络吞吐:100Gbps+
可用性指标:
- RTO:< 30s(故障恢复时间)
- RPO:< 1s(数据恢复点)
- 可用性:99.99%+
12.2 性能对比
PolarDB vs 传统数据库:
| 指标 | 传统数据库 | PolarDB |
|---|---|---|
| 扩展性 | 计算存储耦合 | 计算存储分离 |
| 写入延迟 | 1-5ms | < 1ms |
| 读取延迟 | 0.5-2ms | < 1ms |
| 故障恢复 | 分钟级 | 秒级 |
| 成本 | 固定成本 | 按需付费 |
12.3 性能优化实践
写入性能优化:
- 批量写入:
- 合并多个小事务
- 减少网络往返
- 提高吞吐
- 异步刷新:
- 日志异步刷新到存储
- 减少写入延迟
- 提高响应速度
- 本地缓存:
- Buffer Pool 缓存热点数据
- 减少远程访问
- 提高命中率
读取性能优化:
- 多级缓存:
- 本地 Buffer Pool
- 存储层缓存
- 提高缓存命中率
- 预取策略:
- 预测性预取
- 减少延迟
- 提高吞吐
- 并行读取:
- 并行读取多个 Chunk
- 充分利用网络带宽
- 提高读取速度
13. 工程实践与经验
13.1 部署实践
PolarDB 的部署实践:
计算节点部署:
- 节点配置:
- CPU:16+ 核心
- 内存:64GB+
- 网络:25Gbps+(支持 RDMA)
- 节点数量:
- Primary:1 个
- Read Replicas:2-5 个(根据读负载)
- 负载均衡:
- 读请求分发到多个 Replicas
- 写请求只到 Primary
- 动态调整负载
存储节点部署:
- 节点配置:
- 存储:10TB+ SSD
- 网络:25Gbps+(支持 RDMA)
- 内存:32GB+(用于缓存)
- 副本配置:
- 每个 Chunk 3 个副本
- 副本分布在不同机架
- 保证容错能力
13.2 监控与运维
PolarDB 的监控指标:
关键指标:
- 性能指标:
- QPS/TPS
- 延迟分布(P50/P99/P999)
- 吞吐量
- 资源指标:
- CPU 使用率
- 内存使用率
- 网络带宽
- 磁盘 I/O
- 可用性指标:
- 故障次数
- 故障恢复时间
- 数据一致性
告警策略:
- 延迟告警:P99 延迟 > 阈值
- 错误率告警:错误率 > 阈值
- 资源告警:资源使用率 > 阈值
- 故障告警:节点故障或网络分区
13.3 故障处理经验
常见故障及处理:
- 计算节点故障:
- 现象:节点无响应,心跳超时
- 处理:自动切换到 Read Replica
- 时间:< 30s
- 存储节点故障:
- 现象:Chunk 副本不可用
- 处理:使用其他副本,后台重建
- 时间:立即切换,重建时间取决于数据量
- 网络分区:
- 现象:节点间无法通信
- 处理:使用 Quorum 机制保证一致性
- 时间:自动处理,无需人工干预
14. 与其他系统的对比
14.1 PolarDB vs AWS Aurora
相似点:
- 存储计算分离架构
- 多副本存储
- 快速故障恢复
差异点:
| 特性 | PolarDB | AWS Aurora |
|---|---|---|
| 网络 | RDMA | 传统网络 |
| 延迟 | < 1ms | 1-2ms |
| 存储 | 日志结构 | 日志结构 |
| 兼容性 | MySQL/PostgreSQL | MySQL/PostgreSQL |
14.2 PolarDB vs 传统数据库
优势:
- 弹性扩展
- 高可用性
- 成本优化
适用场景:
- 云原生应用
- 大规模数据
- 高可用要求
15. 小结
PolarDB 是云原生数据库的典型代表,通过存储计算分离架构实现了高性能、高可用和弹性扩展。
核心要点:
- 存储计算分离:计算节点和存储集群解耦,独立扩展
- PolarFS:基于 RDMA 的分布式文件系统,提供低延迟访问(< 10μs)
- PolarStore:日志结构存储,优化写入性能,多副本保证高可用
- 高可用:快速故障恢复,RTO < 30s,RPO < 1s
- 弹性扩展:计算和存储独立扩展,按需付费
- 兼容性:兼容 MySQL/PostgreSQL,便于迁移
- 性能:低延迟(< 1ms),高吞吐(100K+ TPS)
技术贡献:
- 存储计算分离架构:证明了在云环境下的可行性
- RDMA 优化:展示了 RDMA 在数据库中的应用,实现 < 10μs 网络延迟
- 高可用设计:实现了快速故障恢复,RTO < 30s
- 弹性扩展:实现了计算和存储的独立扩展
PolarDB 的设计展示了云原生数据库的发展方向,为后续的云数据库系统提供了重要参考。其存储计算分离架构、RDMA 网络优化、日志结构存储等技术已成为云数据库的标准设计模式。