0


Presto 查 Hive 元数据这么慢?发现 bug 啦?一个成为贡献者的机会!

文章目录

1、又来活儿了

  又是一天风和日丽的下午,本喵收到业务反馈,xx(某知名报表软件)上看表的元数据太慢啦,比查数据还慢,瘦不鸟啦! 跟厂商反馈了,厂商让我们自查 presto 集群问题。


2、问题现象

  presto 用的是 0.220 版本,业务方反应并不是所有的库表都慢,并提供了几个比较慢的库表名称供排查。正常的表大概 1 秒钟左右能出来元数据,有问题的表等 5 min 都不一定出得来。初步看了一下,这几个库的表和字段都比较多(这里已经埋下了伏笔)。


3、勤恳分析过程

3.1、初步定位

  报表软件是通过 presto-jdbc 来获取 hive 表的元信息的,追到这里面的时候还发现了一个点,presto 的 jdbc 居然是封装的 okhttp,本质就是一个 http 客户端,有点变态,哦不,有点东西。ok,这个不重要,让我们继续看下去。通过远程 debug,发现了耗时比较多的点在获取表的字段这个方法里面。

@OverridepublicResultSetgetColumns(String catalog,String schemaPattern,String tableNamePattern,String columnNamePattern)throwsSQLException{StringBuilder query =newStringBuilder(""+"SELECT TABLE_CAT, TABLE_SCHEM, TABLE_NAME, COLUMN_NAME, DATA_TYPE,\n"+"  TYPE_NAME, COLUMN_SIZE, BUFFER_LENGTH, DECIMAL_DIGITS, NUM_PREC_RADIX,\n"+"  NULLABLE, REMARKS, COLUMN_DEF, SQL_DATA_TYPE, SQL_DATETIME_SUB,\n"+"  CHAR_OCTET_LENGTH, ORDINAL_POSITION, IS_NULLABLE,\n"+"  SCOPE_CATALOG, SCOPE_SCHEMA, SCOPE_TABLE,\n"+"  SOURCE_DATA_TYPE, IS_AUTOINCREMENT, IS_GENERATEDCOLUMN\n"+"FROM system.jdbc.columns");List<String> filters =newArrayList<>();emptyStringEqualsFilter(filters,"TABLE_CAT", catalog);emptyStringLikeFilter(filters,"TABLE_SCHEM", schemaPattern);optionalStringLikeFilter(filters,"TABLE_NAME", tableNamePattern);optionalStringLikeFilter(filters,"COLUMN_NAME", columnNamePattern);buildFilters(query, filters);

    query.append("\nORDER BY TABLE_CAT, TABLE_SCHEM, TABLE_NAME, ORDINAL_POSITION");returnselect(query.toString());}

  好家伙,在 jdbc 内部拼 sql 是吧,ok,system.jdbc.columns 是 presto 内部表,机制先不聊。这里看到,这里的变量名都是 pattern 结尾,也就是模糊查询,最后拼接成的 sql 也是长这样子

SELECT TABLE_CAT,TABLE_SCHEM,TABLE_NAME, COLUMN_NAME, DATA_TYPE,TYPE_NAME, COLUMN_SIZE, BUFFER_LENGTH,...FROM system.jdbc.columnsWHERE TABLE_CAT ='hive'AND TABLE_SCHEM ='xx_schema'AND TABLE_NAME like 'xx_table"
ORDERBY TABLE_CAT, TABLE_SCHEM, TABLE_NAME, ORDINAL_POSITION;

  可以看到,前面不同的

xxxFilter

被映射成了

=

like

,那就不能不怀疑这个

like

的问题了。直接把这条生成的 sql 放到 presto 上去执行,效果和反馈的问题一样,某些表查得快,某些表查得慢。关键来了,把

like

换成

=

,不管查什么表都很快了;或者,把

TABLE_SCHEM

的也换成

like

,效果更差了。其实问题到这里,可以结束了,把这里的

like

改一下就能解决问题了。

3.2、深度分析

  但是!为什么这里用

like

就快了呢,system.jdbc.columns 其实不是实体表,也不存在索引的问题,大概率最终还是去查的数据源的元数据信息,就算是模糊匹配,不应该有这么明显的差距。那问题出在了哪里?

  这该死的好奇心啊,那就继续排查!

  sql 在 presto coordinator 这边进行语法解析,生成了执行计划,然后…等等,好像不一样呀,这是用

=

生成的语法树结构

