超詳細 Clickhouse 負載過高問題快速排查思路教程
機器配置
Clickhouse 配置記憶體大小: 250G
Zookeeper 配置記憶體大小:24G
發現問題
企業相關開發人員通過Grafana監控Clickhouse指標觀察到,從12點左右出現了大量的碎片寫入,從而引起了相關指標的快速上升。檢視雲智慧的數字化運維資料平臺 DODB (以下簡稱DODB)概覽頁也未查看出異常出現源頭,而當檢視伺服器負載情況時,已經上升至900+。
開始排查
-
檢查是否為執行 SQL 太多
當遇到此類問題時,普遍情況下均會猜測是 SQL 執行太多導致。因此,團隊開發人員檢查了 Clickhouse 的 Process表,以此判斷執行中的 SQL 是否過多。與此同時與演算法部門的同事進行溝通,最終確認是通過 DODB 寫入資料的。隨後又查詢了一下執行中的 SOL,發現兩臺機器上執行中的 SOL 不太均衡。
-
查詢 Clickhouse 日誌
當檢視 Clickhouse 日誌時,看到有很多 warn 的日誌,但並沒有出現有價值的日誌。
-
查詢 Zookeeper 日誌
當檢視 Zookeeper 日誌時,發現數據同步有比較大的延遲,超過了1-2s。
排查同步資料延遲是因為需要判斷是 load 值上來導致延遲還是因為延遲導致的 load 值上來。可能是一個大查詢導致 load 值上來後,引起 Zookeeper 出現問題,從而導致資料無法同步出現數據堆積的情況。因此,在此時查看了監控的讀寫情況,是由於load值上來後導致寫入變慢。
-
SQL 語句查詢
在 Clickhouse 中,所有被執行 Query 均會記錄到system.query_log
表中。因此可通過該表監控叢集的查詢情況。以下列舉幾種用於監控的常用SQL。
最近查詢
SELECT
event_time,
user,
query_id AS query,
read_rows,
read_bytes,
result_rows,
result_bytes,
memory_usage,
exception
FROM clusterAllReplicas('cluster_name', system, query_log)
WHERE (event_date = today()) AND (event_time >= (now() - 60)) AND (is_initial_query = 1) AND (query NOT LIKE 'INSERT INTO%')
ORDER BY event_time DESC
LIMIT 100
慢查詢
SELECT
event_time,
user,
query_id AS query,
read_rows,
read_bytes,
result_rows,
result_bytes,
memory_usage,
exception
FROM clusterAllReplicas('cluster_name', system, query_log)
WHERE (event_date = yesterday()) AND query_duration_ms > 30000 AND (is_initial_query = 1) AND (query NOT LIKE 'INSERT INTO%')
ORDER BY query_duration_ms desc
LIMIT 100
Top10大表
SELECT
database,
table,
sum(bytes_on_disk) AS bytes_on_disk
FROM clusterAllReplicas('cluster_name', system, parts)
WHERE active AND (database != 'system')
GROUP BY
database,
table
ORDER BY bytes_on_disk DESC
LIMIT 10
Top10查詢使用者
SELECT
user,
count(1) AS query_times,
sum(read_bytes) AS query_bytes,
sum(read_rows) AS query_rows
FROM clusterAllReplicas('cluster_name', system, query_log)
WHERE (event_date = yesterday()) AND (is_initial_query = 1) AND (query NOT LIKE 'INSERT INTO%')
GROUP BY user
ORDER BY query_times DESC
LIMIT 10
使用慢查詢的 SQL 查詢
查詢結果出來,檢視所執行 SQL 具體情況,發現很多都是“異常容器”的。
下圖為作戰地圖所查大屏表,且為 left JOIN 表。由於 life 的 JOIN 在網路及其他因素條件有要求,因此比剛才查詢更消耗資源。隨後通過時間段查詢判斷出現異常時間是否匹配,對比發現時間上有些差異。
查詢 SQL 查詢次數,判斷哪次查詢 時間 最長以及查詢的平均時長。
select left(query, 100) as sql, count() as queryNum, sum(query_duration_ms) as totalTime, totalTime/queryNum as avgTime from system.query_log ql where event_time > toDateTime("2022-09-23 12:00:00") and event_time < toDateTime("2022-09-23 17:00:00") group by sql order by queryNum desc limit 10
通過該 SQL 查詢,發現 5 個小時 SQL 查詢了將近 4 萬次,且同類型查詢執行次數均較高,平均執行時間也比較長。此外,也有許多 5 個小時執行兩三千次的 SQL 查詢,該類查詢平均每次耗時均在二三十秒。
查詢不包含insert into語句的5個小時查詢次數超過1000次的 SQL
select * from (select LEFT(query, 100) as sql, count() as quneryNum, sum(query_duration_ms) as totalTime, totalTime/queryNum as avgTime from system.query_log ql where event_time > toDateTime('2022-09-23 12:00:00') and event_time < toDateTime('2022-09-23 17:00:00') and query not like '%INSERT INTO%' group by sql order by avgTime desc) where queryNum > 1000 limit 50
由於上述 SQL 均做了擷取,故需根據所查詢 SQL 進一步匹配 SQL。
select query from system.query_log where event_time > toDateTime('2022-09-23 12:00:00') and event_time < toDateTime('2022-09-23 17:00:00') and query like '%需要匹配的sql查詢%' limit 5;
使用第三條 SQL 用 queryNum 排序,查詢出執行次數多且平均耗時比較大的 SQL,隨後定位 SQL。以第二條記錄為例:5個小時查詢了 38000+次,耗時12s,每分鐘查 120+次。
使用第三條 SQL ,將 時間 範圍改到上午8點到上午12查詢。 查詢結果對比發現,執行耗時在上午時均很短,但到中午12點以後就變的非常慢了。
由於之前查詢發現有部分 left JOIN 的查詢,故查詢了“異常容器”的left JOIN 的查詢,,隨後將讀取條數為0的排除掉。
select * from (select LEFT(query, 100) as sql, count() as quneryNum, sum(query_duration_ms) as totalTime, totalTime/queryNum as avgTime from system.query_log ql where sql like '%異常容器%' and read_rows != 0 and event_time > toDateTime('2022-09-23 12:00:00') and event_time < toDateTime('2022-09-23 17:00:00') and query not like '%INSERT INTO%' group by sql order by queryNum desc)
根據小時 聚合 每個小時/分鐘段查詢次數耗時
--按照每小時聚合
select toHour(event_time) as t, count() as queryNum, sum(query_duration_ms) as totalTime, totalTime/queryNum as avgTime from system.query_log ql where event_time > toDateTime('2022-09-23 08:00:00') and event_time < toDateTime('2022-09-23 17:00:00') and query not like '%INSERT INTO%' and query like '%異常容器%' and read_rows != 0 group by t limit 50
--按照每分鐘聚合
select toMinute(event_time) as t, count() as queryNum, sum(query_duration_ms) as totalTime, totalTime/queryNum as avgTime from system.query_log ql where event_time > toDateTime('2022-09-23 12:00:00') and event_time < toDateTime('2022-09-23 13:00:00') and query not like '%INSERT INTO%' and query like '%異常容器%' and read_rows != 0 group by t limit 50
查詢結果如下:10點開始有,12 點查詢響應時間翻了10倍。
從32s開始,響應時間變為24s多,後面的時間也逐漸變長。
查詢 left JOIN 的查詢個數。
select * from (select LEFT(query, 100) as sql, count() as quneryNum, sum(query_duration_ms) as totalTime, totalTime/queryNum as avgTime from system.query_log ql where query like '% JOIN%' and read_rows != 0 and event_time > toDateTime('2022-09-23 12:00:00') and event_time < toDateTime('2022-09-23 21:00:00') and query not like '%INSERT INTO%' group by sql order by queryNum desc)
發現有問題的表時,查詢該表結構。
show create table "shard_2"."cw_db_zabbix_metric_data01_replica"
經查詢發現該表建立的比較差,order by 僅有一個 UUID。且該表 JOIN 的 SQL 也比較多,每次查詢的讀取的條數也特別大。
停掉 SQL ,觀察結果
通過觀察,發現有不少類似的 JOIN 的 SQL。當停掉一個異常容器指標的 SQL 時會發現 load 值有些許變化,偶爾會降到700多。由於指標管理都是通過 gatewayAPI 呼叫,停掉 SQL 最快的方法便是直接將 gatewayAPI 停掉,這樣只會影響指標 SQL 調,不會影響其他功能使用。將 gatewayAPI 停掉後就不會出現指標呼叫 SQL 也全部停掉,此時發現 load 值已降低
總結
遇到此類問題可先檢視日誌,首先在(Clickhouse 日誌 Zookeeper 日誌)日誌中看能否找到有用的資訊,例如直接報錯資訊等,如果在日誌中找不到太多有用的資訊的話,可以從下面入手。
一般遇到 load 值比較高的情況時,基本上都是發生在查詢上面。當遇到這種問題時可先查詢帶有JOIN 的 SQL 語句是不是很多。隨後通過 query_log 表中的一些欄位去查詢重要資訊,通過對欄位使用一些函式來獲取有用的資訊,如獲取查詢的 SQL,SQL 執行次數、執行時間等。
本次排查過程主要使用query_log表,下面為重要欄位:
event_time
— 查詢開始時間.
query_duration_ms
— 查詢消耗的時間(毫秒).
read_rows
— 從參與了查詢的所有表和表函式讀取的總行數.
query
— 查詢語句.
Clickhouse query_log 表中所有欄位
-
type
(Enum8) — 執行查詢時的事件型別. 值:'QueryStart' = 1
— 查詢成功啟動.'QueryFinish' = 2
— 查詢成功完成.'ExceptionBeforeStart' = 3
— 查詢執行前有異常.'ExceptionWhileProcessing' = 4
— 查詢執行期間有異常.
-
event_date
(Date) — 查詢開始日期. -
event_time
(DateTime) — 查詢開始時間. -
event_time_microseconds
(DateTime64) — 查詢開始時間(毫秒精度). -
query_start_time
(DateTime) — 查詢執行的開始時間. -
query_start_time_microseconds
(DateTime64) — 查詢執行的開始時間(毫秒精度). -
query_duration_ms
(UInt64) — 查詢消耗的時間(毫秒). -
read_rows
(UInt64) — 從參與了查詢的所有表和表函式讀取的總行數. 包括:普通的子查詢,IN
和 **JOIN
**的子查詢. 對於分散式查詢read_rows
包括在所有副本上讀取的行總數。 每個副本傳送它的read_rows
值,並且查詢的伺服器-發起方彙總所有接收到的和本地的值。 快取卷不會影響此值。 -
read_bytes
(UInt64) — 從參與了查詢的所有表和表函式讀取的總位元組數. 包括:普通的子查詢,IN
和 **JOIN
**的子查詢. 對於分散式查詢read_bytes
包括在所有副本上讀取的位元組總數。 每個副本傳送它的read_bytes
值,並且查詢的伺服器-發起方彙總所有接收到的和本地的值。 快取卷不會影響此值。 -
written_rows
(UInt64) — 對於INSERT
查詢,為寫入的行數。 對於其他查詢,值為0。 -
written_bytes
(UInt64) — 對於INSERT
查詢時,為寫入的位元組數。 對於其他查詢,值為0。 -
result_rows
(UInt64) —SELECT
查詢結果的行數,或**INSERT
** 的行數。 -
result_bytes
(UInt64) — 儲存查詢結果的RAM量. -
memory_usage
(UInt64) — 查詢使用的記憶體. -
query
(String) — 查詢語句. -
exception
(String) — 異常資訊. -
exception_code
(Int32) — 異常碼. -
stack_trace
(String) — Stack Trace. 如果查詢成功完成,則為空字串。 -
is_initial_query
(UInt8) — 查詢型別. 可能的值:- 1 — 客戶端發起的查詢.
- 0 — 由另一個查詢發起的,作為分散式查詢的一部分.
-
user
(String) — 發起查詢的使用者. -
query_id
(String) — 查詢ID. -
address
(IPv6) — 發起查詢的客戶端IP地址. -
port
(UInt16) — 發起查詢的客戶端埠. -
initial_user
(String) — 初始查詢的使用者名稱(用於分散式查詢執行). -
initial_query_id
(String) — 執行初始查詢的ID(用於分散式查詢執行). -
initial_address
(IPv6) — 執行父查詢的IP地址. -
initial_port
(UInt16) — 發起父查詢的客戶端埠. -
interface
(UInt8) — 發起查詢的介面. 可能的值:- 1 — TCP.
- 2 — HTTP.
-
os_user
(String) — 執行 clickhouse-client的作業系統使用者名稱. -
client_hostname
(String) — 執行clickhouse-client 或其他TCP客戶端的機器的主機名。 -
client_name
(String) — clickhouse-client 或其他TCP客戶端的名稱。 -
client_revision
(UInt32) — clickhouse-client 或其他TCP客戶端的Revision。 -
client_version_major
(UInt32) — clickhouse-client 或其他TCP客戶端的Major version。 -
client_version_minor
(UInt32) — clickhouse-client 或其他TCP客戶端的Minor version。 -
client_version_patch
(UInt32) — clickhouse-client 或其他TCP客戶端的Patch component。 -
http_method
(UInt8) — 發起查詢的HTTP方法. 可能值:- 0 — TCP介面的查詢.
- 1 —
GET
- 2 —
POST
-
http_user_agent
(String) — TheUserAgent
The UserAgent header passed in the HTTP request。 -
revision
(UInt32) — ClickHouse revision. -
ProfileEvents
(Map(String, UInt64))) — Counters that measure different metrics. The description of them could be found in the table 系統。活動 -
Settings
(Map(String, String)) — Names of settings that were changed when the client ran the query. To enable logging changes to settings, set thelog_query_settings
引數為1。 -
thread_ids
(Array(UInt64)) — 參與查詢的執行緒數. -
Settings.Names
(Array(String)) — 客戶端執行查詢時更改的設定的名稱。 要啟用對設定的日誌記錄更改,請將log_query_settings引數設定為1。 -
Settings.Values
(Array(String)) —Settings.Names
列中列出的設定的值
開源專案推薦
雲智慧已開源資料視覺化編排平臺 FlyFish 。通過配置資料模型為使用者提供上百種視覺化圖形元件,零編碼即可實現符合自己業務需求的炫酷視覺化大屏。 同時,飛魚也提供了靈活的拓展能力,支援元件開發、自定義函式與全域性事件等配置, 面向複雜需求場景能夠保證高效開發與交付。
如果喜歡我們的專案,請不要忘記點選下方程式碼倉庫地址,在 GitHub / Gitee 倉庫上點個 Star,我們需要您的鼓勵與支援。此外,即刻參與 FlyFish 專案貢獻成為 FlyFish Contributor 的同時更有萬元現金等你來拿。
GitHub 地址: https://github.com/CloudWise-OpenSource/FlyFish
Gitee 地址: https://gitee.com/CloudWise/fly-fish
微信掃描識別下方二維碼,備註【飛魚】加入AIOps社群飛魚開發者交流群,與 FlyFish 專案 PMC 面對面交流~
- 提升使用者體驗與搜尋引擎排名|網頁效能監控實操詳解
- 快速掌握網站監控關鍵資料:儀表盤的建立與管理
- 實戰|網站監控如何做好監測點管理與內網資料採集
- 如何分析網站監控中的資料報表,都有哪些關鍵資訊?
- 深入淺出玩轉監控寶(三)|網站監控之分析監控結果
- 深入淺出玩轉監控寶|網站監控之管理網站監控任務
- 深入淺出玩轉監控寶|網站監控之建立網站監控任務
- 通過應用場景深度理解監控寶在業務中的實踐價值
- 新一代 IT 服務管理平臺 DOSM,助力企業數字化轉型
- 降本增效|網站效能監控平臺監控寶背後功能揭祕
- 【中國信通院|低程式碼·無程式碼應用沙龍】低程式碼平臺在雲智慧的實踐探索
- 超詳細 Clickhouse 負載過高問題快速排查思路教程
- 乾貨|僅需3步完成酷炫資料視覺化大屏製作!
- 行業方案|“醫療”行業智慧運維解決方案介紹
- 行業方案|“機場”行業智慧運維解決方案介紹
- “高校”行業智慧運維解決方案解析(含落地實踐)
- 深度解析智慧運維場景下“港口行業”解決方案
- FlyFish 開發者說|開源低程式碼平臺的體驗與思考
- 資料視覺化系列教程之元件通訊
- 資料視覺化系列教程之React元件使用技巧