DolphinDB內存管理詳解

DolphinDB是一款支持多用戶多任務併發操做的高性能分佈式時序數據庫軟件(distributed time-series database)。針對大數據的高效的內存管理是其性能優異的緣由之一。本教程涉及的內存管理包括如下方面:node

  • 變量的內存管理:爲用戶提供與回收編程環境所需內存。
  • 分佈式表的緩存管理:多個session共享分區表數據,以提升內存使用率。
  • 流數據緩存:流數據發送節點提供持久化和發送隊列緩存,訂閱節點提供接收數據隊列緩存。
  • DFS數據庫寫入緩存:寫入DFS的數據先寫到WAL和緩存,經過批量寫入提高吞吐量。


1. 內存管理機制

DolphinDB向操做系統申請內存塊,自行進行管理。當申請的內存塊閒置時,系統會按期檢查並釋放。目前vector和table以及全部字符串的內存分配都已經歸入DolphinDB的內存管理系統。web

經過參數maxMemSize設定節點的最大內存使用量:該參數制定節點的最大可以使用內存。若是設置過小,會嚴重限制集羣的性能,若是設置太大,例如超過物理內存,可能會觸發操做系統強制關閉進程。若機器內存爲16GB,而且只部署1個節點,建議將該參數設置爲12GB左右。數據庫

以512MB爲單位向操做系統申請內存塊:當用戶查詢操做或者編程換進所須要內存時,DolphinDB會以512MB爲單位向操做系統申請內存。若是操做系統沒法提供大塊的連續內存,則會嘗試256MB,128MB等更小的內存塊。編程

系統充分利用可用內存緩存數據庫數據:當節點的內存使用總量小於maxMemSize時,DolphinDB會盡量多的緩存數據庫分區數據,以便提高用戶下次訪問該數據塊的速度。當內存不足時,系統自動會剔除部分緩存。數組

每隔30秒掃描一次,空閒的內存塊還給操做系統:當用戶使用釋放內存中變量,或者使用函數clearAllCache釋放緩存時,若是內存塊徹底空閒,則會總體還給操做系統,若是仍有小部份內存在使用,好比512MB的內存塊中仍有10MB在使用,則不會歸還操做系統。緩存


2. 變量的內存管理

2.1 建立變量網絡

在DolphinDB節點上,先建立一個用戶user1,而後登錄。建立一個vector,含有1億個INT類型元素,約400MB。session

示例1. 建立vector變量數據結構

login("admin","123456")  //建立用戶須要登錄admin
createUser("user1","123456")
login("user1","123456")
v = 1..100000000
sum(mem().blockSize - mem().freeSize) //輸出內存佔用結果

結果爲: 402,865,056,內存佔用400MB左右,符合預期。併發

再建立一個table,1000萬行,5列,每列4字節,約200MB。

示例2. 建立table變量

n = 10000000
t = table(n:n,["tag1","tag2","tag3","tag4","tag5"],[INT,INT,INT,INT,INT])
(mem().blockSize - mem().freeSize).sum()

結果爲:612,530,448,約600MB,符合預期。


2.2 釋放變量

可經過undef函數,釋放變量的內存。

示例3. 使用undef函數或者賦值爲NULL釋放變量

