第7章:读取流程全解析
读取的三个阶段
Paimon的读取分为三个阶段:
- 扫描计划(Scan):确定需要读取哪些文件
- 分片生成(Split):将文件分割成可并行处理的任务
- 数据读取(Read):从分片读取实际数据
深入每个阶段
7.1 FileStoreScan扫描接口
扫描的目的
从最新的Snapshot开始,找出所有需要读取的数据文件。
java
FileStoreScan scan = fileStore.newScan();
// 应用谓词(where条件)
scan.withFilter(predicate);
// 获得读取计划
Plan plan = scan.plan();
// Plan包含:
// - 所有需要读的文件
// - 文件的元数据(min_key, max_key等)
扫描的性能优化
1. 谓词下推(Predicate Pushdown)
sql
SELECT * FROM users WHERE age > 30;
不优化:
读取: 所有users记录
过滤: 哪些age > 30
优化后:
检查Manifest:
- file1: age min=20, max=28 → skip
- file2: age min=25, max=35 → read ✓
- file3: age min=30, max=40 → read ✓
结果: 只读3个文件中的2个
2. 分区裁剪(Partition Pruning)
sql
SELECT * FROM events WHERE dt='2024-01-15';
执行:
读分区列表 → [dt=2024-01-01, dt=2024-01-15, dt=2024-01-31]
过滤 → [dt=2024-01-15]
只读dt=2024-01-15目录下的文件
效果: 扫描文件数减少 95%
7.2 Split生成与分发
什么是Split?
Split是数据文件的可读取单位,用于分布式读取。
ini
原始文件:data-1.parquet (1GB)
↓
Split1: offset=0, length=256MB
Split2: offset=256MB, length=256MB
Split3: offset=512MB, length=256MB
Split4: offset=768MB, length=256MB
↓
4个Split可以并行读取
Split的分配策略
perl
单个并行度读取:
Task-0: 读取所有splits顺序读取
多个并行度读取:
Task-0: 读取 split-0, split-4, split-8, ...
Task-1: 读取 split-1, split-5, split-9, ...
Task-2: 读取 split-2, split-6, split-10, ...
Task-3: 读取 split-3, split-7, split-11, ...
(轮询分配splits)
7.3 SplitRead数据读取
读取的工作流程
java
SplitRead<T> read = fileStore.newRead();
for (Split split : splits) {
RecordReader reader = read.createReader(split);
RecordBatch batch;
while ((batch = reader.readBatch()) != null) {
// 处理这批记录
for (Record record : batch) {
process(record);
}
}
reader.close();
}
批量读取的好处
scss
逐记录读取(低效):
for i in 1..1000000 {
record = file.read() ← 1000000次I/O调用!
process(record)
}
批量读取(高效):
for batch in batches {
batch = file.readBatch(4096) ← 仅245次I/O调用
for record in batch {
process(record)
}
}
性能提升: 4096倍!
7.4 谓词下推与过滤优化
三层过滤
yaml
层1:Manifest级别过滤
min_key <= target_key <= max_key?
→ 快速排除大量文件
层2:文件级别统计过滤
file_stats:
age: min=25, max=35
age > 30?
→ 排除不符合的文件
层3:记录级别过滤
(age > 30)? → keep : skip
→ 最终过滤
where子句的处理
sql
WHERE user_id = 100 AND age > 30
分解为:
谓词1: user_id = 100 (主键谓词 → Manifest过滤)
谓词2: age > 30 (非主键谓词 → 记录级过滤)
执行:
1. Manifest过滤 → user_id=100的bucket
2. 读该bucket的文件
3. 对每条记录检查 age > 30
总结:读取流程的关键优化
1. 多层过滤链
scss
Manifest → 分区 → 文件统计 → 记录级
(快速) (快速) (中等) (精准)
2. 批量处理
css
减少I/O次数
减少虚拟机调用开销
提升CPU缓存命中率
3. 分布式并行
Split分配 → 并行读取 → 吞吐量线性扩展
相关代码
- FileStoreScan:
paimon-core/src/main/java/org/apache/paimon/operation/FileStoreScan.java - SplitRead:
paimon-core/src/main/java/org/apache/paimon/io/SplitRead.java - Split:
paimon-api/src/main/java/org/apache/paimon/table/source/Split.java