1.技術背景
在SQL語句復雜、處理數據量大的AP場景下,單個查詢對內存的需求越來越大,多個語句的並發很容易將系統的內存吃滿,造成內存不足的問題。為了應對這種問題,GaussDB for DWS引入了內存自適應控制的技術,在上述場景下能夠對運行的作業進行內存級的管控,避免高並發場景下內存不足產生的各種問題。
2. GaussDB的靜態內存管理機制及缺陷
GaussDB的執行引擎繼承自PG,對於優化器生成的執行計划樹,總體采取執行算子+流水線的處理方式,如下圖所示。
對於NestLoop算子節點,需要首先從左樹的IndexScan算子節點獲取元組,然后到右子樹的IndexScan算子節點進行連接,匹配元組后進行輸出。流水線的執行方式使得對於NestLoop, IndexScan類的一般算子,同時只有一定數量的元組處於內存中,對於行引擎每個算子僅占用一條元組的空間,對於列引擎占用一個batch(最多1000條元組)的空間,占用的空間較小,基本可以忽略不計。
但是,GaussDB中也有一些需要將所有數據收集后進行處理的算子,在執行時需要使用較多的內存,通常我們稱這類算子為物化算子。GaussDB中主要存在如下不同種類的物化算子:
(1)HashJoin:Hash連接操作符,主要思想是計算左右兩表連接列的hash值,通過hash值比較減少元組比較的次數,需要將一個表建立hash表,另一個表進行hash值比較操作,建立hash表需要在內存中進行。
(2)HashAgg:Hash聚集操作符,主要思想同HashJoin類似,通過hash值比較減少元組去重比較的次數,需要將不同值的元組保存的內存中。
(3)Sort:排序操作符,需要獲取所有元組后進行排序操作,待排序元組均存在於內存中。
(4)Materialize:物化操作符,通常在需要重復掃描時使用,通過將結果存儲在內存中,保證重復掃描時的效率。
同時,GaussDB也提供下盤的機制,當上述操作符需要使用的內存太大時,可以將部分或全部的數據下盤處理,提高內存的使用效率,但相應的查詢性能也會受到影響。PG使用 work_mem參數來控制算子可使用內存的閾值,當使用內存超過閾值時,就需要做下盤處理。GaussDB的靜態內存管理機制也延續了PG的處理機制,使用work_mem來控制單算子的內存使用上限。
GaussDB的靜態內存管理存在較大弊端,需要調優人員能夠根據數據量、語句復雜程度和系統的內存大小設置合理的work_mem,既避免work_mem設置太大導致系統資源不夠用,還要考慮到數據規模,保證大部分算子不下盤。通常情況下,這個是很難做到的,有以下幾點原因:
(1)通常情況下,復雜語句的執行計划中包含多個復雜算子,每個算子的內存使用上限是work_mem,我們沒有辦法計算一個語句要使用多少內存,因此也就不容易設置一個最優的work_mem參數,保證盡可能不下盤,同時內存又夠用。並發場景更無法設置了。
(2)work_mem只是每個算子內存使用的上限,並不是預分配;如果數據量沒有那么大的話,實際內存使用是達不到work_mem的。因此也會影響work_mem的設置。
(3)每個語句的場景不一樣,有的語句包含多個物化算子,而另外的語句只有一個物化算子,而這個算子對內存的需求會比較大,因此無法全局統一地進行設置。
3. GaussDB的內存自適應技術介紹
針對靜態內存管理機制的弊端,我們設計了內存自適應控制技術,目的有兩個:
(1)去除靜態內存管理對work_mem的依賴。可以由SQL引擎優化器模塊自動估算每個算子所需的內存。
(2)避免大並發場景下內存不足現象的發生。資源管理模塊根據SQL引擎優化器對於每個查詢內存的估算值,對每個查詢進行調度,如果超過系統可用內存,則進行排隊。
如上圖所示,動態資源管理與內存自適應技術的組件圖如上圖所示。我們從多個CN中選擇一個CN,命名為CCN(Central CN),進行語句隊列的管理。對於每個查詢SQL,CN在生成完執行計划后,為每個物化算子分配合適的內存,同時計算整個語句內存使用量,並將語句及對應的內存使用量發給CCN。CCN維護系統可用的內存值,對於新來的語句,如果語句內存使用量小於可用內存值,則允許其下發到DN執行,否則掛起,等到有語句結束釋放內存后再次將其喚醒,是否可以下發。
為了達到上述目的,SQL引擎實現了內存自適應控制技術,步驟如下:
(1)對於每個SQL,生成計划前首先從資源管理模塊獲取系統當前的最大可用內存(Query Max Mem)和當前可用內存(System Available Mem)。最大可用內存通常為每個DN的最大可用內存去除系統預分配內存,例如:數據緩存等,表示語句可用的最大內存,如果語句使用內存超過該值,必須下盤。當前可用內存用於表示當前系統的繁忙程度,如果當前可用內存比較小,傾向於選擇耗費內存少的計划。
(2)依據當前可用內存生成計划,同時根據SQL引擎優化器計划生成過程中的cost估算值估算每個物化算子的內存使用量,以及流水線場景下整個查詢使用的內存總量估算值。如果該值大於當前可用內存,則嘗試將整個查詢的內存使用量調到當前可用內存以下,此時會造成部分算子下盤。
(3)將語句及估算的語句內存發送到CCN,如果當前可用內存小於語句估算內存,則估算語句的內存進一步減少是否對查詢性能造成較大的影響,如果根據cost評估影響不大,則進一步減少算子的內存使用,使語句內存使用滿足當前可用內存,將語句下發執行,否則則進入排隊狀態。
(4)由於每個算子的內存使用量是基於cost評估獲得,可能存在一定的誤差。因此,在SQL語句執行時,支持內存的動態調整,包括:執行算子內存的自動擴展和提前下盤。當算子達到估算的內存值上限,但系統還有寬裕的內存時,會進行算子內存的擴展,繼續保持不下盤的狀態。當系統已用內存達到80%或更高時,如果算子已有最小內存保證,則會觸發提前下盤邏輯,保證不會由於內存不足而報錯。
4.GaussDB內存自適應的使用和參數控制
通過開啟use_workload_manager和enable_dynamic_workload兩個參數開啟GaussDB for DWS的內存自適應控制機制。
使用內存自適應機制時,打印SQL語句的explain performance執行計划運行信息時,會包含以下額外的信息輔助定位問題:
(1)在最下方的Query Summary一欄中,會顯示出System available mem、Query max mem和Query estimated mem,分別表示:系統當前可用內存、語句可用最大內存(系統可用最大內存),語句估算內存使用量,均為單DN的衡量值。下圖表示當前語句的語句最大可用內存和系統當前可用內存均為22G,語句估算內存使用為1.6G。
(2)在Memory Information一欄,會顯示CN和每個DN的內存使用峰值,如下圖所示,語句實際內存使用,單DN使用16GB,CN使用76MB。
(3)在Memory Information一欄下方每個算子對應的位置,會顯示每個算子單DN的內存峰值,同時會顯示每個DN上內存使用的自動擴展和提前下盤情況,例如下圖,可以看出第15號HashJoin算子,每個SMP線程的內存使用均為3.8GB,估算內存是860MB,經歷了五次內存自動擴展,在第五次擴展后,系統內存告急,算子未用到第五次擴展后的峰值即提前下盤。
(4)在explain performance最頂層的表格中,匯總了每個算子的估算內存和實際使用內存的情況,見下圖的E-memory和Peak Memory兩列所示。與上面信息對應,第15號算子單SMP線程的peak memory,最大值為3766MB,最小值為3753MB,估算內存值(單DN4個SMP線程)為860MB。
可以看出,上面例子由於cost估算不准導致內存估算值較小,實際場景也會出現內存估算值較大的場景,會導致CCN預留內存較多,阻塞其它作業的執行。因此,可以使用參數query_mem來控制語句最大可用內存上限(單DN),相當於代替了Query max mem。此參數默認為0,表示未開啟。當此值大於32MB(最小語句內存分配值)時,表示開啟,此時使用work_mem控制系統當前可用內存進行估算,相當於代替了System available mem進行估算。此時,CCN會使用query_mem值進行語句內存估算值的預留和排隊,提高並發場景下的內存使用效率。
5.總結
內存自適應控制技術是GaussDB for DWS的資源管理結合SQL引擎所做的一次嘗試,當然還存在一些不足,比如:cost估算對內存的評估影響較大,部分場景存在失真需要進行參數控制;系統中內存使用情況比較復雜,還存在部分內存不在管控范圍內需要增強。歡迎各位在實用過程中,將遇到的各種問題及時反饋,也幫助我們更好的改進!