第4章:FileStore存储引擎核心
FileStore是什么?
FileStore 是Paimon的存储引擎实现,负责将Table层的抽象操作转化为具体的文件操作。
关键关系:
scss
Table (高级API)
↓
FileStore (存储引擎)
↓
文件系统 (物理存储)
FileStore的两个实现
4.1 FileStore接口与抽象
核心方法
java
FileStore<T> {
// 扫描操作
FileStoreScan newScan()
// 读写操作
SplitRead<T> newRead()
FileStoreWrite<T> newWrite(user)
FileStoreCommit newCommit(user)
// 管理
SnapshotManager snapshotManager()
ChangelogManager changelogManager()
// 配置
CoreOptions options()
BucketMode bucketMode()
}
两种实现
| 实现 | 适用表类型 | 核心特性 | 内部结构 |
|---|---|---|---|
| KeyValueFileStore | 主键表 | 支持更新删除 | LSM树 |
| AppendOnlyFileStore | 追加表 | 高吞吐追加 | 简单序列化存储 |
4.2 AppendOnlyFileStore实现
什么(What):追加表存储如何工作?
追加表只支持INSERT操作,实现非常简洁:
kotlin
写入流程:
Record1 → 写入内存缓冲
Record2 → 写入内存缓冲
Record3 → 写入内存缓冲
...
(缓冲满或提交时)
→ 生成data-file-1.parquet
→ 生成Manifest条目
→ 提交Snapshot
为什么(Why):为什么追加表这么简单?
- 无去重:每条记录都是新增,不需要处理重复
- 无版本管理:不需要合并多个版本的同一主键
- 高性能:直接顺序写入,零开销
怎样(How):实现细节
核心类:
java
AppendOnlyFileStore {
newWrite() {
return AppendFileStoreWrite {
write(record) {
// 直接写入内存缓冲
writeBuffer.append(record);
// 缓冲满或达到时间限制时
if (writeBuffer.isFull()) {
flushBuffer(); // 生成文件
}
}
prepareCommit() {
flushBuffer(); // 最后刷新
return committables; // 返回待提交的文件列表
}
}
}
}
性能特点:
scss
吞吐量:高(直接追加)
延迟:低(避免排序和去重)
存储空间:需要定期清理过期数据
4.3 KeyValueFileStore实现
什么(What):主键表存储如何工作?
主键表需要处理主键重复和版本管理:
ini
写入流程:
(user_id=1, name='Alice', age=25) ← 首次出现
→ 写入LSM树
(user_id=1, name='Alice', age=26) ← 同一主键
→ LSM树自动合并(新版本覆盖旧版本)
(user_id=2, name='Bob', age=30) ← 新主键
→ 写入LSM树
查询结果:
user_id=1: age=26 (最新版本)
user_id=2: age=30
为什么(Why):为什么需要LSM树?
主键表需要:
- 高效的写入:支持大量小批量的UPDATE/DELETE
- 有序扫描:按主键有序存储便于查询
- 自动去重:同一主键只保留最新版本
LSM树的优势:
- 顺序写:所有数据先写MemTable(内存),再合并到磁盘
- 分层存储:新数据在高层,旧数据在低层
- 异步压缩:后台自动合并文件,不阻塞写入
怎样(How):LSM树的简化模型
scss
写入阶段:
├─ MemTable (L0) ← 新数据先进入MemTable
│ └─ sorted by key
├─ SSTable1 (L1) ← 定期从L0刷入L1
├─ SSTable2 (L1)
├─ SSTable3 (L2) ← 较旧的数据
└─ SSTable4 (L3) ← 最旧的数据
读取流程:
查询 key=1001
↓ 先查MemTable(最新)
↓ 再查L1的SSTable
↓ 再查L2的SSTable
→ 找到数据,返回最新版本
相比B树的优势:
| 操作 | LSM树 | B树 |
|---|---|---|
| 随机写 | 快(转化为顺序写) | 快但有随机I/O |
| 顺序读 | 很快 | 很快 |
| 点查询 | 中等(需查多层) | 快 |
| 范围查询 | 快(有序存储) | 快 |
| 压缩开销 | 有(后台进行) | 无 |
4.4 BucketMode分桶策略
三种分桶模式回顾
java
// 模式1:固定桶
options("bucket", "4")
→ hash(primary_key) % 4 决定桶号
// 模式2:动态桶
options("bucket", "-1")
→ 初始4个桶,自动分裂增加
// 模式3:无感知桶(仅追加表)
options("bucket", "-1")
→ 追加表专用,不需要主键分布
分桶对存储引擎的影响
单个桶的LSM树:
scss
FileStore(bucket-0)
├─ MemTable
├─ Level-0
│ ├─ SSTable-0-1
│ └─ SSTable-0-2
├─ Level-1
│ └─ SSTable-0-3
└─ Level-2
└─ SSTable-0-4
FileStore(bucket-1)
├─ MemTable
├─ Level-0
│ ├─ SSTable-1-1
│ └─ SSTable-1-2
└─ ...
并行写入:
diff
写线程1 → bucket-0 → LSM树0 (独立)
写线程2 → bucket-1 → LSM树1 (独立)
写线程3 → bucket-2 → LSM树2 (独立)
优点:
- 三个线程互不竞争
- 可以真正并行写入
- 吞吐量 ≈ 线程数 × 单线程吞吐量
总结:FileStore的核心设计
灵活的抽象层
sql
追加表 ─────────────── 简单高效
FileStore接口
主键表 ─────────────── 功能丰富(支持UPDATE/DELETE)
两种表的选择指南
sql
选择追加表如果:
✓ 仅追加日志/事件
✓ 无须去重
✓ 追求最大吞吐量
选择主键表如果:
✓ 有主键、支持更新
✓ 需要去重
✓ 支持DELETE操作
相关代码
- FileStore接口:
paimon-core/src/main/java/org/apache/paimon/FileStore.java - KeyValueFileStore:
paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java - AppendOnlyFileStore:
paimon-core/src/main/java/org/apache/paimon/AppendOnlyFileStore.java