Iceberg 分区
# Iceberg 中分区的特点
- Iceberg 选中数据行中的某个字段作为分区,分区信息是记录至文件级别的。而 Hive 中选取的分区字段是以目录的形式出现的,最终的物理数据文件中(如 parquet 文件)是没有存储分区字段,也就是说 Hive 的分区是在目录级别的。
- Iceberg 提供的隐藏分区可以根据查询条件自动过滤不需要的分区数据,使用者不需要关心表的分区字段,也不需要根据分区字段额外指定查询条件。
- Iceberg 查询不再依赖于表的物理布局。随着物理和逻辑的分离,Iceberg 表可以随着数据量的变化而随着时间的推移演变分区方案,也就是说一个表中可以同时允许多个分区策略。
- Iceberg 中针对每个数据文件都有记录了它的元数据信息,以及相关的统计信息。如:最大值、最小值、总行数、为 null 的行数等。
# 隐藏分区
隐藏分区,顾名思义就是将分区信息隐藏起来。开发人员只需要建表时指定分区策略,Iceberg 会根据分区策略跟踪该表字段与物理存储的数据文件之间的逻辑关系。使用者在插入和读取的时候不需要关心表的分区布局,不需要像 Hive 那样显示地指定分区字段,只需要关心业务逻辑即可。Iceberg 会在插入数据的时候根据分区策略跟踪新数据的分区信息,并将其记录在元数据中;查询的时候也会根据自动过滤掉不需要扫描的文件。
在 Hive 中使用分区,需要明确指定分区字段,而这些分区字段最终都会以目录的形式出现在文件系统中,其数据文件中不会出现。假设创建以下 Hive 表并插入一条数据:
create table user_log_1 (
imei string,
uuid string
)
partitioned by (udt string comment 'yyyy-MM-dd');
insert into user_log_1 partition (udt='2022-01-01') values ('xxxxxxxxxxxxx', 'yyyyyyyyyyyyy');
1
2
3
4
5
6
7
2
3
4
5
6
7
该表中的数据在文件系统中的组织形式是这样的:
user_log_1/
└── udt=2022-01-01
├── _SUCCESS
└── part-00000-1f6ad6a0-3536-42f1-b354-d4ccb34a7939-c000
1
2
3
4
2
3
4
如果建表语句中指定 year、month、day 三个字段作为分区字段,并插入一条数据:
create table user_log_2 (
imei string,
uuid string
)
partitioned by (year int, month int, day int);
insert into user_log_2 partition (year=2022, month=1, day=1) values ('xxxxxxxxxxxxx', 'yyyyyyyyyyyyy');
1
2
3
4
5
6
7
2
3
4
5
6
7
该表中的数据在文件系统中的组织形式是这样的:
user_log_2/
└── year=2022
└── month=1
└── day=1
├── _SUCCESS
└── part-00000-21bbea3f-350d-4b3f-a820-2d846184d9fc-c000
1
2
3
4
5
6
2
3
4
5
6
但是如果使用 Iceberg 来建表并插入一条数据:
create table hadoop_catalog.iceberg_db.user_log_iceberg (
imei string,
uuid string,
udt timestamp
)
using iceberg
partitioned by (days(udt));
insert into hadoop_catalog.iceberg_db.user_log_iceberg values ('xxxxxxxxxxxxx', 'yyyyyyyyyyyyy', cast(1640966400 as timestamp));
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
查看表 user_log_iceberg 在文件系统中的存储结构:
user_log_iceberg/
├── data
│ └── udt_day=2021-12-31
│ └── 00000-0-67ab9286-794b-456d-a1d3-9c797a2b4b03-00001.parquet
└── metadata
├── f9d66153-6745-4103-ad24-334fc62f0d1e-m0.avro
├── snap-6744647507914918603-1-f9d66153-6745-4103-ad24-334fc62f0d1e.avro
├── v1.metadata.json
├── v2.metadata.json
└── version-hint.text
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
其中data
目录存储的是数据文件,metadata
目录存储的是表的元数据信息,后续会详细介绍。
经过前面几个操作中,我们发现有以下几个不同:
- 在创建表 Iceberg 表的时候并没有像创建 Hive 表那样将分区字段放在
partitioned by
子句中,而是和其它字段一样放在表名后面的括号中,只是在partitioned by (days(udt))
中指定了我们分区策略和udt
字段之间的逻辑关系。 - Iceberg 表中插入数据的 SQL 相比 Hive 的插入 SQL 而言少了
partition (year=2022, month=1, day=1)
部分,也就是说没有额外去指定分区信息。 - 物理存储方面,Hive 的两个表数据文件
part-00000-1f6ad6a0-3536-42f1-b354-d4ccb34a7939-c000
和part-00000-21bbea3f-350d-4b3f-a820-2d846184d9fc-c000
中没有udt
、year
、month
、day
几个分区字段的信息,分区字段体现在了目录中,但是在 Iceberg 表中,所有的字段数据都在00000-0-67ab9286-794b-456d-a1d3-9c797a2b4b03-00001.parquet
文件中,且自动转换了分区策略和字段时间的关系。 - Hive 表中如果要使用分区键进行查询,开发人员必须先查看该表的建表语句以了解该表的物理存储方式,但使用 Iceberg 表时不需要关心物理存储。
- 假设在使用表
user_log_1
的时候将查询条件中的条件指定为udt = '20220101'
(日期格式为 yyyyMMdd),查询不会报错,但是可能会返回一个错误的结果。
在user_log_iceberg
表中的分区策略中使用到了days
函数,将timestamp
中的day
作为分区属性,实际上除了将timestamp
转换为day
,还支持其它转换:
转换方式 | 描述 | 原类型 | 转换后的类型 |
---|---|---|---|
identity | 原值,不进行修改 | 所有类型型 | 转换后的类型和原类型对应 |
bucket[N] | 采用哈市算法分桶 | int, long, decimal, date, time, timestamp, timestamptz, string, uuid, fixed, binary | int |
truncate[W] | 截取固定长度进行分区 | int, long, decimal, string | 转换后的类型和原类型对应 |
year | 提取 date 或 timestamp 中的 year 信息 | date, timestamp, timestampz | int |
month | 提取 date 或 timestamp 中的 month 信息 | date, timestamp, timestampz | int |
day | 提取 date 或 timestamp 中的 day 信息 | date, timestamp, timestampz | int |
hour | 提取 timestamp 中的 hour 信息 | timestamp, timestampz | int |
void | 无论元数据等于什么,输出为 null | 所有类型 | 原型类或 int |
提示
- 所有的转换都允许输入为 null,输出为 null;
- 以上转换方式列对应的值只代表具体的转换方式,并不是在 SQL 中使用的转换函数,如转换方式
day
在 Spark SQL 中对应的转换函数为days
。
上次更新: 2023/11/01, 03:11:44