文章目录
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
(硬核版)
最后厂商用的第一个方案,成功解决问题~
(看了新版本的也还是有这个问题)
下次见~
版权归原作者 猫语大数据 所有, 如有侵权,请联系我们删除。