undef(`v)

或者

v = NULL

除了手動釋放變量,當session關閉時,好比關閉GUI和其餘API鏈接,都會觸發對該session的全部內存進行回收。當經過web notebook鏈接時,10分鐘內無操做,系統會關閉session,自動回收內存。


3. 分佈式表的緩存管理

DolphinDB對分佈式表是以分區爲單位管理的。分佈式表的緩存是全局共享的,不一樣的session或讀事務在大部分狀況下,會看到同一份數據copy(版本可能會有所不一樣),這樣極大的節省了內存的使用。

歷史數據庫都是以分佈式表的形式存在數據庫中,用戶平時查詢操做也每每直接與分佈式表交互。分佈式表的內存管理有以下特色:

  • 內存以分區列爲單位進行管理。
  • 數據只加載到所在的節點,不會在節點間轉移。
  • 多個用戶訪問相同分區時,使用同一份緩存。
  • 內存使用不超過maxMemSize狀況下,儘可能多緩存數據。
  • 緩存數據達到maxMemSize時,系統自動回收。

如下多個示例是基於如下集羣:部署於2個節點,採用單副本模式。按天分30個區,每一個分區1000萬行,11列(1列DATE類型,1列INT類型,9列LONG類型),因此每一個分區的每列(LONG類型)數據量爲1000萬行 * 8字節/列 = 80M,每一個分區共1000萬行 * 80字節/行 = 800M,整個表共3億行,大小爲24GB。

函數clearAllCache()可清空已經緩存的數據,下面的每次測試前,先用該函數清空節點上的全部緩存。


3.1 內存以分區列爲單位進行管理

DolphinDB採用列式存儲,當用戶對分佈式表的數據進行查詢時,加載數據的原則是,只把用戶所要求的分區和列加載到內存中。

示例4. 計算分區2019.01.01最大的tag1的值。該分區儲存在node1上,能夠在controller上經過函數getClusterChunksStatus()查看分區分佈狀況,並且由上面可知,每列約80MB。在node1上執行以下代碼,並查看內存佔用。

select max(tag1) from loadTable(dbName,tableName) where day = 2019.01.01
sum(mem().blockSize - mem().freeSize)

輸出結果爲84,267,136。咱們只查詢1個分區的一列數據,因此把該列數據所有加載到內存,其餘的列不加載。

示例5. 在node1 上查詢 2019.01.01的前100條數據,並觀察內存佔用。

select top 100 * from loadTable(dbName,tableName) where day = 2019.01.01
sum(mem().blockSize - mem().freeSize)

輸出結果爲839,255,392。雖然咱們只取100條數據,可是DolphinDB加載數據的最小單位是分區列,因此須要加載每一個列的所有數據,也就是整個分區的所有數據,約800MB。

注意:  合理分區以免"out of memory":DolphinDB是以分區爲單位管理內存,所以內存的使用量跟分區關係密切。假如用戶分區不均勻,致使某個分區數據量超大,甚至機器的所有內存都不足以容納整個分區,那麼當涉及到該分區的查詢計算時,系統會拋出"out of memory"的異常。通常原則,若是用戶設置maxMemSize=8,則每一個分區經常使用的查詢列之和爲100-200MB爲宜。若是表有10列經常使用查詢字段,每列8字段,則每一個分區約100-200萬行。


3.2 數據只加載到所在的節點

在數據量大的狀況下,節點間轉移數據是很是耗時的操做。DolphinDB的數據是分佈式存儲的,當執行計算任務時,把任務發送到數據所在的節點,而不是把數據轉移到計算所在的節點,這樣大大下降數據在節點間的轉移,提高計算效率。

示例6. 在node1上計算兩個分區中tag1的最大值。其中分區2019.01.02數組存儲在node1上,分區2019.01.03數據存儲在node2上。

select max(tag1) from loadTable(dbName,tableName) where day in [2019.01.02,2019.01.03]
sum(mem().blockSize - mem().freeSize)

輸出結果爲84,284,096。在node2上用查看內存佔用結果爲84,250,624。每一個節點存儲的數據都爲80M左右,也就是node1上存儲了分區2019.01.02的數據,node2上存儲了2019.01.03的數據。

示例7. 在node1上查詢分區2019.01.02和2019.01.03的全部數據,咱們預期node1加載2019.01.02數據,node2加載2019.01.03的數據,都是800M左右,執行以下代碼並觀察內存。

select top 100 * from loadTable(dbName,tableName) where day in [2019.01.02,2019.01.03]
sum(mem().blockSize - mem().freeSize)

node1上輸出結果爲839,279,968。node2上輸出結果爲839,246,496。結果符合預期。

注意:  謹慎使用沒有過濾條件的"select *",由於這會將全部數據載入內存。在列數不少的時候尤爲要注意該點,建議僅加載須要的列。若使用沒有過濾條件的"select top 10 *",會將第一個分區的全部數據載入內存。


3.3 多個用戶訪問相同分區時,使用同一份緩存

DolphinDB支持海量數據的併發查詢。爲了高效利用內存,對相同分區的數據,內存中只保留同一份副本。

示例8. 打開兩個GUI,分別鏈接node1和node2,查詢分區2019.01.01的數據,該分區的數據存儲在node1上。

select * from loadTable(dbName,tableName) where date = 2019.01.01
sum(mem().blockSize - mem().freeSize)

上面的代碼無論執行幾回,node1上內存顯示一直是839,101,024,而node2上無內存佔用。由於分區數據只存儲在node1上,因此node1會加載全部數據,而node2不佔用任何內存。


3.4 節點內存佔用狀況與緩存數據的關係

3.4.1 節點內存使用不超過maxMemSize狀況下,儘可能多緩存數據

一般狀況下,最近訪問的數據每每更容易再次被訪問,所以DolphinDB在內存容許的狀況下(內存佔用不超過用戶設置的maxMemSize),儘可能多緩存數據,來提高後續數據的訪問效率。

示例9. 數據節點設置的maxMemSize=8。連續加載9個分區,每一個分區約800M,總內存佔用約7.2GB,觀察內存的變化趨勢。

days = chunksOfEightDays();
for(d in days){
    select * from loadTable(dbName,tableName) where  = day
    sum(mem().blockSize - mem().freeSize)
}

內存隨着加載分區數的增長變化規律以下圖所示:

當遍歷每一個分區數據時,在內存使用量不超過maxMemSize的狀況下,分區數據會所有緩存到內存中,以在用戶下次訪問時,直接從內存中提供數據,而不須要再次從磁盤加載。

 

3.4.2 節點內存使用達到maxMemSize時,系統自動回收


若是DolphinDB server使用的內存,沒有超過用戶設置的maxMemSize,則不會回收內存。當總的內存使用達到maxMemSize時,DolphinDB 會採用LRU的內存回收策略, 來騰出足夠的內存給用戶。

示例10. 上面用例只加載了8天的數據,此時咱們繼續共遍歷15天數據,查看緩存達到maxMemSize時,內存的佔用狀況。以下圖所示:

如上圖所示,當緩存的數據超過maxMemSize時,系統自動回收內存,總的內存使用量仍然小於用戶設置的最大內存量8GB。

示例11. 當緩存數據接近用戶設置的maxMemSize時,繼續申請Session變量的內存空間,查看系統內存佔用。此時先查看系統的內存使用:

sum(mem().blockSize - mem().freeSize)

輸出結果爲7,550,138,448。內存佔用超過7GB,而用戶設置的最大內存使用量爲8GB,此時咱們繼續申請4GB空間。

v = 1..1000000000
sum(mem().blockSize - mem().freeSize)

輸出結果爲8,196,073,856。約爲8GB,也就是若是用戶定義變量,也會觸發緩存數據的內存回收,以保證有足夠的內存提供給用戶使用。


4. 流數據消息緩存隊列

當數據進入流數據系統時,首先寫入流表,而後寫入持久化隊列和發送隊列(假設用戶設置爲異步持久化),持久化隊列異步寫入磁盤,將發送隊列發送到訂閱端。

當訂閱端收到數據後,先放入接受隊列,而後用戶定義的handler從接收隊列中取數據並處理。若是handler處理緩慢,會致使接收隊列有數據堆積,佔用內存。以下圖所示:

流數據內存相關的配置選項:

  • maxPersistenceQueueDepth: 流表持久化隊列的最大消息數。對於異步持久化的發佈流表,先將數據放到持久化隊列中,再異步持久化到磁盤上。該選項默認設置爲1000萬。在磁盤寫入成爲瓶頸時,隊列會堆積數據。
  • maxPubQueueDepthPerSite: 最大消息發佈隊列深度。針對某個訂閱節點,發佈節點創建一個消息發佈隊列,該隊列中的消息發送到訂閱端。默認值爲1000萬,當網絡出現擁塞時,該發送隊列會堆積數據。
  • maxSubQueueDepth: 訂閱節點上最大的每一個訂閱線程最大的可接收消息的隊列深度。訂閱的消息,會先放入訂閱消息隊列。默認設置爲1000萬,當handler處理速度較慢,不能及時處理訂閱到的消息時,該隊列會有數據堆積。
  • 流表的capacity:在函數enableTablePersistence()中第四個參數指定,該值表示流表中保存在內存中的最大行數,達到該值時,從內存中刪除一半數據。當流數據節點中,流表比較多時,要總體合理設置該值,防止內存不足。

運行過程,能夠經過函數getStreamingStat()來查看流表的大小以及各個隊列的深度。


5. 爲寫入DFS數據庫提供緩存

DolphinDB爲了提升讀寫的吞吐量和下降讀寫的延遲,採用先寫入WAL和緩存的通用作法,等累積到必定數量時,批量寫入。這樣減小和磁盤文件的交互次數,提高寫入性能,可提高寫入速度30%以上。所以,也須要必定的內存空間來臨時緩存這些數據,以下圖所示:

當事務t1,t2,t3都完成時,將三個事務的數據一次性寫入到DFS的數據庫磁盤上。Cache Engine空間通常推薦爲maxMemSize的1/8~1/4,可根據最大內存和寫入數據量適當調整。CacheEngine的大小能夠經過配置參數chunkCacheEngineMemSize來配置。

  • chunkCacheEngineMemSize:指定cache engine的容量。cache engine開啓後,寫入數據時,系統會先把數據寫入緩存,當緩存中的數據量達到chunkCacheEngineMemSize的30%時,纔會寫入磁盤。


6. 高效使用內存

在企業的生產環境中,DolphinDB每每做爲流數據中心以及歷史數據倉庫,爲業務人員提供數據查詢和計算。當用戶較多時,不當的使用容易形成Server端內存耗盡,拋出"out of memory" 異常。可遵循如下建議,儘可能避免內存的不合理使用。

  • 合理均勻分區:DolphinDB是以分區爲單位加載數據,所以,分區大小對內存影響巨大。合理均勻的分區,無論對內存使用仍是對性能而言,都有積極的做用。所以,在建立數據庫的時候,根據數據規模,合理規劃分區大小。每一個分區的經常使用字段數據量約100MB左右爲宜。
  • 及時釋放數據量較大的變量:若用戶建立數據量較大的變量,例如v = 1..10000000,或者將含有大量數據的查詢結果賦值給一個變量t = select * from t where date = 2010.01.01,v和t將會在用戶的session佔用大量的內存。若是不及時釋放,當其餘用戶申請內存時,就有可能由於內存不足而拋出異常。
  • 只查詢須要的列:避免使用select *,若是用select *會把該分區全部列加載到內存。實際中,每每只須要幾列。所以爲避免內存浪費,儘可能明確寫出全部查詢的列,而不是用*代替。
  • 數據查詢儘量使用分區過濾條件:DolphinDB按照分區進行數據檢索,若是不加分區過濾條件,則會所有掃描全部數據,數據量大時,內存很快被耗盡。有多個過濾條件的話,要優先寫分區的過濾條件。
  • 儘快釋放再也不須要的變量或者session:根據以上分析可知,用戶的私有變量在建立的session裏面保存。session關閉的時候,會回收這些內存。所以,儘早使用undef函數或者關閉session來釋放內存。
  • 合理配置流數據的緩存區:通常狀況下流數據的容量(capacity)會直接影響發佈節點的內存佔用。好比,capacity設置1000萬條,那麼流數據表在超過1000萬條時,會回收約一半的內存佔用,也就是內存中會保留500萬條左右。所以,應根據發佈節點的最大內存,合理設計流表的capacity。尤爲是在多張發佈表的狀況,更須要謹慎設計。


7. 內存監控及常見問題


7.1 內存監控


7.1.1 controller上監控集羣中節點內存佔用

在controller上提供函數getClusterPerf()函數,顯示集羣中各個節點的內存佔用狀況。包括:

MemAlloc:節點上分配的總內存,近似於向操做系統申請的內存總和。

MemUsed:節點已經使用的內存。該內存包括變量、分佈式表緩存以及各類緩存隊列等。

MemLimit:節點可以使用的最大內存限制,即用戶配置的maxMemSize。

 

7.1.2 mem()函數監控某個節點內存佔用

mem()函數能夠顯示整個節點的內存分配和佔用狀況。該函數輸出4列,其中列blockSize表示分配的內存塊大小,freeSize表示剩餘的內存塊大小,經過sum(mem().blockSize - mem().freeSize) 獲得節點所使用的總的內存大小。


7.1.3 監控節點上不一樣session的內存佔用

可經過函數getSessionMemoryStat()查看節點上每一個session佔用的內存量,該內存只包含session內定義的變量。當節點上內存佔用過高時,能夠經過該函數排查哪一個用戶使用了大量的內存。


7.1.4 查看某個對象佔用的內存大小

經過函數memSize來查看某個對象佔用內存的具體大小,單位爲字節。好比:

v=1..1000000
memSize(v)

輸出:4000000。


7.2 常見問題


7.2.1 監控顯示節點內存佔用過高

經過上面的分析可知,DolphinDB會在內存容許的狀況下,會盡量多的緩存數據。所以,若是隻顯示節點內存佔用過高,接近maxMemSize,而沒有其餘內存相關的錯誤,那麼這種狀況是正常的。 若是出現"out of memory"等相似的錯誤,首先能夠經過函數getSessionMemoryStat()查看各個session佔用的內存大小,其次經過函數clearAllCache()來手動釋放節點的緩存數據。

7.2.2 MemAlloc顯示值跟操做系統實際顯示值有差別

DolphinDB是C++程序,自己須要一些基礎的數據結構和內存開銷,MemAlloc顯示內存不包括這些內存,若是二者顯示相差不大,幾百MB之內,都屬於正常現象。


7.2.3 查詢時,報告"out of memory"

該異常每每是因爲query所需的內存大於系統可提供的內存致使的。可能由如下緣由致使:

  • 查詢沒有加分區過濾條件或者條件太寬,致使單個query涉及的數據量太大。
  • 分區不均勻。可能某個分區過大,該分區的數據超過節點配置的最大內存。
  • 某個session持有大的變量,致使節點可用的內存很小。

7.2.4 查詢時,DolphinDB進程退出,沒有coredump產生

這種狀況每每是因爲給節點分配的內存超過系統物理內存的限制,操做系統把DolphinDB強制退出。Linux上能夠經過操做系統的日誌查看緣由。


7.2.5 執行clearAllCache()函數後,MemUsed沒有明顯下降

能夠經過getSessionMemoryStst()查看各個session佔用的內存大小。多是因爲某個session持有佔用大量內存的變量不釋放,致使該部份內存一直不能回收。

相關文章
相關標籤/搜索