天天看點

Grafana釋出2.0版:解析ClickHouse查詢方式

作者:JAVA我要發大财
Grafana釋出2.0版:解析ClickHouse查詢方式

早在 2022 年 5 月,我們就釋出了Grafana 的 第一方 ClickHouse 插件 ,由 Grafana 與 ClickHouse 合作開發。 作為我們對維護和改進此插件的承諾的一部分,我們很高興地宣布釋出 2.0 版。

這個主要版本對我們解析 ClickHouse 查詢的方式進行了根本性更改,這将有助于解決我們的使用者遇到的關于 ad-hoc 過濾器的一組常見問題。 我們還添加了對使用 HTTP 作為傳輸協定的支援,并增強了對 ClickHouse 資料類型(包括 JSON)的支援。

處理過濾器的更好方法

當我們最初開發插件時,我們想使用 Grafana 的最新插件架構。 這可確定我們支援具有伺服器端依賴性的警報等功能。 通過這個新架構,我們支援了 Ad-hoc 過濾器的初始實作。 這個強大的 Grafana 功能 僅支援最流行的資料源 ,允許使用者通過選擇列、運算符和值來過濾所有可視化。 雖然我們最初的産品在很大程度上發揮了作用,但它依賴于使用用戶端 AST 實作來解析 SQL 查詢。 即使對于有經驗的開發人員來說,這也是一個複雜的問題——尤其是因為 ClickHouse SQL 有幾個擴充來幫助使用者建構分析查詢。 這個實作随後是 幾個問題 ,主要與使用 Grafana 變量、模闆 和子查詢有關。

我們沒有在 AST 解析器上投入更多時間,而是聯系了一個已經以最佳方式解決了這個問題的團隊:我們在 ClickHouse 核心開發團隊中的朋友。 經過一番 簡短的讨論 ,誕生 additional_table_filters 于 22.7 。 這允許任何過濾器作為設定的一部分與查詢一起發送。 在解析查詢時,ClickHouse 可以将這些過濾器注入到适當的子句中。

随意針對 play.clickhouse.com 測試以下任何示例。 資料庫中的任何表都可用 blogs ,是以 FROM <table> 應相應調整子句,即 FROM blogs <table> .

考慮來自英國房地産價格資料集 的簡單查詢 。

SELECT     toStartOfYear(date) AS time,     town,     round(avg(price)) AS price FROM uk_price_paid WHERE town IN (     SELECT town     FROM uk_price_paid     WHERE town != 'GATWICK'     GROUP BY town     ORDER BY avg(price) DESC     LIMIT 10 ) GROUP BY     time,     town ORDER BY time ASC
           

這個簡單的查詢告訴我們随着時間的推移,英國最昂貴的 10 個城鎮的每個城鎮的平均價格。 請注意,我們排除了蓋特威克機場,因為它代表了異常情況。 這自然會在 Grafana 中呈現為區域圖或折線圖。 這裡的邏輯過濾器可能是供使用者按城鎮過濾。 確定将其注入查詢的正确部分是一項挑戰。 基于 JavaScript 的解決方案雖然可能,但需要涵蓋所有可能的邊緣情況。 例如,在這裡,我們将最好将城鎮過濾器注入 IN 子句并與 town != 'Gatwick' .

Grafana釋出2.0版:解析ClickHouse查詢方式

注意:由于使用權限不足 additional_table_filters ,如果使用 play.clickhouse.com 作為 ClickHouse 資料源,Adhoc 過濾器将不起作用。 有關詳細資訊,請參閱下面的“注意更改”。

添加一個 ad hoc 過濾器并選擇 town=London ,會導緻 Grafana 發送以下查詢:

SELECT toStartOfYear(date) AS time, town, round(avg(price)) AS price FROM uk_price_paid WHERE town IN (SELECT town                FROM uk_price_paid                WHERE uk_price_paid.town != 'GATWICK'                GROUP BY town                ORDER BY avg(price) DESC                LIMIT 10) GROUP BY time, town ORDER BY time SETTINGS additional_table_filters = {'uk_price_paid' : 'town = \'LONDON\' '}
           

反過來,ClickHouse 會努力确定需要在何處添加詞字句 - 請注意如何将過濾器指定為鍵等于表名的映射 uk_price_paid 。

Grafana釋出2.0版:解析ClickHouse查詢方式

添加更多過濾器是微不足道的。 在下面的示例中,我們按地區過濾,重點關注哈克尼。

SELECT toStartOfYear(date) AS time, town, round(avg(price)) AS price
FROM uk_price_paid
WHERE town IN (SELECT town
FROM uk_price_paid
WHERE uk_price_paid.town != 'GATWICK'
AND uk_price_paid.town == 'LONDON'
GROUP BY town
ORDER BY avg(price) DESC
LIMIT 10)
GROUP BY time, town
ORDER BY time settings additional_table_filters = {'uk_price_paid' : 'town = \'LONDON\' AND district = \'HACKNEY\' '}           
Grafana釋出2.0版:解析ClickHouse查詢方式

