<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ClickHouse 剖析 | 結(jié)合 SQL 解析的 ClickHouse 表生命周期管理

          共 1480字,需瀏覽 3分鐘

           ·

          2022-02-03 15:31

          1. 問題背景在我們?nèi)粘5臉I(yè)務(wù)中,數(shù)據(jù)往往以庫表的形式呈現(xiàn),數(shù)據(jù)生產(chǎn)和數(shù)據(jù)消費(fèi)則分別對應(yīng)著庫表的創(chuàng)建和查詢。對于ClickHouse而言,數(shù)據(jù)的生成是上游庫表的同步導(dǎo)入,數(shù)據(jù)的消費(fèi)是用戶通過諸如BI平臺等服務(wù)對庫表進(jìn)行查詢。理論上,按照業(yè)務(wù)的需求,每個(gè)ClickHouse的表都應(yīng)該有一個(gè)相應(yīng)的生命周期,假設(shè)所有的表都以天粒度為分區(qū),則某些表往往只需要保留一周或一個(gè)月的數(shù)據(jù),其它有一些表可能需要保留三個(gè)月或半年,可見不同的表生命周期應(yīng)該是不一樣的。但問題在于如何為每個(gè)表設(shè)定合適的生命周期?過長的生命周期會造成存儲資源的浪費(fèi),占滿ClickHouse集群的磁盤空間,而過短的生命周期可能不能滿足業(yè)務(wù)方的需求,導(dǎo)致查不出需要的數(shù)據(jù)。我們過往的做法是,在ClickHouse入庫前,讓用戶填寫生命周期。但這樣的做法并未能從根本上解決問題,究其原因總結(jié)下來有以下幾點(diǎn):

          • 隨著業(yè)務(wù)發(fā)展,業(yè)務(wù)方對于同一張表的生命周期的需求也在不斷地發(fā)生變化。一開始需要存儲一年的數(shù)據(jù),經(jīng)過一段時(shí)間之后可能只需要一個(gè)月甚至已經(jīng)逐漸廢棄無人問津,但業(yè)務(wù)方可能并不會去修改這個(gè)生命周期。
          • ?業(yè)務(wù)方有時(shí)候自己也并不知道生命周期要設(shè)多久才合適,這種情況下用戶一般會選擇偏大的生命周期,但最終使用的時(shí)候其實(shí)只用了最近幾天的數(shù)據(jù),以致于造成ClickHouse集群磁盤空間的浪費(fèi)。
          • ?最初建表的用戶并不一定是查詢數(shù)據(jù)的用戶,通常某一張表建立之后,會有多個(gè)用戶同時(shí)使用這一張表,真正使用該表的用戶往往對生命周期是無感的,他們不一定有義務(wù)或者并不一定能意識到表生命周期的問題。最初填寫生命周期的用戶可能在一段時(shí)間之后也離職了。
          綜合上述的幾個(gè)因素,我們需要一套自動探測ClickHouse庫表生命周期的解決方案,降低生命周期的人工干預(yù)成本,做到更精確地評估庫表生命周期,從而進(jìn)一步提高ClickHouse集群磁盤空間利用率,降低查詢響應(yīng)時(shí)延(減少不必要的數(shù)據(jù)掃描時(shí)間)。為了解決這一問題,我們從ClickHouse的審計(jì)日志中對歷史SQL進(jìn)行分析,得出一段時(shí)間內(nèi)每個(gè)表在查詢時(shí)所涉及到的最大分區(qū)范圍(SQL所覆蓋的分區(qū)字段的天數(shù)),進(jìn)而根據(jù)分區(qū)范圍作為表的生命周期。
          2. 解決方案:?結(jié)合歷史SQL解析的表生命周期管理方法

          2.1 整體思路

          整體思路可以拆解為以下幾步:
          • 從審計(jì)日志中篩選出近10天內(nèi)接受過的所有SQL請求。
          • ?利用AST Parser對SQL進(jìn)行解析,找出每個(gè)SQL所涉及到的表及其分區(qū)范圍。
          • ?統(tǒng)計(jì)所有的表所涉及到的最大分區(qū)范圍,將最大分區(qū)范圍設(shè)置為該表的生命周期。
          上述的幾個(gè)步驟中,最為關(guān)鍵的是第二步,需要根據(jù)SQL解析出所涉及到的分區(qū)范圍。

          2.2 基于AST Parser解析SQL的分區(qū)范圍

          為了解析SQL的分區(qū)范圍,在實(shí)現(xiàn)層面首先要將SQL解析成AST,隨后再對AST進(jìn)行遍歷找到所涉及到的分區(qū)范圍,如圖1所示:29867d6e8b6a6e9b46c51a9521decd76.webp?
          1 解析SQL分區(qū)范圍的過程其中,遍歷AST時(shí)有以下幾個(gè)關(guān)鍵的步驟需要實(shí)現(xiàn):
          • 遍歷AST,遍歷過程中需要獲取到ClickHouse的元數(shù)據(jù)信息(包括表的分區(qū)字段名等),最后在遍歷AST的對比表達(dá)式節(jié)點(diǎn)時(shí),解析出所有與分區(qū)字段名相關(guān)的過濾條件;
          • 合并過濾條件得出最終的分區(qū)范圍。
          舉例來說,下面的這一段SQL,在經(jīng)過第二步之后會得出如圖2所示左右兩邊的兩個(gè)分區(qū)范圍,進(jìn)一步合并之后得到一個(gè)完整的分區(qū)范圍。需要注意的是,合并的過程需要考慮到到底是要取并集還是交集。
          SELECT
          *
          FROM t1
          WHERE
          (ftime >= '2021-09-01' AND ftime <= '2021-09-10')
          OR ftime IN ('2021-08-01', '2021-08-02')


          f3f70d1903e6a1a1b8311a58ee2ba1b8.webp?
          2 分區(qū)范圍合并所涉及到的AST解析的代碼已經(jīng)抽取成ClickHouse AST Parser,有需要的同學(xué)可以參考使用 https://github.com/JiamingMai/clickhouse-ast-parser?

          2.3 ClickHouse AST Parser的使用?

          ClickHouse AST Parser不僅僅是一個(gè)SQL語法的解析器,而是一個(gè)提供了AST 相關(guān)搜索功能的工具,主要的應(yīng)用場景在于將SQL語句轉(zhuǎn)換為 AST,并進(jìn)一步利用解析后的結(jié)果。目前ClickHouse AST Parser實(shí)現(xiàn)了以下幾種場景:
          • 給定SQL語句,找出與該SQL相關(guān)的表名?;谒梢詫?shí)現(xiàn)熱點(diǎn)表分析、緩存機(jī)制等功能。
          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 tables = referredTablesDetector.searchTables(ast);
          其中,AstParser可以解析SQL,得到對應(yīng)的AST。ReferredTablesDetector用于檢測SQL中所涉及到的所有表
          • 給定SQL語句,找出該SQL所涉及到的分區(qū)范圍。
          // 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中所涉及到的所有表及其分區(qū)范圍,使用時(shí)需要傳入一個(gè)MetadataService的實(shí)現(xiàn)類,用于獲取ClickHouse的元數(shù)據(jù)。
          public interface MetadataService {
          String getPartitionColName(String tableFullName);
          List getTables();
          }
          • 抽取Distributed引擎表的參數(shù)信息。ClickHouse的Distributed引擎給予了我們靈活的數(shù)據(jù)組織方式,但有時(shí)我們確實(shí)需要提取Distributed引擎表里的相關(guān)信息,比如所涉及到的cluster、database和table。單純使用正則表達(dá)式來提取很容易出錯(cuò),尤其是當(dāng)CREATE TABLE建表語句中有復(fù)雜的注釋時(shí),容易抽取出注釋的內(nèi)容。通過AST解析可以比較好地解決這個(gè)問題。
          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,優(yōu)化效率。目前只實(shí)現(xiàn)了對JOIN操作增加GLOBAL關(guān)鍵字,更多的改寫邏輯可以在日后更新,幫助提高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
          各個(gè)使用的方法也可以測試用例中找到。
          4. 效果表現(xiàn)及后續(xù)工作通過本文方法對ClickHouse庫表生命周期進(jìn)行梳理后,我們發(fā)現(xiàn)了大量的表設(shè)置了過長的生命周期,最終集群內(nèi)有大概1/3的冗余數(shù)據(jù)可以清理,大幅度減少了整體的磁盤空間占用率,降低了查詢時(shí)延。目前對于較為復(fù)雜的SQL還沒有辦法解析出分區(qū)范圍,還需要進(jìn)一步完善,也歡迎各位同學(xué)一起參與完善。也可以基于本文方法將估算得出的生命周期推送給業(yè)務(wù)方,讓業(yè)務(wù)方確認(rèn),詢問生命周期是否合理。作者介紹:麥嘉銘,前后就職于阿里云和BIGO,目前在騰訊音樂參與大數(shù)據(jù)分析平臺建設(shè),主要負(fù)責(zé)Clickhouse和Presto的運(yùn)維和開發(fā)

          作者介紹:麥嘉銘,前后就職于阿里云和BIGO,目前在騰訊音樂參與大數(shù)據(jù)分析平臺建設(shè),主要負(fù)責(zé)Clickhouse和Presto的運(yùn)維和開發(fā)



          瀏覽 112
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  美女被日的网站 | 日韩免费A片 | 天天综合天天做 | www.大香蕉伊人 | 久久综合婷婷国产五区 |