在 Doris 數據庫中,高效的查詢性能是數據處理的關鍵。當我們遇到查詢緩慢、資源消耗異常等問題時,Doris 提供的 Profile 工具就如同一位 “性能偵探”,能幫我們抽絲剝繭,找到問題根源。今天,我們就來深入聊聊如何分析 Profile,讓 Doris 的查詢性能更上一層樓。
一、Profile:解決查詢性能的利器
Profile 是 Doris 用于記錄和展示查詢執行詳細信息的強大工具,它以樹狀結構將查詢計劃的每個階段(Operator)的執行時間、資源消耗等指標清晰呈現。通過 Profile,我們可以直觀地了解查詢的性能表現,判斷哪些操作耗時過長,是否存在數據傾斜、網絡延遲等問題,從而為優化查詢性能、排查問題提供重要依據。無論是慢查詢分析、性能調優,還是高并發場景下的瓶頸定位,Profile 的分析都貫穿其中,是 Doris 性能調優的核心依據。
對于用戶而言,使用 Profile 有三個核心目標:精準找到查詢的瓶頸點,進行基礎的調優操作,以及基于瓶頸點判斷需要哪些專業人員介入分析(找社區大佬支持)。掌握 Profile 的分析方法,能讓你成為真正懂Doris性能優化的 “行家”。
二、獲取 Profile:不同環境下的操作指南
想要利用 Profile 優化查詢性能,首先要學會如何獲取它。在不同的網絡環境下,獲取 Profile 的方法有所不同。
(一)Doris 集群能夠正常訪問外網
-
開啟 Profile 收集參數
enable_profile
,該參數為 session 變量,不建議全局開啟。在 mysql 客戶端執行set enable_profile=true;
,并通過show variables like '%profile%';
確認變量是否正常開啟(如果是壓測時候,可以全局開啟)。 -
執行對應 Query。需要注意的是,在集群有多個 FE 的情況下,要在開啟 Profile 上報參數的 FE 上執行 Query,因為該參數并非全局生效(session變量,不加global,只在當前環境下生效)。
-
獲取 Profile。訪問執行對應 Query 的 FE HTTP 界面(
HTTP://FE_IP:HTTP_PORT
)的 QueryProfile 頁面,點擊對應 Profile ID 查看 Profile,還可以在 Profile 界面下載對應 Profile。
3.0.3 版本開始支持auto_profile_threshold_ms 變量,這樣就不需要收集所有的query profile了,只需要收集超過指定時間的慢查詢的profile
(二)Doris 集群訪問外網受到限制
當集群不能正常訪問外網時,需要通過 API 的方式獲取 Profile(HTTP://FE_IP:HTTP_PORT/API/Profile?Query_ID=
),IP 和端口是指執行對應 Query 的 FE 對應 IP 和端口。具體步驟如下:
-
前兩步與正常訪問外網時相同,即開啟
enable_profile
參數并執行對應 Query。 -
找到對應 Query ID,通過
show query profile "/";
根據對應 Query 找到 Profile ID。
+-----------------------------------+-----------+---------------------+---------------------+-------+------------+------+------------+-------------------------------------------------------+
| Profile ID | Task Type | Start Time | End Time | Total | Task State | User | Default Db | Sql Statement |
+-----------------------------------+-----------+---------------------+---------------------+-------+------------+------+------------+-------------------------------------------------------+
| 1b0bb22689734d30-bbe56e17c2ff21dc | QUERY | 2024-02-28 11:00:17 | 2024-02-28 11:00:17 | 7ms | EOF | root | | select id,name from test.test where name like "%RuO%" |
| 202fb174510c4772-965289e8f7f0cf10 | QUERY | 2024-02-25 19:39:20 | 2024-02-25 19:39:20 | 19ms | EOF | root | | select id,name from test.test where name like "%KJ%" |
+-----------------------------------+-----------+---------------------+---------------------+-------+------------+------+------------+-------------------------------------------------------+
2 rows in set (0.00 sec)
-
查詢 Profile 并將 Profile 重定向到一個文本中,使用命令
CURL -X GET -u user:password http://fe_ip:http_port/api/profile?query_id=1b0bb22689734d30-bbe56e17c2ff21dc > test.profile
。 -
返回的 Profile 換行符為
n
,分析起來不方便,可以在文本編輯工具中將n
替換為n
,以便更好地查看和分析。
三、Profile 基礎信息總覽:快速定位問題方向
先來看一個 Profile 的基礎信息示例:
Profile ID: 26ec8a4369b46ba-9af43a31cc4f5102- Task Type: QUERY- Start Time: 2025-03-31 17:23:47- End Time: 2025-03-31 17:25:51- Total: 2min4sec [總的耗時]- Task State: OK- User: root- Default Catalog: internal- Default Db: fine_bi_crossdata- Sql Statement: select * from view1 limit 100;
Execution Summary:- Parse SQL Time: 15ms- Nereids Analysis Time: 10ms- Nereids Rewrite Time: 51ms- Nereids Optimize Time: 30ms- Nereids Translate Time: 3ms- Workload Group: normal- Analysis Time: 10ms- Plan Time: 99ms [優化器的耗時,如果這個時間占據單個查詢的比例過大,比如超過20%,需要聯系社區同學分析]- JoinReorder Time: N/A- CreateSingleNode Time: N/A- QueryDistributed Time: N/A- Init Scan Node Time: N/A- Finalize Scan Node Time: N/A- Get Splits Time: N/A [如果是這塊的占比時間比較高的情況,大概率是外表的問題]- Get Partitions Time: N/A- Get Partition Files Time: N/A- Create Scan Range Time: N/A- Get Partition Version Time: 9.561ms- Get Partition Version Count (hasData): 0- Get Partition Version Count: 1- Get Table Version Time: N/A- Get Table Version Count: 0- Schedule Time: 98ms- Fragment Assign Time: 1ms- Fragment Serialize Time: 21ms [這里也類似,如果耗時過多,請也得聯系社區的同學分析了。很可能是有GC了]- Fragment RPC Phase1 Time: 75ms- Fragment RPC Phase2 Time: 1ms- Fragment Compressed Size: 3.92 MB- Fragment RPC Count: 24- Schedule Time Of BE: {...}- Wait and Fetch Result Time: 2min3sec- Fetch Result Time: 2min3sec [BE具體執行的耗時]- Write Result Time: 1ms [回寫mysql的耗時,如果這塊耗時過多,可能是客戶端的瓶頸,分析一下結果集是不是過大]
從這些信息中,我們可以快速判斷問題可能出在 FE 還是 BE 。通常情況下,95% 以上的主要耗時集中在Wait and Fetch Result Time
。若 FE 出現問題,常見原因有 GC 問題、鎖問題、CPU 打滿等,這類問題與 FE 的集群負載密切相關,一旦確認是 FE 的問題,需要收集當時的 CPU、內存監控數據進一步分析。
四、定位瓶頸點:Merged Profile 匯總信息
為了更準確地分析性能瓶頸,Doris 提供了各個 operator 聚合后的 Merged Profile 結果。以EXCHANGE_OPERATOR
為例:
EXCHANGE_OPERATOR (id=4):- BlocksProduced: sum 0, avg 0, max 0, min 0- CloseTime: avg 34.133us, max 38.287us, min 29.979us- ExecTime: avg 700.357us, max 706.351us, min 694.364us- InitTime: avg 648.104us, max 648.604us, min 647.605us- MemoryUsage: sum , avg , max , min - PeakMemoryUsage: sum 0.00 , avg 0.00 , max 0.00 , min 0.00 - OpenTime: avg 4.541us, max 5.943us, min 3.139us- ProjectionTime: avg 0ns, max 0ns, min 0ns- RowsProduced: sum 0, avg 0, max 0, min 0- WaitForDependencyTime: avg 0ns, max 0ns, min 0ns- WaitForData0: avg 9.434ms, max 9.476ms, min 9.391ms
Merged Profile 對每個 operator 的核心指標做了合并,核心指標和含義包括:
指標名稱 | 指標含義 |
---|---|
BlocksProduced | 產生的 Data Block 數量 |
CloseTime | Operator 在 close 階段的耗時 |
ExecTime | Operator 在各個階段執行的總耗時 |
InitTime | Operator 在 Init 階段的耗時 |
MemoryUsage | Operator 在執行階段的內存用量 |
OpenTime | Operator 在 Open 階段的耗時 |
ProjectionTime | Operator 在做 projection 的耗時 |
RowsProduced | Operator 返回的行數 |
WaitForDependencyTime | Operator 等待自身執行的條件依賴的時間 |
我們可以通過以下簡單方法快速定位瓶頸點:
cat profile_b112de34c7a94d2e-a6b773cc1db43602.txt |grep ExecTime | grep max
找到max
耗時最久的時間后,再回頭在 Profile 中查找對應的算子,就能確定查詢的性能瓶頸所在。
例如,通過上述方法找出慢的算子是AGG算子
,后續就可以針對AGG算子
進行深入分析和調優。
五、基礎調優:對癥下藥,提升性能
Doris 中的查詢主要分為兩類,針對不同類型的查詢,調優方法也有所不同。
(一)單表大寬表分析
對于單表的大寬表分析,優化方法可參考 Doris 查詢優化秘籍(上篇):關鍵優化策略剖析,通過合理設計表結構、選擇合適的索引等方式提升查詢性能。
(二)多表關聯復雜分析
多表關聯的復雜分析中,70% - 80% 的性能問題是由 RuntimeFilter 和 JoinReorder 不合適導致的。
-
RuntimeFilter 調優:在 Profile 中搜索
RuntimeFilterState
關鍵字,如果發現IsPushDown = false
或RuntimeFilter
的狀態為NOT_READY
,可以通過設置set runtime_filter_wait_infinitely = true
,重新驗證是否是 RF 導致的性能問題。 -
Join 方式調優:在 Doris 中,
shuffle
和broadcast
是常見的數據分發方式,選擇不當會影響查詢性能。例如,當出現數據傾斜導致Shuffle
性能極差時,可以通過指定broadcast join hint
來優化,如SELECT COUNT(*) FROM orders o JOIN [broadcast] customer c ON o.customer_number = c.customer_number;
。 -
Join 順序調優:Doris 中 JOIN 操作建議左表大于右表(或至少左表不小于右表),以優化查詢性能。如果發現
max
的耗時主要在join
上,可以查看join
順序是否合理。通過對比HASH_JOIN_OPERATOR
(左表)和HASH_JOIN_SINK_OPERATOR
(右表)的數據量情況,判斷join
順序是否存在問題。若InputRows > ProbeRows
,大概率join
順序不合理,此時可以請優化器的專家進行分析處理。 -
并行度調整:當
Schedule time
占據大量查詢耗時,通常是instance
過多,導致rpc
、序列化處理的數據量過大。這種情況下,可以考慮調小pipeline
的并行度,如設置set global parallel_pipeline_task_num = 1
,觀察性能是否改善。
六、深入分析 Profile:常見問題與處理方案
通過 Execution Summary 部分,我們可以初步確定查詢的瓶頸在 FE 還是 BE。若在 BE,我們可以重點分析各個算子的情況。在 2.1 版本的 Profile 經過 merge 后,界面較為精簡,我們可以通過查看每個算子的查詢時間來定位問題。一般來說,ExecTime
最大的節點就是最耗時的節點,順著這個 id 繼續分析。如果各個算子耗時相差不大,說明沒有明確的熱點。
以下是一些常見問題的處理方案:
-
熱點在 nested loop join 上:嘗試修改 sql,將 nested loop join 改為 hash join,可顯著提升性能。
-
熱點在 join 上:
-
檢查 join order 順序,從條目數上判斷是否合理。若右表條目數顯著大于左表,或左右表數據量相近但右表列數顯著大于左表,可能需要優化器同學介入。
-
排查 Runtime filter,檢查 rf 是否生效、等待時間是否合理,以及是否存在多余或無用的 rf。
-
優化 join 列,盡量將 join 列和 group by 列建為非 null,能用定長列就不用變長列。
-
評估 join 的 shuffle 方式是否合理,考慮使用
Broadcast
、colocate
或優化Bucket shuffle
。
-
-
熱點在 agg 上:參考 join 列的優化方式,調整表結構,如將 group by 的列作為表的分桶列或分區列,可對單表聚合性能提升有顯著效果。
-
scan 上慢:
-
檢查表引擎類型,優先選擇查詢性能較好的
duplicate key
引擎。 -
使用
select 分桶列,count(*) from table group by 分桶列 order by count(*) desc limit 100;
檢查分桶是否有數據傾斜問題。 -
注意 key 列的順序,因為 Doris 底層存儲引擎按 key 列排序存儲,合理的 key 列順序有助于快速二分查找。
-
觀察表的 compaction 情況,找到慢 scan 的 tablet,查看 compaction 鏈接,若存在慢副本,可通過
set use_fix_replica
繞過,并聯系官方同學分析原因。
-
Profile 是 Doris 性能優化的關鍵利器,掌握它的獲取、分析和調優方法,能讓我們在面對查詢性能問題時游刃有余。希望通過本文的分享,大家都能成為 Doris 性能優化的高手!如果你在使用 Profile 的過程中還有其他問題或經驗,歡迎在評論區留言交流~