AND(AND(EQUAL(table_cat, CAST(Slice{base=[B@29291eb8, address=16, length=4})), EQUAL(table_schem, CAST(Slice{base=[B@40c3d3f0, address=16, length=7}))), LIKE(table_name, CAST(Slice{base=[B@7020a486, address=16, length=11})))

  会生成一个叫

prefix

的过滤条件供查元数据的时候使用,为

hive.default.*

这是用

like

生成的:

AND(AND(EQUAL(table_cat, CAST(Slice{base=[B@29291eb8, address=16, length=4})), EQUAL(table_schem, CAST(Slice{base=[B@40c3d3f0, address=16, length=7}))), EQUAL(table_name, CAST(Slice{base=[B@7020a486, address=16, length=11})))

  对应的

prefix

hive.default.table_name

  所以当使用

like

的时候,根本不是模糊查询,就是全量在匹配!前者拿的是某库下的所有表的所有字段,然后逐一比对查出来的schema、表是否匹配,耗时就在这里;实际后者只用拿到这个表的所有字段。所以这个问题一直存在,只是在表和字段数量都多的时候才暴露出来。

  所以可以基本确实,是执行计划的生成有问题了。直接祭出我的二分定位法,出来吧!bug 代码!
  查内置表元数据的 where 表达式里面的代码会调到

RowExpressionDomainTranslator#visitCall

来解析处理

@OverridepublicExtractionResult<T>visitCall(CallExpression node,Boolean complement){// not 语法if(node.getFunctionHandle().equals(resolution.notFunction())){return node.getArguments().get(0).accept(this,!complement);}// beetween 操作if(resolution.isBetweenFunction(node.getFunctionHandle())){// Re-write as two comparison expressionsreturnand(binaryOperator(GREATER_THAN_OR_EQUAL, node.getArguments().get(0), node.getArguments().get(1)),binaryOperator(LESS_THAN_OR_EQUAL, node.getArguments().get(0), node.getArguments().get(2))).accept(this, complement);}// 比较操作FunctionMetadata functionMetadata = metadata.getFunctionManager().getFunctionMetadata(node.getFunctionHandle());if(functionMetadata.getOperatorType().map(OperatorType::isComparisonOperator).orElse(false)){Optional<NormalizedSimpleComparison> optionalNormalized =toNormalizedSimpleComparison(functionMetadata.getOperatorType().get(), node.getArguments().get(0), node.getArguments().get(1));if(!optionalNormalized.isPresent()){returnvisitRowExpression(node, complement);}NormalizedSimpleComparison normalized = optionalNormalized.get();RowExpression expression = normalized.getExpression();Optional<T> column = columnExtractor.extract(expression);if(column.isPresent()){NullableValue value = normalized.getValue();Type type = value.getType();// common type for symbol and valuereturncreateComparisonExtractionResult(normalized.getComparisonOperator(), column.get(), type, value.getValue(), complement);}elseif(expression instanceofCallExpression&& resolution.isCastFunction(((CallExpression) expression).getFunctionHandle())){CallExpression castExpression =(CallExpression) expression;if(!isImplicitCoercion(castExpression)){//// we cannot use non-coercion cast to literal_type on symbol side to build tuple domain//// example which illustrates the problem://// let t be of timestamp type://// and expression be:// cast(t as date) == date_literal//// after dropping cast we end up with://// t == date_literal//// if we build tuple domain based coercion of date_literal to timestamp type we would// end up with tuple domain with just one time point (cast(date_literal as timestamp).// While we need range which maps to single date pointed by date_literal.//returnvisitRowExpression(node, complement);}CallExpression cast =(CallExpression) expression;Type sourceType = cast.getArguments().get(0).getType();// we use saturated floor cast value -> castSourceType to rewrite original expression to new one with one cast peeled off the symbol sideOptional<RowExpression> coercedExpression =coerceComparisonWithRounding(
                    sourceType, cast.getArguments().get(0), normalized.getValue(), normalized.getComparisonOperator());if(coercedExpression.isPresent()){return coercedExpression.get().accept(this, complement);}returnvisitRowExpression(node, complement);}else{returnvisitRowExpression(node, complement);}}returnvisitRowExpression(node, complement);}

  其实就是这几个 if 处理分支都没有处理

like

的情况,

like

对应的函数是

likeFunctionCall

,over~


4、解决方案

那很明显解决方案有两个

  • 改 presto-jdbc 客户端代码,直接把 LikeFilter 换成用 EqualsFilter(简单无脑版)
  • 改 coordinator 的代码,添加处理 like 的用法生成正确的 prefix(硬核版)

最后厂商用的第一个方案,成功解决问题~

(看了新版本的也还是有这个问题)

下次见~

标签: bug 大数据 hadoop

本文转载自: https://blog.csdn.net/weixin_45232029/article/details/135093537
版权归原作者 猫语大数据 所有, 如有侵权,请联系我们删除。

“Presto 查 Hive 元数据这么慢?发现 bug 啦?一个成为贡献者的机会!”的评论:

还没有评论