未记录事实核验 · 状态:现行内容
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适合用于建立Paimon表、快照、分区、Bucket及文件引用之间的基础概念关系,不是完整的可执行SQL练习或容量规划指南;本次仅作全文结构审阅,未作外部事实核验或运行验证。建表、快照查询与合并调用需结合实际Paimon版本、执行引擎、Catalog及表标识使用;Bucket数量区间、下游并发倍数、本地Join和性能描述仅作条件性参考,不能替代执行计划分析与负载测试,文中的默认文件大小也应以实际版本和配置为准。LSM层级说明针对主键表,各层文件均已描述为有序文件,末尾按包整理的类比同样说明Level 0文件内部有序、不同文件键范围可能重叠;Manifest是记录数据文件信息的元数据,不应因表组成中的归类而被理解为承载实际数据行的文件。
📚 Paimon核心概念 - 表、快照与文件布局
abstract 📋 本节概览
- 🎯 学习目标:深入理解Paimon的核心概念和数据组织方式
- 📚 前置知识:Paimon概述与特性
- ⏱️ 预计用时:20分钟
- 🔧 核心内容:表、快照、文件、分区、Bucket
- 💡 关键收获:理解Paimon的数据组织逻辑
🎯 为什么要理解核心概念
在开始使用Paimon之前,理解其核心概念至关重要。就像开车前要了解方向盘、油门、刹车的作用一样,只有理解了这些基本概念,才能高效地使用Paimon。
生活化类比:理解Paimon核心概念就像理解图书馆系统
- 表:整个图书馆
- 分区:不同楼层(按主题分类)
- Bucket:每层的不同书架(按编号分组)
- 文件:书架上的具体书籍
- 快照:图书馆某个时刻的完整目录
📊 核心概念1:表(Table)
🎯 什么是Paimon表
专业定义:Paimon表是数据的逻辑组织单位,包含Schema定义、数据文件、元数据等。每个表都有唯一的标识符和独立的配置。
表的组成部分:
| 组成部分 | 子项 |
|---|---|
| Schema(表结构) | 列定义、主键定义、分区键定义、表属性 |
| 快照(Snapshots) | Snapshot-1、Snapshot-2、Snapshot-N |
| 文件与元数据 | 数据文件承载数据行;Manifest描述数据文件;索引文件提供辅助索引 |
📋 表的类型
1. 主键表(Primary Key Table)
定义:支持按主键进行更新和删除的表。
创建示例:
CREATE TABLE user_profile (
user_id BIGINT, -- 主键字段
name STRING,
age INT,
city STRING,
update_time TIMESTAMP(3),
PRIMARY KEY (user_id) NOT ENFORCED -- 定义主键
) WITH (
'bucket' = '4', -- 设置4个bucket
'changelog-producer' = 'input' -- 生成changelog
);
特点:
- ✅ 支持UPDATE和DELETE操作
- ✅ 相同主键的数据自动合并
- ✅ 适合维度表、用户画像等场景
- ⚠️ 写入性能略低于仅追加表
2. 仅追加表(Append-Only Table)
定义:只支持INSERT操作的表,数据只能追加不能修改。
创建示例:
CREATE TABLE access_log (
user_id BIGINT,
action STRING,
url STRING,
timestamp BIGINT,
dt STRING
) PARTITIONED BY (dt) -- 按日期分区
WITH (
'bucket' = '-1' -- 仅追加表采用不分桶模式,不等同于主键表的动态分桶
);
特点:
- ✅ 只支持INSERT操作
- ✅ 仅追加写入通常避免按主键合并的开销,实际性能取决于负载与配置
- ✅ 适合日志、事件流等场景
- ⚠️ 不支持数据更新
生活化理解:
- 主键表:像银行账户,可以修改余额和信息
- 仅追加表:像交易流水,只能追加新记录
📸 核心概念2:快照(Snapshot)
🎯 什么是快照
专业定义:快照是表在某个时间点的完整状态快照,包含该时刻的所有数据文件引用。每次提交都会生成一个新的快照。
快照的作用:
1. 版本管理:记录表的历史状态
2. 时间旅行:可以查询历史数据
3. 增量读取:获取两个快照之间的变更
4. 故障恢复:可以回滚到之前的快照
📊 快照的生命周期
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
graph LR
A[数据写入] --> B[提交Commit]
B --> C[生成快照Snapshot-N]
C --> D[快照可见]
D --> E[保留期内]
E --> F[过期清理]
style C fill:#e1f5fe
style D fill:#c8e6c9
style F fill:#ffccbc
快照示例:
快照ID: 5
提交时间: 2024-01-01 10:30:00
Schema版本: 1
文件清单:
- manifest-list-snapshot-5
- 100个数据文件
- 总大小: 5GB
变更类型: APPEND (新增数据)
🔍 快照相关操作
1. 查看快照历史:
-- 查看表的所有快照
SELECT * FROM paimon_table$snapshots;
-- 输出示例:
snapshot_id | commit_time | schema_id | commit_kind
------------|---------------------|-----------|------------
1 | 2024-01-01 10:00:00 | 0 | APPEND
2 | 2024-01-01 11:00:00 | 0 | APPEND
3 | 2024-01-01 12:00:00 | 0 | COMPACT
2. 时间旅行查询:
-- 查询历史快照的数据
SELECT * FROM paimon_table /*+ OPTIONS('scan.snapshot-id'='2') */;
-- 查询指定时间点的数据
SELECT * FROM paimon_table /*+ OPTIONS('scan.timestamp-millis'='1704096000000') */;
3. 增量读取:
-- 读取从快照2到快照5之间的变更
SELECT * FROM paimon_table /*+ OPTIONS(
'incremental-between'='2,5'
) */;
生活化理解:快照就像Git的commit
- 每次提交代码生成一个commit
- 可以查看历史commit
- 可以回滚到之前的commit
- 可以比较两个commit的差异
📁 核心概念3:分区(Partition)
🎯 什么是分区
专业定义:分区是按照特定列的值将表数据划分成多个独立的子集,每个分区对应一个独立的目录。
分区的价值:
✅ 查询加速:只扫描相关分区,减少数据量
✅ 数据管理:方便按分区删除历史数据
✅ 并行处理:不同分区可以并行读写
✅ 成本优化:冷数据分区可以迁移到低成本存储
📊 分区设计
单级分区:
CREATE TABLE orders (
order_id BIGINT,
user_id BIGINT,
amount DECIMAL(10,2),
order_time TIMESTAMP(3),
dt STRING
) PARTITIONED BY (dt) -- 按日期分区
WITH ('bucket' = '4');
-- 物理存储结构:
-- /warehouse/orders/dt=2024-01-01/
-- /warehouse/orders/dt=2024-01-02/
-- /warehouse/orders/dt=2024-01-03/
多级分区:
CREATE TABLE user_behavior (
user_id BIGINT,
action STRING,
timestamp BIGINT,
dt STRING,
hour STRING
) PARTITIONED BY (dt, hour) -- 按日期和小时分区
WITH ('bucket' = '8');
-- 物理存储结构:
-- /warehouse/user_behavior/dt=2024-01-01/hour=00/
-- /warehouse/user_behavior/dt=2024-01-01/hour=01/
-- /warehouse/user_behavior/dt=2024-01-01/hour=02/
🎯 分区策略选择
按时间分区 (最常用):
适用场景:日志、事件流、时序数据
分区粒度:天、小时、月
示例:dt=2024-01-01
按业务维度分区:
适用场景:多租户系统、多地域数据
分区字段:租户ID、地域、业务线
示例:region=cn-north, tenant_id=1001
组合分区:
适用场景:大规模数据,需要细粒度控制
分区字段:时间 + 业务维度
示例:dt=2024-01-01/region=cn-north
生活化理解:分区就像文件柜的抽屉
- 每个抽屉存放特定类别的文件(按日期、部门等)
- 找文件时只需打开对应抽屉,不用翻遍整个文件柜
- 清理旧文件时可以整个抽屉移除
🗂️ 核心概念4:Bucket
🎯 什么是Bucket
专业定义:Bucket是分区内部的数据分组机制,用于并行处理和数据分布。每个Bucket对应一组数据文件。
Bucket的作用:
✅ 并行度控制:决定读写的并发度
✅ 数据分布:将数据均匀分散到多个文件
✅ 主键定位:主键表通过bucket快速定位数据
✅ Join优化:相同bucket的数据可以本地Join
📊 Bucket分配机制
Hash分桶:
CREATE TABLE user_info (
user_id BIGINT,
name STRING,
PRIMARY KEY (user_id) NOT ENFORCED
) WITH (
'bucket' = '8', -- 设置8个bucket
'bucket-key' = 'user_id' -- 按user_id哈希分桶
);
-- 数据分布示意:
-- 对bucket-key计算哈希后映射到8个bucket。
-- 不能直接以user_id % 8推断实际bucket编号。
仅追加表的不分桶模式:
CREATE TABLE access_log (
user_id BIGINT,
action STRING,
timestamp BIGINT,
dt STRING
) PARTITIONED BY (dt)
WITH (
'bucket' = '-1' -- 此仅追加表示例采用不分桶模式
);
-- 特点:
-- 此示例说明仅追加表的不分桶模式,不表示自动扩展bucket数量。
-- 主键表的动态分桶是另一种模式,不能据此认定其不适合主键表。
🎯 Bucket数量设置
设置原则:
小表(< 1GB): bucket = 1-2
中表(1-100GB): bucket = 4-8
大表(> 100GB): bucket = 16-32
主键表: 建议设置固定bucket数
仅追加表: 可以使用bucket=-1的不分桶模式,不应将其称为主键表动态分桶
影响因素:
数据量大小: 数据越大,bucket越多
写入并发: 并发越高,bucket越多
查询模式: 点查多用固定bucket,全表扫描可用动态bucket
下游消费: 下游并发度应该是bucket的整数倍
生活化理解:Bucket就像快递分拣系统
- 包裹(数据)按照地址(主键)分配到不同分拣区(bucket)
- 多个分拣区可以并行工作,提高效率
- 收件时只需要去对应分拣区找包裹
📦 核心概念5:文件布局
🎯 LSM树存储结构
Paimon主键表采用LSM(Log-Structured Merge-tree)组织按键合并的数据文件;以下层级说明针对主键表,仅追加表的文件合并不等同于主键表的LSM按键合并:
| 层级 | 状态 | 示例文件 |
|---|---|---|
| Level 0 | 主键表中新写入的有序文件,不同文件的键范围可能重叠 | file-1.parquet, file-2.parquet, file-3.parquet |
| Level 1 | 合并形成的有序文件,层级不表示“部分排序” | file-4.parquet, file-5.parquet |
| Level 2+ | 更高层级的有序文件,文件大小取决于配置与合并结果 | file-6.parquet |
📊 文件类型
1. 数据文件(Data Files):
格式: Parquet或ORC
存储: 实际的数据行
大小: 默认128MB
位置: /bucket-{id}/data-*.parquet
2. Manifest文件:
作用: 记录数据文件的元信息
内容: 文件路径、大小、行数、最小/最大值等
类型:
- manifest-list: 快照级别的文件清单
- manifest: 详细的文件元信息
3. Schema文件:
作用: 记录表的结构定义
内容: 列名、类型、主键、分区键等
版本: 支持Schema演进,每个版本独立存储
🔄 文件合并机制
为什么需要合并:
问题1: 小文件过多,影响查询性能
问题2: 主键表Level0中不同文件的键范围可能重叠,读取时可能需要合并多个文件中的记录
问题3: 删除的数据占用空间,需要清理
解决: 合并小文件以减少文件数量;主键表还会按键合并记录并生成有序文件,仅追加表的文件合并不执行相同的主键更新与删除合并语义
合并策略:
1. Full Compaction(全量合并):
-- 手动触发全量合并
CALL sys.compact('my_db.my_table');
-- 特点:
-- ✅ 在合并范围内处理删除记录;历史文件的物理清理还受快照保留与过期清理机制约束
-- ✅ 优化查询性能
-- ⚠️ 耗时较长,消耗资源
2. Incremental Compaction(增量合并):
触发条件: 文件数量或大小达到阈值
合并范围: 部分文件
执行时机: 后台自动执行
生活化理解:文件合并就像整理房间
- Level0文件:每一包内部已按键整理,但不同包的键范围可能重叠;并非文件内部无序
- 合并过程:定期整理,把同类物品放在一起
- Level2+文件:整理后的储物柜,物品有序,便于查找
🔗 概念之间的关系
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
graph TB
A[Paimon表] --> B[分区Partition]
B --> C[Bucket]
C --> D[数据文件]
A --> E[快照Snapshot]
E --> F[Manifest清单]
F --> D
style A fill:#e1f5fe
style E fill:#fff3e0
style D fill:#c8e6c9
关系说明:
表 → 分区 → Bucket → 文件
↓
快照 → Manifest → 文件引用
完整示例:
表名: user_behavior,分区: dt=2024-01-01,Bucket: 8个
| 层级 | 内容 |
|---|---|
| 快照5 | manifest-list-5 |
| manifest-partition-2024-01-01 | 各 bucket 的文件引用 |
| bucket-0 | file-1, file-2 |
| bucket-1 | file-3, file-4 |
| ... | ... |
📋 本节要点回顾
✅ 核心概念掌握检查
- [ ] 理解主键表和仅追加表的区别
- [ ] 掌握快照的概念和作用
- [ ] 理解分区的价值和设计原则
- [ ] 了解Bucket的分配机制
- [ ] 熟悉LSM树存储结构
🎯 实践能力检查
- [ ] 能够根据业务选择合适的表类型
- [ ] 会设计合理的分区策略
- [ ] 能够计算合适的bucket数量
- [ ] 理解文件合并的时机和策略
🔗 学习路径连接
已完成:✅ 01-Paimon概述 → ✅ 02-核心概念 下一步:Paimon架构原理 - 深入理解LSM树和读写流程
tip 学习建议
这些核心概念是理解Paimon的基础,建议结合实际案例加深理解。下一节我们将深入探讨Paimon的架构原理和数据流转过程。
创建时间:2024-12-04 最后更新:2026-10-09 学习时长:20分钟 难度等级:⭐ (初级)
#Paimon #核心概念 #快照 #分区 #Bucket #LSM树