前言
本文为笔者个人阅读Apache Impala源码时的笔记,仅代表我个人对代码的理解,个人水平有限,文章可能存在理解错误、遗漏或者过时之处。如果有任何错误或者有更好的见解,欢迎指正。
上一篇文章Impala4.x源码阅读笔记(二)——Impala如何高效读取Iceberg表简单介绍了Iceberg表的基本情况和Impala是如何对其进行扫描的。这一篇则从元数据的角度对Impala如果管理Iceberg元数据进行一些简单的分析,这里的Iceberg元数据不是指Iceberg的那些元数据文件,那些是Iceberg API负责管理的,这里的元数据是指Iceberg表在Impala中的那些内存对象。
为了提升查询性能,Impala本身有一套比较复杂的元数据管理机制,这套机制以Catalogd服务进程为核心,实现了元数据在集群内的集中管理。在一个完整的Impala集群中,Catalogd服务进程主要担任了两个角色,首先是一个自动化的元数据缓存,它会负责缓存并自动同步Hive Metastore中的元数据,并将其广播给集群中其他负责处理查询的Coordinator节点,这使得Coordinator可以省去每次查询时和Hive Metastore(HMS)以及HDFS Namenode(NN)的交互,从而缩短了查询耗时。Catalogd的另一个角色是一个集中式的DDL执行者,其他Coordinator节点接收到的DDL最终都会以RPC的形式交由Catalogd进行执行,这样使得集群内部的元数据统一得到了保证。
Iceberg表作为一种表格式而非文件格式,其核心目标是高效且多功能地管理大量的数据文件,为了达成这一目标,Iceberg表的元数据相较于Hive表也更加复杂,从Impala支持Iceberg表的代码大部分都是元数据相关的也能看出这一点。关于Iceberg元数据管理的代码十分庞大,想要在一篇文章内全面地分析一遍是不太现实的,所以本文首先还是整体地、笼统地描述一下Iceberg表元数据的全貌,然后着重对元数据管理的两个关键环节——Iceberg表的加载和创建进行分析。
Iceberg表的相关接口
由于Impala本身元数据管理的特性和Iceberg表元数据的特殊性,在Impala支持Iceberg表各项功能的过程中定义了很多类型,我们首先看一下Iceberg相关类的整体UML图:
图中画出了Iceberg表在Impala中的主要相关接口和类以及其之间的实现或继承关系,其中绿色背景的就是与Iceberg表直接相关的,每个接口和类中都只列举了个别关键的成员变量和方法。在介绍具体的类之前我们先看一下其中的几个接口。
FeTable
首先是Impala中所有类型的表都要实现的接口FeTable
,它定义了Impala Frontend与各类型表交互的一些基本操作,比如获取获取库表名、数据列列表、所有者等,其中还包括了可以获取org.apache.hadoop.hive.metastore.api.Table
对象的getMetaStoreTable()
方法。因为Impala对HMS是强依赖的,基本上所有元数据都来自HMS,为了与HMS进行元数据交互,Impala中所有类型的表中都包括一个HMS中表对象也就是org.apache.hadoop.hive.metastore.api.Table
。它为Impala的表提供了基本的元数据,比如库表名、字段信息和表属性properties等。Iceberg表在Impala也不例外,需要在HMS注册了才能被Impala查询。当然Iceberg表本身并不一定依赖HMS,比如使用HadoopCatalog创建的Iceberg表只依赖一个像HDFS一样的支持原子重命名文件的文件系统而已。对于这种Iceberg表需要先在Impala中通过创建外表的方式在HMS进行注册才能被Impala元数据管理所接受。
FeFsTable
FeFsTable
是Impala中所有基于文件系统的表类型都要实现的接口,它继承了FeTable
接口,作用也是类似的。像存储在HDFS、S3这种常见的文件系统或存储服务上的表都属于FeFsTable
,Iceberg表自然也是FeFsTable
。在FeTable
的基础之上,FeFsTable
额外定义了许多和文件系统相关的方法,比如获取文件系统类型、表位置和文件系统对象FileSystem
等。
FeIcebergTable
FeIcebergTable
是Impala中Iceberg表类型都要实现的接口,继承了FeFsTable
并额外定义了关于Iceberg的通用方法。其中有几个关键方法需要重点介绍:
-
getFeFsTable()
,它会返回Iceberg对象内置的一个FeFsTable
对象,这个对象会被用于将Iceberg表传递给Impala Backend。我们知道在Impala中Frontend负责制定执行计划、Backend负责执行,两者分别由Java和C++开发,之间主要通过Thrift结构体传递数据,这些数据也包括了查询的执行计划。而Iceberg表在执行期间与普通HDFS表实际上并没有显著差别,可以说都只是一系列规划好的数据文件而已。因此,为了复用Backend中现有的HDFS表扫描代码,Iceberg表对象都内置了一张普通HDFS表对象,在序列化为Thrift结构体传递给Backend时就使用这个内置的FeFsTable
对象的相关方法将自身“转变”为HDFS表。 -
getIcebergApiTable()
,它会返回Iceberg表对象对应的Iceberg API中的表对象org.apache.iceberg.Table
,这是Iceberg API中的表示Iceberg表的接口,它提供了Iceberg表的许多重要API,比如获取快照、Schema和扫描计划。依靠这些接口,Impala可以进行Iceberg表的时间旅行查询、模式演进、谓词下推和获取数据文件列表等操作。 -
getIcebergCatalog()
,它会返回Iceberg表的Catalog类型,目前Impala支持的Iceberg Catalog类型有HadoopTables、HadoopCatalog、HiveCatalog和Catalogs。Iceberg的Catalog是用于追踪Iceberg表的,它主要负责储存Iceberg表最近元数据文件的位置,可以说是Iceberg表元数据的元数据。换句话说如果说Iceberg表是管理一系列数据文件并告诉我们数据文件在哪里,那么Iceberg Catalog的作用就是管理一系列Iceberg表并告诉我们Iceberg表在哪里。如果Iceberg表的最近元数据位置也直接储存在文件系统的一个文件中,则对应HadoopTables。如果文件系统中有一个专门的Catalog目录,Iceberg表的元数据位置由其负责管理,则对应HadoopCatalog。如果使用HMS储存Iceberg表的最近元数据位置,则对应HiveCatalog。而Catalogs接口相当于一种复合的自动Catalog,它依赖配置文件和表属性自动识别Iceberg表的Catalog类型。
接口FeIcebergTable
可以说是Iceberg表在Impala中的关键抽象,从图中也可以看到许多Iceberg表的相关类实现了该接口。
Iceberg表的相关类
介绍完了相关接口之后,我们接下来继续看看Iceberg相关的类。从图中可以看到与Iceberg直接相关的表类型就有足足七种,当然其中除了IcebergTable
和LocalIcebergTable
这两个真正表示实际存在的Iceberg表的“正经”表类型外,其他的都可以算是为了支持各种Iceberg特性而抽象出来的功能性的工具类。接下来我们逐个介绍。
IcebergTable
IcebergTable
是Iceberg表在Impala元数据管理中的代理类之一,每个对象都是对应了一张实际存在的Iceberg表。IcebergTable
实现了FeIcebergTable
接口并继承了Table
类。Table
类是Impala中所有表类的主要父类之一(另一个是LocalTable
),它是一个抽象类,实现了FeTable
接口,它定义了所有表共有的一些成员变量,如库对象、表名、所有者、表锁和数据列容器等等,它还定义了表对象共有的一些成员方法,其中最重要的就是实现表加载的抽象方法load()
以及Coordinator接收到Catalogd服务广播的元数据Thrift结构体后从Thrift结构体加载元数据的loadFromThrift(TTable)
方法。IcebergTable
作为Table
的子类,实现了自己的load()
方法来加载Iceberg表,除此之外还包括一些特有的成员,如前文提到的内置的HDFS表对象hdfsTable_
、Iceberg API表对象icebergApiTable_
和从Iceberg元数据加载Schema的方法loadSchemaFromIceberg()
等等。
LocalIcebergTable
LocalIcebergTable
可以理解为IcebergTable
的Local版本,它只在Coordinator的Local Catalog模式下使用,而IcebergTable
会在Catalogd和Coordinator的传统Catalog模式下使用,LocalIcebergTable
在Coordinator的作用和IcebergTable
基本是一致的,可以说是更加轻量化的IcebergTable
。Local Catalog模式是为了解决传统Catalog模式的一些缺点而设计的,它支持更细粒度的元数据缓存并能在启动时按需加载元数据,提升了Coordinator的启动速度并减少了内存消耗。LocalIcebergTable
同样实现了FeIcebergTable
接口,但是继承的是LocalTable
类,而不是Table
类。LocalTable
类也是抽象类,是Table
类的Local版本,其成员LocalDb
这是Db
类的Local版本。如同IcebergTable
一样,LocalIcebergTable
也内置了一张HDFS表对象,不过不再是HdfsTable
类了,而是其Local版本的LocalFsTable
,这些Local类都是只在Coordinator的Local Catalog模式下使用的,和非Local版本一一对应。
IcebergPositionDeleteTable
IcebergPositionDeleteTable
是用于Iceberg MOR的虚拟表,在上一篇文章中其实已经登场过了,它只在制定Iceberg的Position Delete扫描计划中会被使用到,用来将Iceberg表的Delete File组织为一张虚拟表,这样才能使用Impala的ScanNode进行扫描,具体的使用过程可以参考上一篇文章Impala4.x源码阅读笔记(二)——Impala如何高效读取Iceberg表。IcebergPositionDeleteTable
同样实现了FeIcebergTable
接口,不过它继承的是表示虚拟表的抽象类VirtualTable
,虚拟表不是实际存在的表,而是为了实现某些特定功能而虚拟出来的表,它往往会根据需要而添加一些虚拟列,可以将非表形式的数据以表的形式进行处理。
IcebergMetadataTable
Iceberg API提供了一系列专门的元数据表来查询Iceberg表的元数据,可通过其MetadataTableUtils
类来创建各种类型的Iceberg元数据表,如ManifestEntriesTable
、FilesTable
和SnapshotsTable
等。这些元数据表基于基本的Iceberg表创建,有各自的Schema,用于查询该表的各种元数据。IcebergMetadataTable
就是Impala为了对接这些Iceberg元数据表而定义的类,它是另外一个继承了VirtualTable
的类,不过它并没有实现FeIcebergTable
接口,因为它不是通常的Iceberg表。它可以根据一个FeIcebergTable
对象和元数据表类型字符串来创建,利用MetadataTableUtils
来对接Iceberg元数据表获取Schema并依此填充自身作为VirtualTable
的虚拟列,执行时IcebergMetadataTable
由执行引擎这边的专门的IcebergMetadataScanNode
负责扫描,当然由于执行引擎是C++编写的,所以实际扫描时还是需要通过JNI调用Iceberg API来完成。
IcebergCtasTarget
IcebergCtasTarget
是用于CTAS(Create Table As Select)语句的临时目标表类型,它继承了CtasTargetTable
类并实现了FeIcebergTable
接口,不过它也不是实际存在的表,只是用于CTAS的分析过程。Impala分析CTAS语句时会将其分解为CREATE语句和INSERT语句,然后根据CREATE语句先创建临时目标表,再结合临时目标表来分析INSERT语句。如果分析过程顺利完成才会真正创建目标表。对于Iceberg表来说,临时目标表只是分析使用的,不应该通过Iceberg API实际创建它,所以需要IcebergCtasTarget
来充当这一角色。IcebergCtasTarget
实现了FeIcebergTable
,但是并不会通过Iceberg API实际创建一张Iceberg表。
ForwardingFelcebergTable
ForwardingFelcebergTable
一个用于FeIcebergTable
的转发类,也并非什么实际存在的表,只是一种使用组合代替继承的编程技巧,通过ForwardingFelcebergTable
可以在不继承基类的前提下将不需要重写的方法委托给基类FeIcebergTable
。这个类会在IcebergTimeTravelTable
中使用,避免IcebergTimeTravelTable
继承IcebergTable
、LocalIcebergTable
等类。
IcebergTimeTravelTable
IcebergTimeTravelTable
表示进行时间旅行的Iceberg表,由于Iceberg表时间旅行和模式演进的特性,在不同的时间点Iceberg表可能有不同的Schema,因此对于进行时间旅行的Iceberg表我们需要根据时间或版本重新加载Schema,为了避免复制或破坏原始的Iceberg元数据,Impala通过IcebergTimeTravelTable
来实现时间旅行的Iceberg表。IcebergTimeTravelTable
没有继承FeIcebergTable
而是继承了ForwardingFelcebergTable
,通过ForwardingFelcebergTable
嵌入对原始Iceberg表的引用并在此基础之上实现readSchema()
加载自己的Schema,而那些未涉及时间旅行的方法都可以通过ForwardingFelcebergTable
委托给原始Iceberg表类的同名方法 。
至此Iceberg表在Impala中的相关类就介绍完了,可以发现除了IcebergTab