為了獲得正确的結果,ClickHouse 需要確定将過濾器注入到頂級 WHERE 子句中。

雖然是為了響應 Grafana 要求而添加的,但此功能可用于更廣泛的工具來利用和改進其産品。 請讓我們知道這是否有用!

對 HTTP 的需求

在底層,Grafana 插件使用 clickhouse-go 用戶端向 ClickHouse 發送查詢。 從曆史上看,這個用戶端使用原生格式通過 ClickHouse 二進制協定進行通信。 這代表了最有效的通信方式,并出于性能原因而選擇。 這對于 INSERT 繁重的用例是有意義的,但對于 Grafana 中常用的聚合查詢則不太有意義。 我們的使用者經常需要通過 HTTP 傳輸流量,以允許打開負載均衡器或使用代了解決方案,例如 ch-proxy 。

由于 社群貢獻 ,clickhouse-go 驅動程式添加了對 HTTP 原生格式的支援。 此功能現在在 Grafana 中公開,可以在資料源級别進行選擇。

Grafana釋出2.0版:解析ClickHouse查詢方式

對于那些嘗試此功能的人,請記住 HTTP 使用與 Native 不同的端口 - 預設情況下 HTTP/HTTPS 使用 8123/8443。 最後,我們将之前使用相同值的連接配接逾時和查詢逾時分開。

歡迎半結構化資料

ClickHouse 22.6 添加 了對 JSON 作為資料類型的支援。 JSON Object 類型在處理複雜的嵌套結構時具有優勢,這些結構可能會發生變化。 該類型在插入過程中自動從結構中推斷列,并将這些列合并到現有的表模式中。 列将根據需要建立,允許使用者在不維護模式的情況下處理半結構化資料。 這種能力有很多用途,尤其是簡化了 ClickHouse 作為日志存儲引擎的使用。

在它添加到 ClickHouse 後不久,對 JSON 的支援被添加到 clickhouse-go 用戶端。 這個支援現在已經在我們的 Grafana 插件 v2.0 中實作了。

JSON 葉節點現在可以像任何其他等效原始類型的列一樣用于圖表。 表示 JSON 對象或 JSON 對象清單的列的處理方式分别類似于 ClickHouse 中的 Tuple 和 Nested 類型。 在 Grafana 中,這意味着将它們呈現為 JSON 字元串。 雖然這與 Grafana 圖表不相容,但 JSON 對象可以顯示在 探索視圖 或 日志面闆 中- 對于在 ClickHouse 中存儲結構化日志的人來說,這是一個有用的補充。

為了測試此功能,使用者可以使用 此處 描述的日志資料集的子集。 這裡我們使用一個簡單的模式:

SET allow_experimental_object_type=1;

CREATE TABLE http_logs
(
`message` JSON,
`timestamp` DateTime
)
ENGINE = MergeTree()
ORDER BY timestamp;

INSERT INTO http_logs (timestamp, message) SELECT
`@timestamp` AS timestamp,
concat('{"status":', toString(status), ', "size":', toString(size), ', "clientip": "', toString(clientip), '", "request": ', toJSONString(request), '}') AS message
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/http/documents-01.ndjson.gz', 'JSONEachRow')           

我們從公開可用的 s3 存儲桶中插入上述資料 - 随意嘗試或使用 play.clickhouse.com ! 請注意,我們確定我們有一個 timestamp 與 JSON 分開的時間列。 由于移動了消息字段下的所有列,每行如下所示。

{"message":{"clientip":"40.135.0.0","request":{"method":"GET","path":"\/images\/hm_bg.jpg","version":"HTTP\/1.0"},"size":24736,"status":200},"timestamp":"1998-04-30 19:30:17"}
           

這些 JSON 日志現在可以在浏覽視圖或日志面闆中呈現。 我們從響應狀态中推斷出一個“級别”字段,它控制顔色渲染。 我們建議對所有查詢施加限制以避免逾時,因為 Grafana 需要将完整的結果集轉換為幀。

探索視圖的直方圖渲染基于傳回的結果,盡管我們計劃在以後的版本中計算 類似于 Loki 和 Elasticsearch 的全範圍日志分布。 為了確定使用者不需要在 Grafana 中調整時間範圍,我們使用資料集的最大日期将此資料集轉移到目前時間範圍。

SELECT     now() - (toDateTime('1998-05-08 13:44:46') - timestamp) AS log_time,     multiIf(message.status > 500, 'critical', message.status > 400, 'error', message.status > 300, 'warning', 'info') AS level,     message.request.method AS method,     message.status AS status,     message.size AS size,     message.request AS log FROM http_logs ORDER BY timestamp DESC LIMIT 10000
           

Grafana釋出2.0版:解析ClickHouse查詢方式

日志面闆和浏覽視圖都對字段名稱敏感。 要使用 Explore 的日志可視化模式(包括直方圖)顯示結果,行必須包含“log_time”字段并按“log_time”字段排序。

