-- 先准备一张Hudi MOR表
CREATE TABLE hudi_test_dijie(
id bigint,
dt string,
ts TIMESTAMP(3),
PRIMARY KEY(id) NOT ENFORCED
)
PARTITIONED BY (`dt`)
WITH (
'connector'= 'hudi',
'path'= 'hdfs:///hudi/hudi_test_dijie',
'table.type'= 'MERGE_ON_READ',
'read.streaming.enabled'= 'true',
-- 'read.streaming.start-commit'= '20210401134557',
'read.streaming.check-interval'= '4'
);
-- 再准备一张有数据的Kafka表
CREATE TABLE kafka_test (
id bigint,
dt string,
ts as localtimestamp
) WITH (
'connector' = 'kafka',
'topic' = 'a_dijie_test',
'properties.bootstrap.servers' = 'localhost:9092',
'properties.group.id' = 'testGroup',
'scan.startup.mode' = 'earliest-offset',
'format' = 'json'
);
-- 我的任务开了checkpoint,并且周期是一秒;写入Hudi请开启checkpoint
-- 我分别在15点、18点、19点执行了这段SQL,并且在19点执行的时候,使用了MiniBatch模式
-- set table.exec.mini-batch.enabled=true;
-- set table.exec.mini-batch.allow-latency=5s;
-- set table.exec.mini-batch.size=5000;
set table.dynamic-table-options.enabled=true;
insert into hudi_test_dijie select * from kafka_test /*+ options ('properties.group.id' = 'test_d') */;
# 使用Hdfs 命令下载目录下所有数据
# 注意这里的目录用的是建Hudi表时指定的`path`,而非{DAWEHOUSE}/databases/table
hdfs dfs -get /hudi/hudi_test_dijie
以下适用于Mac电脑
# 用于查看avro文件
brew install avro-tools
# 用于查看parquet文件
brew install parquet-tools
非Mac电脑可以直接下载对应的Jar包,下载路径
先看看表下面都有什么文件
目录下有两个文件夹,分别是.hoodie
和2021-05-01
首先.hoodie
目录对应着表的元数据信息,包括表的版本管理(Timeline)、归档目录(存放过时的instant也就是版本)、回滚记录(.rollback
)文件等等;因为我们也没有手动触发Hudi自身的savepoint
操作,没有savepoint
文件,一会儿会简单说一下这个文件的产生和作用
2021-05-01
对应着分区路径,因为我Kafka中dt
字段的数据都是指定为2021-05-01
,所以只有该天的分区,分区里面存放着Base File(*.parquet)和Log File(*.log.*)以及分区信息
接下来,我们分成表元数据
和表数据
两部分来进行分析
之前介绍Hudi的时候和大家说过,Hudi有个Timeline
的概念,它包含了在不同Instant
时间所有对表的操作,一个Instant
由以下3种东西构成
Hudi保证在时间轴上执行的操作的原子性和基于Instant时间的时间轴一致性。
执行的关键操作包括
COMMITS :一次提交表示将一组记录原子写入到表中。
CLEANS :删除表中不再需要的旧文件版本的后台活动。
DELTA_COMMIT :增量提交是指将一批记录原子写入到MOR表中,其中部分或全部数据都将只写入到日志中。
COMPACTION :合并日志、小文件的后台操作。合并其实是Timeline
上的特殊提交。
ROLLBACK :表示COMMITS
/DELTA_COMMIT
不成功并且进行回滚,删除在写入过程中产生的所有不完整的文件。
SAVEPOINT :由hudi-CLI
手动触发,对Instant
进行备份,当后续出现提交错误时,便可ROLLBACK
至指定SAVEPOINT
任何给定的Instant都可以处于以下状态之一
REQUESTED :表示已调度但尚未开始的操作。
INFLIGHT :表示当前正在执行该操作。
COMPLETED :表示在Timeline
上完成了该操作。
以上是我对官网首页文档的翻译,认真看完了这些,再来看.hoodie
下的文件会更清晰
我们按照时间顺序,依次打开有文件内容的文件
首先我们看一下./20210501150108.deltacommit
文件中的内容
{
"partitionToWriteStats": {
"2021-05-01": [
{
"fileId": "817c5634-926f-41e9-a4b1-5c52ae4ce81b",
"path": "2021-05-01/.817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501150108.log.1_1-4-0",
"prevCommit": "20210501150108",
"numWrites": 150159,
"numDeletes": 0,
"numUpdateWrites": 72544,
"numInserts": 77615,
"totalWriteBytes": 18470596,
"totalWriteErrors": 0,
"tempPath": null,
"partitionPath": "2021-05-01",
"totalLogRecords": 0,
"totalLogFilesCompacted": 0,
"totalLogSizeCompacted": 0,
"totalUpdatedRecordsCompacted": 0,
"totalLogBlocks": 0,
"totalCorruptLogBlock": 0,
"totalRollbackBlocks": 0,
"fileSizeInBytes": 18470596,
"minEventTime": null,
"maxEventTime": null,
"logVersion": 1,
"logOffset": 0,
"baseFile": "",
"logFiles": [
".817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501150108.log.1_1-4-0"
]
}
]
},
"compacted": false,
"extraMetadata": {
"schema": "{\"type\":\"record\",\"name\":\"record\",\"fields\":[{\"name\":\"id\",\"type\":\"long\"},{\"name\":\"dt\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"ts\",\"type\":[\"null\",{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}],\"default\":null}]}"
},
"operationType": null,
"totalScanTime": 0,
"totalCompactedRecordsUpdated": 0,
"totalLogFilesCompacted": 0,
"totalLogFilesSize": 0,
"minAndMaxEventTime": {
"Optional.empty": {
"val": null,
"present": false
}
},
"totalCreateTime": 0,
"totalUpsertTime": 2616,
"totalRecordsDeleted": 0,
"totalLogRecordsCompacted": 0,
"writePartitionPaths": [
"2021-05-01"
],
"fileIdAndRelativePaths": {
"817c5634-926f-41e9-a4b1-5c52ae4ce81b": "2021-05-01/.817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501150108.log.1_1-4-0"
}
}
partitionToWriteStats
2021-05-01
这一个对象,实际上,如果写入了多个分区,那么这里会有多个对象,每个对象的Key都是分区名fileId
:在每个分区内,文件被组织为File Group,由文件Id唯一标识。每个File Group包含多个File Slice,其中每个Slice包含在某个Commit或Compcation Instant时间生成的Base File(*.parquet)以及Log Files(*.log*),该文件包含自生成基本文件以来对基本文件的插入和更新。path
:对应着本次写入的文件路径,因为我们是MOR的表,所以写入的是日志文件prevCommit
:上一次的成功Commit,因为本次Commit是这个表的第一次Commit,所以指向的Instant是自己baseFile
:基本文件,经过上一次Compaction后的文件,对于MOR表来说,每次读取的时候,通过将baseFile
和logFiles
合并,就会读取到实时的数据logFiles
:日志文件,MOR表写数据时,数据首先写进日志文件,之后会通过一次Compaction进行合并compacted
:是否合并extraMetadata
:元数据信息writePartitionPaths
:写入的分区路径fileIdAndRelativePaths
至此,一个deltacommit
文件了解的基本上差不多了;接下来的几个deltacommit
内容都差不多,而我们是通过Flink写入的Hudi表,又自动设置了经过5次deltacommit
之后,就会合并文件,所以我们来看一次文件合并产生的Instant中的操作是什么样的
ll 20210501150617.*
-rw-r--r--@ 1 dijie staff 1772 5 1 19:32 20210501150617.commit
-rw-r--r--@ 1 dijie staff 0 5 1 19:32 20210501150617.compaction.inflight
-rw-r--r--@ 1 dijie staff 1490 5 1 19:32 20210501150617.compaction.requested
有两个文件有数据有内容,我们按照Instant State顺序来看
先看20210501150617.compaction.requested
文件
{
"operations": {
"array": [
{
"baseInstantTime": {
"string": "20210501150108"
},
"deltaFilePaths": {
"array": [
".817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501150108.log.1_1-4-0"
]
},
"dataFilePath": null,
"fileId": {
"string": "817c5634-926f-41e9-a4b1-5c52ae4ce81b"
},
"partitionPath": {
"string": "2021-05-01"
},
"metrics": {
"map": {
"TOTAL_LOG_FILES": 1,
"TOTAL_IO_READ_MB": 1461,
"TOTAL_LOG_FILES_SIZE": 1532984788,
"TOTAL_IO_WRITE_MB": 120,
"TOTAL_IO_MB": 1581
}
},
"bootstrapFilePath": null
}
]
},
"extraMetadata": null,
"version": {
"int": 2
}
}
baseInstantTime
:记录了是基于哪一次的InstantdeltaFilePaths
:Log File路径,在这次Compaction完成之后,对应的Log File也会被清理,因为已经被合并Compaction至Base File中dataFilePath
:Base File路径,因为这是第一次合并,尚未有Base File生成,所以还是NullfileId
:文件IdpartitionPath
:分区路径metrics
:指标值,一眼就能看明白都是什么信息bootstrapFilePath
:引导文件,之后会详解什么是引导,这里就不展开说了,主要是做存量表迁移使用extraMetadata
:元数据version
:版本,这里为何是2我不太理解,之后在看Compaction的时候再去研究再看20210501150617.commit
文件
{
"partitionToWriteStats": {
"2021-05-01": [
{
"fileId": "817c5634-926f-41e9-a4b1-5c52ae4ce81b",
"path": "2021-05-01/817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet",
"prevCommit": "null",
"numWrites": 84455,
"numDeletes": 0,
"numUpdateWrites": 0,
"numInserts": 84455,
"totalWriteBytes": 1096720,
"totalWriteErrors": 0,
"tempPath": null,
"partitionPath": "2021-05-01",
"totalLogRecords": 12277396,
"totalLogFilesCompacted": 1,
"totalLogSizeCompacted": 1532984788,
"totalUpdatedRecordsCompacted": 84455,
"totalLogBlocks": 123,
"totalCorruptLogBlock": 0,
"totalRollbackBlocks": 0,
"fileSizeInBytes": 1096720,
"minEventTime": null,
"maxEventTime": null
}
]
},
"compacted": true,
"extraMetadata": {
"schema": "{\"type\":\"record\",\"name\":\"record\",\"fields\":[{\"name\":\"id\",\"type\":\"long\"},{\"name\":\"dt\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"ts\",\"type\":[\"null\",{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}],\"default\":null}]}"
},
"operationType": "UNKNOWN",
"totalScanTime": 32727,
"totalCompactedRecordsUpdated": 84455,
"totalLogFilesCompacted": 1,
"totalLogFilesSize": 1532984788,
"minAndMaxEventTime": {
"Optional.empty": {
"val": null,
"present": false
}
},
"totalCreateTime": 0,
"totalUpsertTime": 0,
"totalRecordsDeleted": 0,
"totalLogRecordsCompacted": 12277396,
"writePartitionPaths": [
"2021-05-01"
],
"fileIdAndRelativePaths": {
"817c5634-926f-41e9-a4b1-5c52ae4ce81b": "2021-05-01/817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet"
}
}
重复字段就不说了,我们来看点不一样的
partitionToWriteStats
path
:因为我们是Compaction操作,所以路径已经变成了Base File而不是之前的Log FileprevCommit
:变成Null,因为我们已经产生了Base File,之前的Commit其实可以看错无效Commitcompacted
:本次是一次Compaction操作fileIdAndRelativePaths
:变成了本次Compaction之后的路径在完成Compaction没多久之后,我取消了Insert
任务,并在18点多点的时候又执行了代码,我们继续观察时间轴上的变化
-rw-r--r--@ 1 dijie staff 1772 5 1 19:32 20210501150617.commit
-rw-r--r--@ 1 dijie staff 0 5 1 19:32 20210501150617.compaction.inflight
-rw-r--r--@ 1 dijie staff 1490 5 1 19:32 20210501150617.compaction.requested
-rw-r--r--@ 1 dijie staff 1962 5 1 19:32 20210501182405.deltacommit
-rw-r--r--@ 1 dijie staff 0 5 1 19:32 20210501182405.deltacommit.inflight
-rw-r--r--@ 1 dijie staff 0 5 1 19:32 20210501182405.deltacommit.requested
-rw-r--r--@ 1 dijie staff 1550 5 1 19:32 20210501182406.rollback
-rw-r--r--@ 1 dijie staff 0 5 1 19:32 20210501182406.rollback.inflight
时间轴一下子从20210501150617
跳到了20210501182405
,并且在20210501182406
发生了一次rollback
时间轴跳跃能够理解,为什么会发生一次回滚呢?我们先来看文件内容
因为20210501182405.deltacommit
的内容变化不大,所以我们直接来看20210501182406.rollback
这个文件
avro-tools tojson 20210501182406.rollback
{
"startRollbackTime": "20210501182406",
"timeTakenInMillis": 25,
"totalFilesDeleted": 0,
"commitsRollback": [
"20210501150618"
],
"partitionMetadata": {
"2021-05-01": {
"partitionPath": "2021-05-01",
"successDeleteFiles": [],
"failedDeleteFiles": [],
"rollbackLogFiles": {
"map": {}
},
"writtenLogFiles": {
"map": {}
}
}
},
"version": {
"int": 1
},
"instantsRollback": [
{
"commitTime": "20210501150618",
"action": "deltacommit"
}
]
}
startRollbackTime
:进行Rollback的时间timeTakenInMillis
:花费时间totalFilesDeleted
:总共删除的文件,这里有些同学可能会有疑问,既然发生了Rollback,那么应该把当时Instant所产生的文件都删除,为何这里的删除文件个数是0呢?我们接着往下看commitsRollback
:回滚的Instant,你会发现20210501150618
在文件路径下并不存在,这又是为什么?20210501150618
这个时间点上,我应该刚好把任务关闭,所以在目录下20210501150618
这个Instant并没有完成,所以有20210501150618.inflight
和20210501150618.requested
两个文件,而没有20210501150618.deltacommit
文件;而在我又把任务启动,并且触发了第一次Flink的Checkpoint的时候,发现了有未完成的deltacommit
,所以进行了回滚操作,并且删除了20210501150618.inflight
和20210501150618.requested
;而且,我们看到本次ROllback和20210501182405.deltacommit
只差了1秒,这很明显是在Checkpoint时,发现了有未完成的Commit,所以进行了一次回滚;而我在上一次停止任务的原因是没有数据了,所以上一个字段中的总删除文件个数为0partitionMetadata
:分区元数据,一眼能看出描述的是什么,就不再赘述version
:不知道为啥是1,之后看看源码instantsRollback
:是个数组,所有当前被Rollback的Commit都会被记录在案
commitTime
:Commit时间action
:Commit类型,MOR表是deltacommit
,COW表/Compaction是commit
接着时间轴继续往下看,已经讲过的文件类型就不再看了,我们来看看这个
-rw-r--r--@ 1 dijie staff 1468 5 1 19:32 20210501192800.clean
-rw-r--r--@ 1 dijie staff 1450 5 1 19:32 20210501192800.clean.inflight
-rw-r--r--@ 1 dijie staff 1450 5 1 19:32 20210501192800.clean.requested
发现3个文件都有值,我们按照Instant的State,依次来看
先看.requested
文件
avro-tools tojson 20210501192800.clean.requested
{
"earliestInstantToRetain": {
"org.apache.hudi.avro.model.HoodieActionInstant": {
"timestamp": "20210501182405",
"action": "deltacommit",
"state": "COMPLETED"
}
},
"policy": "KEEP_LATEST_COMMITS",
"filesToBeDeletedPerPartition": {
"map": {}
},
"version": {
"int": 2
},
"filePathsToBeDeletedPerPartition": {
"map": {
"2021-05-01": [
{
"filePath": {
"string": "hdfs://hacluster/hudi/hudi_test_dijie/2021-05-01/.817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501150108.log.1_1-4-0"
},
"isBootstrapBaseFile": {
"boolean": false
}
}
]
}
}
}
earliestInstantToRetain
:最早的需要保留的Instant,比这个Instant对应的时间点要小的Instant,都需要被清理;因为Flink Sink的默认清理保留Commit Instant个数为10,所以找到了20210501182405
这个Instantpolucy
:清理策略,保留最新的Commit,另一种是KEEP_LATEST_FILE_VERSIONS
保留最新的File版本filesToBeDeletedPerPartition
:应该是旧的保留字段,现在已经被filePathsToBeDeletedPerPartition
替代filePathsToBeDeletedPerPartition
:每个分区中要被删除的文件再看.inflight
文件,因为和.requested
内容一致,就不再赘述
最后来看20210501192800.clean
{
"startCleanTime": "20210501192800",
"timeTakenInMillis": 21,
"totalFilesDeleted": 1,
"earliestCommitToRetain": "20210501182405",
"partitionMetadata": {
"2021-05-01": {
"partitionPath": "2021-05-01",
"policy": "KEEP_LATEST_COMMITS",
"deletePathPatterns": [
".817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501150108.log.1_1-4-0"
],
"successDeleteFiles": [
".817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501150108.log.1_1-4-0"
],
"failedDeleteFiles": []
}
},
"version": {
"int": 2
},
"bootstrapPartitionMetadata": {
"map": {}
}
}
文件内容比较简单,基本上见字识意
看到这里,基本上时间轴上的文件内容都被我们浏览过了一遍,我们会发现有一个Instant只有.inflight
和.requested
,而且我也是在这个时间点关闭的任务,所以,等我将任务再次启动之后,可以预见的是,将会再次触发一次Rollback
-rw-r--r--@ 1 dijie staff 0 5 1 19:32 20210501192902.deltacommit.inflight
-rw-r--r--@ 1 dijie staff 0 5 1 19:32 20210501192902.deltacommit.requested
看完了时间轴,我们再看一下元数据目录的另外几个文件夹
首先来看.aux
下面的内容
tree -a
.
├── .bootstrap
│ ├── .fileids
│ └── .partitions
├── 20210501150617.compaction.requested
├── 20210501182821.compaction.requested
└── 20210501192800.compaction.requested
这3个.requested
文件和我们时间轴上的文件是一一对应的,就不展开说了;.bootstrap
下存放的是进行引导操作的时候的文件,引导操作是用来将已有的表转化为Hudi表的操作,因为我们并没有执行这个,所以下面也自然没有内容
再来看.temp
下的内容
tree -a
.
├── 20210501150617
│ └── 2021-05-01
│ └── 817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet.marker.CREATE
├── 20210501182821
│ └── 2021-05-01
│ └── 817c5634-926f-41e9-a4b1-5c52ae4ce81b_4-10-0_20210501182821.parquet.marker.MERGE
└── 20210501192800
└── 2021-05-01
├── 817c5634-926f-41e9-a4b1-5c52ae4ce81b_5-10-0_20210501192800.parquet.marker.MERGE
└── b8561bdf-2434-4f3b-8f78-41c648762f68_0-10-0_20210501192800.parquet.marker.CREATE
这里记录的是每次Base File的一些操作,在我们程序进行中时,如果存在往Base File中追加的操作,那么在这个目录下,也会有*.APPEND
文件的产生,现在看不到了是因为我们发生了MERGE的操作
archived
目录,存放归档Instant的目录,当不断写入Hudi表时,Timeline上的Instant数量会持续增多,为减少Timeline的操作压力,会在Commit时对Instant进行归档,并将Timeline上对应的Instant删除。
因为我们的Instant个数尚未达到默认值30个,所以并没有产生对应的文件,大家可以自行尝试后自行分析
最后一个元数据文件hoodie.properties
#Properties saved on Sat May 01 15:01:08 CST 2021
#Sat May 01 15:01:08 CST 2021
hoodie.compaction.payload.class=org.apache.hudi.common.model.OverwriteWithLatestAvroPayload
hoodie.table.name=hudi_test_dijie
hoodie.archivelog.folder=archived
hoodie.table.type=MERGE_ON_READ
hoodie.table.version=1
hoodie.timeline.layout.version=1
记录表的类型、Compaction Class名称、归档目录、版本等信息;如果一个Hudi表没有这个文件,那么将无法读取表中内容,这个文件不在建表时被创建,而是当第一次往该表中写入数据,才会创建这个文件;所以如果一个表没有写入过数据,那么无法被读取
至此,Hudi表的所有元数据信息都被我们分析完毕
先看看都有哪些文件
tree -a
.
├── .817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501150617.log.1_1-4-0
├── .817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501182821.log.1_1-4-0
├── .817c5634-926f-41e9-a4b1-5c52ae4ce81b_20210501192800.log.1_1-4-0
├── .b8561bdf-2434-4f3b-8f78-41c648762f68_20210501192502.log.1_3-4-0
├── .b8561bdf-2434-4f3b-8f78-41c648762f68_20210501192800.log.1_3-4-0
├── .hoodie_partition_metadata
├── 817c5634-926f-41e9-a4b1-5c52ae4ce81b_4-10-0_20210501182821.parquet
├── 817c5634-926f-41e9-a4b1-5c52ae4ce81b_5-10-0_20210501192800.parquet
├── 817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet
└── b8561bdf-2434-4f3b-8f78-41c648762f68_0-10-0_20210501192800.parquet
分为3种文件:Log File(.*log.*),分区元数据(.hoodie_partition_metadata),数据文件(.*parquet)
其中Log File是Hudi自己编码的Avro文件,无法用avro-tools
进行查看,所以就不在分析了;可以告诉大家的是,并不是实时将数据写入Log File,Hudi先将数据缓存至内存,然后再批量构造成Block后再写入Log File,并且当Log File达到一定大小后,会自动写入新的Log File,有点像Log4j的日志滚动策略;另外,每个Block中包含以下内容
Magic Number
Block Size
Block Version
Block Type
Block Headers
Data Length
Data
Block Footers
Block Size
如果想更深入的了解Log File的写入过程,可以参考org.apache.hudi.common.table.log.HoodieLogFormatWriter
这个类
接下来再看.hoodie_partition_metadata
文件,顾名思义,存放的是分区元数据
cat .hoodie_partition_metadata
#partition metadata
#Sat May 01 15:01:28 CST 2021
commitTime=20210501150108
partitionDepth=1
比较简单,再让我们看最后的Base File;因为它是个parquet
文件,所以我们先看meta
再看schema
最后看里面的数据
parquet-tools meta 817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet > ttt.log
vim ttt.log
因为meta
里面的内容太多了一屏幕展示不下,所以丢到了临时文件里面,这里也就不全部贴出来了,放不下
1 file: file:/Users/dijie/Downloads/hudi_test_dijie/2021-05-01/817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet
2 creator: parquet-mr version 1.11.1 (build 765bd5cd7fdef2af1cecd0755000694b992bfadd)
这两行应该是parquet-tools
输出的,无视即可,我们将下一行当做真正的第一行
extra: org.apache.hudi.bloomfilter = /wAAAB4BACd9PmrJ0jr1fDm3LH4fx...
省略号是我自己加的,内容太多不贴出来了;可以看到,在该parquet
的meta
中,构建了一个Bloomfilter,记录了所有写入该文件的数据的key
,当有数据想要写入时,会用来判断数据存在与否,当数据存在时,会读取实文件进行二次判断,以便修正Bloomfilter的误差。
我们再看一下剩下的内容
4 extra: hoodie_min_record_key = 1
5 extra: parquet.avro.schema = {"type":"record","name":"record","fields":[{"name":"_hoodie_commit_time","type":["null","string"],"doc":"","default":null},{"name":"_hoodie_commit_seqno","type":["null","string"],"doc":"", "default":null},{"name":"_hoodie_record_key","type":["null","string"],"doc":"","default":null},{"name":"_hoodie_partition_path","type":["null","string"],"doc":"","default":null},{"name":"_hoodie_file_name","type":["null","string"],"do c":"","default":null},{"name":"id","type":"long"},{"name":"dt","type":["null","string"],"default":null},{"name":"ts","type":["null",{"type":"long","logicalType":"timestamp-millis"}],"default":null}]}
6 extra: writer.model.name = avro
7 extra: hoodie_max_record_key = 9999
8
9 file schema: record
10 --------------------------------------------------------------------------------
11 _hoodie_commit_time: OPTIONAL BINARY L:STRING R:0 D:1
12 _hoodie_commit_seqno: OPTIONAL BINARY L:STRING R:0 D:1
13 _hoodie_record_key: OPTIONAL BINARY L:STRING R:0 D:1
14 _hoodie_partition_path: OPTIONAL BINARY L:STRING R:0 D:1
15 _hoodie_file_name: OPTIONAL BINARY L:STRING R:0 D:1
16 id: REQUIRED INT64 R:0 D:0
17 dt: OPTIONAL BINARY L:STRING R:0 D:1
18 ts: OPTIONAL INT64 L:TIMESTAMP(MILLIS,true) R:0 D:1
19
20 row group 1: RC:84455 TS:3715500 OFFSET:4
21 --------------------------------------------------------------------------------
22 _hoodie_commit_time: BINARY GZIP DO:0 FPO:4 SZ:329/219/0.67 VC:84455 ENC:RLE,BIT_PACKED,PLAIN_DICTIONARY ST:[min: 20210501150617, max: 20210501150617, num_nulls: 0]
23 _hoodie_commit_seqno: BINARY GZIP DO:0 FPO:333 SZ:219024/2184907/9.98 VC:84455 ENC:RLE,PLAIN,BIT_PACKED ST:[min: 20210501150617_6_1, max: 20210501150617_6_9999, num_nulls: 0]
24 _hoodie_record_key: BINARY GZIP DO:0 FPO:219357 SZ:192644/749171/3.89 VC:84455 ENC:RLE,PLAIN,BIT_PACKED ST:[min: 1, max: 9999, num_nulls: 0]
25 _hoodie_partition_path: BINARY GZIP DO:0 FPO:412001 SZ:324/214/0.66 VC:84455 ENC:RLE,BIT_PACKED,PLAIN_DICTIONARY ST:[min: 2021-05-01, max: 2021-05-01, num_nulls: 0]
26 _hoodie_file_name: BINARY GZIP DO:0 FPO:412325 SZ:485/347/0.72 VC:84455 ENC:RLE,BIT_PACKED,PLAIN_DICTIONARY ST:[min: 817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet, max: 817c5634-926f-41e9-a4b1-5c52ae4ce81b_6 -10-0_20210501150617.parquet, num_nulls: 0]
27 id: INT64 GZIP DO:0 FPO:412810 SZ:166768/675782/4.05 VC:84455 ENC:PLAIN,BIT_PACKED ST:[min: 1, max: 84455, num_nulls: 0]
28 dt: BINARY GZIP DO:0 FPO:579578 SZ:324/214/0.66 VC:84455 ENC:RLE,BIT_PACKED,PLAIN_DICTIONARY ST:[min: 2021-05-01, max: 2021-05-01, num_nulls: 0]
29 ts: INT64 GZIP DO:0 FPO:579902 SZ:80767/104646/1.30 VC:84455 ENC:RLE,BIT_PACKED,PLAIN_DICTIONARY ST:[min: 2021-05-01T15:05:46.010+0000, max: 2021-05-01T15:05:51.844+0000, num_nulls: 0]
可以看到,meta
还记录了该文件中的最小Key和最大Key,可以在Bloomfilter过滤之前就过滤掉数据
剩下的就是一些字段信息,字段格式,字段最大值最小值等等,它的schema
信息可以说已经包含在了meta
之中,所以说我们就直接跳过,来看最终的数据
parquet-tools head -n 2 817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet
_hoodie_commit_time = 20210501150617
_hoodie_commit_seqno = 20210501150617_6_1
_hoodie_record_key = 3640
_hoodie_partition_path = 2021-05-01
_hoodie_file_name = 817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet
id = 3640
dt = 2021-05-01
ts = 1619881551277
_hoodie_commit_time = 20210501150617
_hoodie_commit_seqno = 20210501150617_6_2
_hoodie_record_key = 3638
_hoodie_partition_path = 2021-05-01
_hoodie_file_name = 817c5634-926f-41e9-a4b1-5c52ae4ce81b_6-10-0_20210501150617.parquet
id = 3638
dt = 2021-05-01
ts = 1619881551295
内容比较简单,就是每条数据对应的字段的值,除此之外还有一些额外的信息,也比较好理解,就不再多说
好了,至此,我们可以说是把MOR表生成的文件都看了一遍;可能有些文件有所遗漏,比如Savepoint产生的文件,但是只有Hudi-cli才能进行这样的操作并生产对应的文件,我并没有尝试,所以也就没有无法去分析