Implementation
(为此文档的整理工作道歉,我认为有总比没有好-Thejas)
这是 hbase 工作中 metastore 的一部分-HIVE-9452-获取问题详细信息...状态
Functionality needed
支持分区过滤的 RawStore 函数如下:
-
getPartitionsByExpr
-
getPartitionsByFilter(将过滤字符串作为参数,从 hcatalog 使用)
对于给定的过滤条件,我们需要根据 Hbase 扫描 api 调用生成查询执行计划。
有关要支持的 api 的说明
getPartitionsByExpr-当前分区表达式评估路径 ExprNodeGenericFuncDesc 表示计划中的分区过滤器表达式
-
它被序列化为 byte [],并使用 byte []调用 Metastore api。
-
ObjectStore 处理表达式-
-
反序列化 byte [],将其打印以将其转换为 Filter 字符串
-
使用解析器(Filter.g)将筛选器字符串转换为 ExpressionTree
-
遍历 ExpressionTree 创建 SQL 查询(在直接 SQL 中)
-
getPartitionsByFilter-评估类似,只是跳过创建过滤器字符串所需的步骤。我们当然需要能够使用过滤器字符串来支持此功能。
为什么我们从 ExprNodeGenericFuncDesc 转换为 kryo 序列化 byte []而不转换为过滤器字符串?
筛选当前支持的表达式
叶子运算符:=,>,\ <, <=, > =,喜欢,!=
逻辑运算符:AND,OR
hbase 中的分区表
分区信息与键一起作为分隔字符串存储在其中,该字符串由-数据库名称,表名称,分区值组成
该值包含其余分区信息。 (旁注:我们需要值部分中的分区值吗?)
hbase 中分区表键的序列化格式
密钥序列化格式的理想属性-
-
可以在不反序列化字段的情况下对键执行过滤器操作(LIKE 运算符并不常见,因此,如果我们必须对该序列反序列化则可以)
-
分区键的实际 Sequences 和键的字节 Sequences 应匹配
-
应该有可能有效地提取过滤器密钥的相关部分。即,应该有可能找到代表分区值的字节的开始和结束而无需检查每个先前的字节。
BinarySortableSerDe 满足这些要求(数字 3 除外)。满足要求 3 可能需要一些索引信息存储在序列化密钥的末尾。
当前存储格式的限制(无辅助键)
如果一个表有多个分区键,并且分区过滤条件在第一个分区键上没有条件,我们将最终在表的所有分区中进行扫描以找到匹配项。对于这种情况,我们需要支持表上的二级索引。尽管我们可以使用第二个表来实现这一点,但是缺少对跨行/表的原子操作的支持是一个问题。我们将需要在 hbase 中提供某种程度的事务支持,以便能够可靠地创建二级索引。
过滤分区
hbase api 的使用将取决于过滤条件-
-
对于初始分区列中用于检查特定分区或分区范围的简单分区过滤条件,我们可以将它们转换为简单的 Hbase 扫描操作,而无需任何过滤器(新Scan(byte [] startRow,byte [] stopRow))
-
如果涉及到更多分区列的查询更为复杂,我们还需要使用一个扫描过滤器,同时对其余列也有条件。即,新的Scan(byte [] startRow,byte [] stopRow)Scan.setFilter(..)
-
如果第一分区列上没有条件,则将需要扫描表上的所有分区。在这种情况下,开始行和结束行将仅基于键的 db 表前缀。
具有顶级“ OR”条件的过滤器-应对 OR 下的每个条件进行评估,以查看上述哪种 api 调用模式适合它们。如果任一条件不需要 3 个调用模式,则使用 api 调用模式 3 表示整个过滤条件是有意义的。
将查询计划转换为 HBase API 调用的示例
-
下面的合并功能执行一个 set-union
-
p1 代表第一个分区列
-
scan(startRow,endRow)从 startRow 扫描到 endRow 之前的行。即,它通过在 startRow 上添加零字节来表示(r> = startRow 和 r< endRow). But it can be made to represent (r > startRow)的行,并使其表示(r<= endRow) by adding zero byte to endRow. ie, the plans for > =和>相似,<=和=相似。
-
与表分区相对应的所有键的公共前缀均为“ db tablename”。在以下示例中将其称为“ X”。
Filter expression | HBase calls |
p1> 10 和 p1 <20 | Scan(X10+, X20) |
p1 = 10(如果是单个分区列) | 扫描(X10,X10)。优化? :获取(X10) |
如果指定了所有分区列,则与上述类似 | |
p1 = 10(多个分区列) | Scan(X10, X+) |
p1 = 9 或 p1 = 10 | merge(get(X9),get(X10)) |
p1> 10 或 p1 <20 | 合并(scan(X10,X),scan(X,X20)) |
(第一个分区列以外的列上的条件):condition1 | Scan(X, X+).setFilter(genFilter(condition1)) |
p1> 10 和 condition1 | scan(X10, X+).setFilter(genFilter(condition1)) |
p1 <20 和 condition1 | 扫描(X,X20).setFilter(genFilter(condition1)) |
p1> 10 和 p1> 20 且 p1 <30 和 p1 <40 | Scan(X20+, X30) |
p1> 10 和(p1> 20 或 c1 = 5) | |
=> (p1> 10 且 p1> 20)或(p1> 10 且 c1 = 5) | 合并(Scan(X20,X),Scan(X10,X).setFilter(genFilter(c1 = 5))) |
(特殊条件为 OR 条件,如果条件之一导致全表扫描):condition1 或 condition2 | Scan(X).filter(getCombinedFilter(condition1,condition2) (即使用过滤器转换为全表扫描) |
(具有“或”条件的一般情况):condition1 或 condition2 | merge(getResult(condition1),getResult(condition2)) |
c1 和(c2 或 c3) | (c1 和 c2)或(c1 和 c3) |
(c1 或 c2)和(c3 或 c4) | (c1 和 c3)或(c2 和 c3)或(c1 和 c4)或(c2 和 c4) |
相关类:
Input:
ExpressionTree(现有)-AND/OR 表达式的 TreeNodes。叶节点,用于具有=,<...的叶表达式
Output:
公共静态抽象类 FilterPlan {
抽象的 FilterPlan 和(FilterPlan 其他);
抽象 FilterPlan 或(FilterPlan 其他);
抽象列表\ getPlans();
}
//表示多个 ScanPlan 的并集
MultiScanPlan 扩展了 FilterPlan
ScanPlan 扩展了 FilterPlan
//代表扫描开始
私有 ScanMarker startMarker;
//代表扫描结束
私有 ScanMarker endMarker;
专用 ScanFilter 过滤器;
公共 FilterPlan 和(其他 FilterPlan){
//在其他每个扫描计划上调用 this.and(otherScanPlan)
}
私人 ScanPlan 和(ScanPlan 其他){
//组合开始标记和结束标记以及此标记和其他标记的过滤器
}
公共 FilterPlan 或(其他 FilterPlan){
//只需使用其他计划,就可以从其他人创建一个新的 FilterPlan
}
PartitionFilterGenerator -
/**
- ExpressionTree 的访问者。
*它首先为叶节点生成 ScanPlan。较高级别的节点是
- AND 或 OR 运算。然后使用以下命令调用 FilterPlan.and 和 FilterPlan.or
*子节点为更高级别的节点生成计划。
*/
初始实现:从 ExpressionTree 转换为 Hbase 过滤器,从而实现 getPartitionsByFilter 和 getPartitionsByExpr
需要创建一个新的自定义 Filter 类实现。 Filter 类实现 Writable,并且要评估的 hbase 表达式被序列化
如果设置了新的 fastpath 配置,我们可以直接从 ExprNodeGenericFuncDesc 创建过滤器。