JSON 支援需要重寫我們在插件中處理 ClickHouse 類型的方式。 覆寫範圍現在應該是全面的,支援所有類型,包括我們需要作為 JSON 工作的一部分支援的複雜類型,例如 Tuple 和 Nested。

變量的變化

從曆史上看,我們依靠 AST 自動優化 WHERE 子句中的 IN 條件,以應對使用者為變量選擇“全部”的情況。 該插件将有效地從任何 WHERE 子句中删除對變量的使用,進而避免發送條件的需要。 雖然這是一個不錯的功能,但它依賴于 AST,并假設完全了解 ClickHouse SQL 方言。 在 2.0 版中,我們将此優化推遲給應該使用宏 __conditionalAll 包裝 IN 子句的使用者,該子句使用變量。

例如,假設我們正在可視化倫敦的房價,并希望允許按使用者選擇的地區進行過濾,即通過 district IN (${district:singlequote}) :

Grafana釋出2.0版:解析ClickHouse查詢方式

我們的變量過濾器看起來像這樣——注意選擇“All”選項的能力:

Grafana釋出2.0版:解析ClickHouse查詢方式

如果選擇全部,則查詢變為:

SELECT     toStartOfYear(date) AS time,     district,     round(avg(price)) AS price FROM uk_price_paid WHERE (district IN (     SELECT district     FROM uk_price_paid     WHERE town = 'LONDON'     GROUP BY district     ORDER BY avg(price) DESC     LIMIT 10 )) AND (district IN ('TOWER HAMLETS', 'HACKNEY', 'NEWHAM', 'CITY OF LONDON', 'WALTHAM FOREST', 'REDBRIDGE', 'BARKING AND DAGENHAM', 'HAVERING', 'HARINGEY', 'EPPING FOREST', 'ISLINGTON', 'CAMDEN', 'CITY OF WESTMINSTER', 'BARNET', 'HARROW', 'HILLINGDON', 'ENFIELD', 'EALING', 'HOUNSLOW', 'HAMMERSMITH AND FULHAM', 'LEWISHAM', 'BRENT', 'WANDSWORTH', 'SOUTHWARK', 'LAMBETH', 'GREENWICH', 'KENSINGTON AND CHELSEA', 'MERTON', 'BROMLEY', 'RICHMOND UPON THAMES', 'CROYDON', 'BEXLEY', 'KINGSTON UPON THAMES', 'HARLOW', 'SUTTON', 'CITY OF BRISTOL', 'MALVERN HILLS', 'THURROCK', 'RHONDDA CYNON TAFF')) GROUP BY     time,     district ORDER BY time ASC
           

雖然對于隻有幾個值的變量(例如倫敦的地區)來說很好,但對于較長的清單來說,它會成為性能開銷。 為了優化,使用者可以在子句周圍加上一個 __conditionalAll 例如

SELECT toStartOfYear(date) AS time,        town,        round(avg(price))   AS price FROM uk_price_paid WHERE town IN (     SELECT town     FROM uk_price_paid     WHERE town != 'GATWICK' AND $__conditionalAll(district IN (${district:singlequote}), $district)     GROUP BY town     ORDER BY avg(price) DESC     LIMIT 10 ) GROUP BY time, town ORDER BY time ASC
           

在選擇“全部”時,地區限制被簡單地替換為一個 1=1 條件。

SELECT     toStartOfYear(date) AS time,     district,     round(avg(price)) AS price FROM uk_price_paid WHERE (district IN (     SELECT district     FROM uk_price_paid     WHERE uk_price_paid.town = 'LONDON'     GROUP BY district     ORDER BY avg(price) DESC     LIMIT 10 )) AND (1 = 1) GROUP BY     time,     district ORDER BY time ASC
           

注意變化

除了上述的大量新增内容外,我們還修複了 此版本的 幾個錯誤。 AST 的删除特别意味着此版本确實有一些重大更改,除了需要手動優化 IN 過濾器之外,使用者應該注意:

  • 新的 Adhoc 過濾器實作依賴于該 additional_table_filters 功能,是以 ClickHouse 22.7。 舊版本的 ClickHouse 不會填充過濾器。 如果您無法遷移到大于或等于此版本的 ClickHouse 版本,請不要更新您的插件。
  • additional_table_filters 與查詢本身一起在 SETTINGS 中傳遞。 除非readonly=2 ,否則隻讀使用者不允許這樣做 。 這并不理想,我們不建議為 ClickHouse 的公共執行個體設定此設定。 我們認識到這裡需要改進。 敬請期待 。

由于上述原因,Adhoc 過濾器不适用于 play.clickhouse.com。

對于希望貢獻或關注最新問題和改進的使用者,ClickHouse 官方插件是 托管在 GitHub 上并在 TypeScript 和 Go 中實作 的開源項目。 我們始終重視您的回報并鼓勵使用者提出問題,以便我們繼續改進插件。

繼續閱讀