1. 问题背景
随着业务发展,业务方对于同一张表的生命周期的需求也在不断地发生变化。一开始需要存储一年的数据,经过一段时间之后可能只需要一个月甚至已经逐渐废弃无人问津,但业务方可能并不会去修改这个生命周期。 业务方有时候自己也并不知道生命周期要设多久才合适,这种情况下用户一般会选择偏大的生命周期,但最终使用的时候其实只用了最近几天的数据,以致于造成ClickHouse集群磁盘空间的浪费。 最初建表的用户并不一定是查询数据的用户,通常某一张表建立之后,会有多个用户同时使用这一张表,真正使用该表的用户往往对生命周期是无感的,他们不一定有义务或者并不一定能意识到表生命周期的问题。最初填写生命周期的用户可能在一段时间之后也离职了。
2. 解决方案: 结合历史SQL解析的表生命周期管理方法
2.1 整体思路
从审计日志中筛选出近10天内接受过的所有SQL请求。 利用AST Parser对SQL进行解析,找出每个SQL所涉及到的表及其分区范围。 统计所有的表所涉及到的最大分区范围,将最大分区范围设置为该表的生命周期。
2.2 基于AST Parser解析SQL的分区范围

遍历AST,遍历过程中需要获取到ClickHouse的元数据信息(包括表的分区字段名等),最后在遍历AST的对比表达式节点时,解析出所有与分区字段名相关的过滤条件; 合并过滤条件得出最终的分区范围。
SELECT*FROM t1WHERE(ftime >= '2021-09-01' AND ftime <= '2021-09-10')OR ftime IN ('2021-08-01', '2021-08-02')

2.3 ClickHouse AST Parser的使用
给定SQL语句,找出与该SQL相关的表名。基于它可以实现热点表分析、缓存机制等功能。
String sql = "SELECT t1.id, count(1) as total_count FROM my_db1.table1 t1 LEFT JOIN my_db2.table2 t2 ON t1.id = t2.id GROUP BY t1.id";AstParser astParser = new AstParser();INode ast = (INode) astParser.parse(sql);ReferredTablesDetector referredTablesDetector = new ReferredTablesDetector();// tables should be ["my_db1.table1", "my_db2.table2"] in this caseList<String> tables = referredTablesDetector.searchTables(ast);
给定SQL语句,找出该SQL所涉及到的分区范围。
// we need to implement MetadataService firstMetadataService metadataService = new MetadataService() {@Overridepublic String getPartitionColName(String tableFullName) {// TODO: implement this methodreturn null;}@Overridepublic List<String> getTables() {// TODO: implement this methodreturn null;}};String todayDate = "2022-01-01"; // for parsing UDF like today() and yesterday() in the SQLString targetIP = "127.0.0.1"; // the node to get metadataReferredPartitionsDetector referredPartitionsDetector = new ReferredPartitionsDetector(todayDate, targetIp, metadataService);List<String> partitionRangeList = referredPartitionsDetector.searchTablePartitions(ast);
public interface MetadataService {String getPartitionColName(String tableFullName);List<String> getTables();}
抽取Distributed引擎表的参数信息。ClickHouse的Distributed引擎给予了我们灵活的数据组织方式,但有时我们确实需要提取Distributed引擎表里的相关信息,比如所涉及到的cluster、database和table。单纯使用正则表达式来提取很容易出错,尤其是当CREATE TABLE建表语句中有复杂的注释时,容易抽取出注释的内容。通过AST解析可以比较好地解决这个问题。
String sql = "CREATE TABLE my_db.my_tbl (date Date, name String) Engine = Distributed('my_cluster', 'my_db', 'my_tbl_local', rand())";DistributedTableInfoDetector distributedTableInfoDetector = new DistributedTableInfoDetector();// clusterName is "my_cluster"String clusterName = distributedTableInfoDetector.searchCluster(sql);// tableFullName is "my_db.my_tbl_local"String tableFullName = distributedTableInfoDetector.searchLocalTableFullName(sql);
改写SQL,优化效率。目前只实现了对JOIN操作增加GLOBAL关键字,更多的改写逻辑可以在日后更新,帮助提高SQL效率,在解析层屏蔽掉慢查询SQL。
String sql = "SELECT t1.id, count(1) as total_count FROM my_db1.table1 t1 LEFT JOIN my_db2.table2 t2 ON t1.id = t2.id GROUP BY t1.id";AstParser astParser = new AstParser(false);SelectUnionQuery ast = (SelectUnionQuery) astParser.parse(sql);GlobalJoinAstRewriter globalJoinAstRewriter = new GlobalJoinAstRewriter();String rewrittenSql = globalJoinAstRewriter.visit((INode) ast);// the rewritten SQL should be:// SELECT t1.id, count(1) as total_count FROM my_db1.table1 t1 GLOBAL LEFT JOIN my_db2.table2 t2 ON t1.id = t2.id GROUP BY t1.id
4. 效果表现及后续工作
作者介绍:麦嘉铭,前后就职于阿里云和BIGO,目前在腾讯音乐参与大数据分析平台建设,主要负责Clickhouse和Presto的运维和开发

文章转载自ClickHouse开发者,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




