- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
在我们日常的业务中,数据往往以库表的形式呈现,数据生产和数据消费则分别对应着库表的创建和查询。对于ClickHouse而言,数据的生成是上游库表的同步导入,数据的消费是用户通过诸如BI平台等服务对库表进行查询。理论上,按照业务的需求,每个ClickHouse的表都应该有一个相应的生命周期,假设所有的表都以天粒度为分区,则某些表往往只需要保留一周或一个月的数据,其它有一些表可能需要保留三个月或半年,可见不同的表生命周期应该是不一样的。但问题在于如何为每个表设定合适的生命周期?过长的生命周期会造成存储资源的浪费,占满ClickHouse集群的磁盘空间,而过短的生命周期可能不能满足业务方的需求,导致查不出需要的数据。
我们过往的做法是,在ClickHouse入库前,让用户填写生命周期。但这样的做法并未能从根本上解决问题,究其原因总结下来有以下几点:
综合上述的几个因素,我们需要一套自动探测ClickHouse库表生命周期的解决方案,降低生命周期的人工干预成本,做到更精确地评估库表生命周期,从而进一步提高ClickHouse集群磁盘空间利用率,降低查询响应时延(减少不必要的数据扫描时间)。
为了解决这一问题,我们从ClickHouse的审计日志中对历史SQL进行分析,得出一段时间内每个表在查询时所涉及到的最大分区范围(SQL所覆盖的分区字段的天数),进而根据分区范围作为表的生命周期。
整体思路可以拆解为以下几步:
上述的几个步骤中,最为关键的是第二步,需要根据SQL解析出所涉及到的分区范围。
为了解析SQL的分区范围,在实现层面首先要将SQL解析成AST,随后再对AST进行遍历找到所涉及到的分区范围,如图1所示:
图1 解析SQL分区范围的过程
其中,遍历AST时有以下几个关键的步骤需要实现:
举例来说,下面的这一段SQL,在经过第二步之后会得出如图2所示左右两边的两个分区范围,进一步合并之后得到一个完整的分区范围。需要注意的是,合并的过程需要考虑到到底是要取并集还是交集。
SELECT
*
FROM t1
WHERE
(ftime >= '2021-09-01' AND ftime <= '2021-09-10')
OR ftime IN ('2021-08-01', '2021-08-02')
图2 分区范围合并
所涉及到的AST解析的代码已经抽取成ClickHouse AST Parser,有需要的同学可以参考使用 https://github.com/JiamingMai/clickhouse-ast-parser
ClickHouse AST Parser不仅仅是一个SQL语法的解析器,而是一个提供了AST 相关搜索功能的工具,主要的应用场景在于将SQL语句转换为 AST,并进一步利用解析后的结果。
目前ClickHouse AST Parser实现了以下几种场景:
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);
其中,AstParser可以解析SQL,得到对应的AST。ReferredTablesDetector用于检测SQL中所涉及到的所有表
// we need to implement MetadataService first
MetadataService metadataService = new MetadataService() {
@Override
public String getPartitionColName(String tableFullName) {
// TODO: implement this method
return null;
}
@Override
public List<String> getTables() {
// TODO: implement this method
return null;
}
};
String todayDate = "2022-01-01"; // for parsing UDF like today() and yesterday() in the SQL
String targetIP = "127.0.0.1"; // the node to get metadata
ReferredPartitionsDetector referredPartitionsDetector = new ReferredPartitionsDetector(todayDate, targetIp, metadataService);
List<String> partitionRangeList = referredPartitionsDetector.searchTablePartitions(ast);
其中,ReferredPartitionsDetector用于检测SQL中所涉及到的所有表及其分区范围,使用时需要传入一个MetadataService的实现类,用于获取ClickHouse的元数据。
public interface MetadataService {
String getPartitionColName(String tableFullName);
List<String> getTables();
}
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);
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
各个使用的方法也可以测试用例中找到。
通过本文方法对ClickHouse库表生命周期进行梳理后,我们发现了大量的表设置了过长的生命周期,最终集群内有大概1/3的冗余数据可以清理,大幅度减少了整体的磁盘空间占用率,降低了查询时延。目前对于较为复杂的SQL还没有办法解析出分区范围,还需要进一步完善,也欢迎各位同学一起参与完善。也可以基于本文方法将估算得出的生命周期推送给业务方,让业务方确认,询问生命周期是否合理。
作者介绍:麦嘉铭,前后就职于阿里云和BIGO,目前在腾讯音乐参与大数据分析平台建设,主要负责Clickhouse和Presto的运维和开发
我一直在使用 AJAX 从我正在创建的网络服务中解析 JSON 数组时遇到问题。我的前端是一个简单的 ajax 和 jquery 组合,用于显示从我正在创建的网络服务返回的结果。 尽管知道我的数据库查
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
我在尝试运行 Android 应用程序时遇到问题并收到以下错误 java.lang.NoClassDefFoundError: com.parse.Parse 当我尝试运行该应用时。 最佳答案 在这
有什么办法可以防止etree在解析HTML内容时解析HTML实体吗? html = etree.HTML('&') html.find('.//body').text 这给了我 '&' 但我想
我有一个有点疯狂的例子,但对于那些 JavaScript 函数作用域专家来说,它看起来是一个很好的练习: (function (global) { // our module number one
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 8 年前。 Improve th
我需要编写一个脚本来获取链接并解析链接页面的 HTML 以提取标题和其他一些数据,例如可能是简短的描述,就像您链接到 Facebook 上的内容一样。 当用户向站点添加链接时将调用它,因此在客户端启动
在 VS Code 中本地开发时,包解析为 C:/Users//AppData/Local/Microsoft/TypeScript/3.5/node_modules/@types//index而不是
我在将 json 从 php 解析为 javascript 时遇到问题 这是我的示例代码: //function MethodAjax = function (wsFile, param) {
我在将 json 从 php 解析为 javascript 时遇到问题 这是我的示例代码: //function MethodAjax = function (wsFile, param) {
我被赋予了将一种语言“翻译”成另一种语言的工作。对于使用正则表达式的简单逐行方法来说,源代码过于灵活(复杂)。我在哪里可以了解更多关于词法分析和解析器的信息? 最佳答案 如果你想对这个主题产生“情绪化
您好,我在解析此文本时遇到问题 { { { {[system1];1;1;0.612509325}; {[system2];1;
我正在为 adobe after effects 在 extendscript 中编写一些代码,最终变成了 javascript。 我有一个数组,我想只搜索单词“assemble”并返回整个 jc3_
我有这段代码: $(document).ready(function() { // }); 问题:FB_RequireFeatures block 外部的代码先于其内部的代码执行。因此 who
背景: netcore项目中有些服务是在通过中间件来通信的,比如orleans组件。它里面服务和客户端会指定网关和端口,我们只需要开放客户端给外界,服务端关闭端口。相当于去掉host,这样省掉了些
1.首先贴上我试验成功的代码 复制代码 代码如下: protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
什么是 XML? XML 指可扩展标记语言(eXtensible Markup Language),标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。 你可以通过本站学习 X
【PHP代码】 复制代码 代码如下: $stmt = mssql_init('P__Global_Test', $conn) or die("initialize sto
在SQL查询分析器执行以下代码就可以了。 复制代码代码如下: declare @t varchar(255),@c varchar(255) declare table_cursor curs
前言 最近练习了一些前端算法题,现在做个总结,以下题目都是个人写法,并不是标准答案,如有错误欢迎指出,有对某道题有新的想法的友友也可以在评论区发表想法,互相学习🤭 题目 题目一: 二维数组中的
我是一名优秀的程序员,十分优秀!