1. 程式人生 > >批量隨機鍵值查詢測試 集算器

批量隨機鍵值查詢測試 集算器

【摘要】
當資料量巨大時,使用大批量隨機鍵值集獲取對應記錄集合,不僅僅考驗資料庫軟體本身,更在於程式設計師對資料的理解!如何在硬體資源有限的情況下將效能發揮到極致?點選:批量隨機鍵值查詢測試,來乾學院一探究竟!

複製摘要

本次測試主要針對集算器組表索引實現的批量鍵值取數效能,並與 Oracle 進行同規模運算對比。

一、測試環境

處理器 Intel(R) Xeon(R) CPU E5-2670 @   2.60GHz兩顆
記憶體 64G
硬碟 SAS 1TB
作業系統 centos6.8(64 位)

二、資料描述

2.1資料結構

 

欄位 型別 備註
id long 1000000000001開始自增
data string 隨機字串(長度為 180 位元組)

2.2資料規模

按以上資料結構,造出 6 億條記錄的行存組表文件和對應的索引檔案:

 

型別 檔名 大小
組表 id_600m.ctx 111G
索引 id_600m.ctx__id_idx 14G

三、測試過程

3.1生成測試檔案

3.1.1  建組表

 

  A B
1 1234567890qwertyuiopasdfghjklzxcvbnm  
2 =file("id_600m.ctx")  
3
[email protected]
(#id,data)
 
4 for 6000 =to((A4-1)*100000+1,A4*100000).new(~+1000000000000:id,rands(A1,180):data)
5   =A3.append(B4.cursor())

A1:包含 26 個英文字母和 10 個阿拉伯數字的字串。

A2、A3:建立結構為 (id,data) 的組表文件,@r 選項表示使用行式儲存方式。

A4:迴圈 6000 次,迴圈體B4、B5,每次生成 10 萬條對應結構的記錄,並追加到組表文件。

執行後,生成組表文件:id_600m.ctx

3.1.2  建索引

  A
1 =file("id_600m.ctx  ")
2 =A1.create().index(id_idx;id)

A2:根據組表文件的 id 列,建立組表索引。

執行後,生成組表的索引檔案:id_600m.ctx__id_idx

3.2查詢測試

  A
1 =file("id_600m.ctx").create()
2 =10000.(1000000000000+(rand(600000000)+1)).sort()
3 =now()
4 =A1.icursor(A2.contain(id),id_idx).fetch()
5 [email protected](A3,now())

A2:迴圈一萬次,每次獲取對應組表文件 id 列中的隨機一個,並排序。(可能會有少量重複值,但對測試影響不大)

A4:在組表的 icursor()這個函式中,使用索引 id_idx,以條件 A2.contain(id) 來過濾組表。集算器會自動識別出 A2.contain(id) 這個條件可以使用索引,並會自動將 A2 的內容排序後從前向後查詢。

3.3奇怪的現象

原本希望多次執行後,求得一個平均值作為測試結果。但是發現每執行完畢一次該測試程式碼,都會比上一次執行快一些,這裡列出從第一次執行該程式碼後的 5 次測試查詢耗時:

次數 查詢耗時(毫秒)
1 79778
2 78391
3 78186
4 76192
5 74244

手動一次次點選設計器中的執行按鈕,並記錄下查詢耗時,太費勁了。為了找出規律,將程式碼改為以下形式:

  A B
1    
2 for =file("id_600m.ctx").create()
3   =10000.(1000000000000+(rand(600000000)+1)).sort()
4   =now()
5   =B2.icursor(B3.contain(id),id_idx).fetch()
6   [email protected](B4,now())
7   >A1=A1|B6

B7:將迴圈體中 icursor() 函式每一次查詢的耗時,在 A1 中追加記錄下來。

執行過程中,觀察 A1 中新追加的查詢耗時與上一次的比較,發現經過大約 350 次迴圈後接近極限值 25 秒。再後續近千次迴圈中,查詢耗時也都是如此,基本穩定。

難道是集算器對資料進行了快取?抱著懷疑的態度,重啟了集算器設計器,再次執行了查詢程式碼。發現重啟後第一次的查詢耗時也是 25 秒。這樣看來提速的原因和集算器本身並沒有什麼直接的關係了。

另一方面,可以想到基於目前測試的資料量,能夠在短時間內完成查詢,部分資料可能已經裝載至記憶體,那麼很可能是 linux 作業系統的檔案快取造成了這個現象。重啟伺服器後,再通過集算器設計器來執行查詢,發現耗時又開始從 80 秒左右慢慢減少了。

進一步的測試中,使用了 linux 的 free 命令檢視系統記憶體使用情況。發現每完成一次組表的查詢,其中的 cached 一項就會變大。而隨著 cached 慢慢的變大,查詢的耗時又逐步減少。

[email protected]的使用

在網路上查詢了一些資料,瞭解到 Linux 會存在快取記憶體,通常叫做 Cache Memory。就是之前使用 free 命令看到其中的 cached 一項,執行 free -h:

  total used free shared buffers cached
Mem: 62G 62G 519M 9.1M 10M 45G

當我們讀寫檔案的時候,Linux 核心為了提高讀寫效率與速度,會將檔案在記憶體中進行快取,這部分記憶體就是 Cache Memory(快取記憶體)。即使我們的程式執行結束後,Cache Memory 也不會自動釋放。這就會導致我們在 Linux 系統中程式頻繁讀寫檔案後,我們會發現可用實體記憶體會很少。其實這個快取記憶體在我們需要使用記憶體的時候會自動釋放,所以我們不必擔心沒有記憶體可用。並且手動去釋放 Cache Memory 也是有辦法的,但此處不再詳細探討。

這個函式涉及資料量有 111G,比機器的實體記憶體 64G 更大,顯然不可能把所有資料都快取到記憶體中,那麼到底快取了哪些資料後就能穩定地提高查詢效能呢?是不是可以事先就把需要這些資料先快取起來以獲得高效能?請教了高手後,發現果然還有選項可以來預先快取索引的索引。在使用 icursor()函式查詢之前,對組表索引使用了 [email protected](idx) 使用了 [email protected](idx)。程式碼如下:

 

  A
1 =file("id_600m.ctx").create()
2 =now()
3 [email protected](id_idx)
4 [email protected](A2,now())
5 =10000.(1000000000000+(rand(600000000)+1)).sort()
6 =now()
7 =A1.icursor(A5.contain(id),id_idx).fetch()
8 [email protected](A6,now())

集算器的索引有個分級快取,@3 的意思是將索引的第三級快取先載入進記憶體。經過 [email protected] 預處理,第一遍查詢時間也能達到上面查詢數百次後才能達到的極限值。

四、與 Oracle 對比

測試環境、資料結構和規模與上文一致,測試物件如下:

產品 版本
Oracle資料庫 Oracle Database 12c Release 1(12.1.0.2.0)
集算器 集算器 V2018

Oracle建表語句為:

create table ctx_600m (id number(13),data varchar2(200));

資料由集算器生成同結構的文字檔案後,使用 Oracle 的 SqlLoader 匯入表中。

Oracle建索引語句為:

create unique index idx_id_600m on ctx_600m(id);

使用 Oracle 進行批量隨機取數測試時,我們使用這樣的 SQL:

select * from ctx_600m where id in (…)

 

使用單執行緒連線 Oracle 進行查詢的集算器指令碼為:

 

  A
1 =10000.(1000000000000+rand(600000000)+1).sort()
2 =A1.group((#-1)\1000)
3 =connect("oracle12c")
4 =now()
5 =A2.(A3.query("select * from ctx_600m where id in   (?)",~)).conj()
6 [email protected](A4,now())
7 >A3.close()

由於 oracle 的 in 個數有限制,指令碼中進行分批執行後合併。

 

使用 10 執行緒連線 Oracle 進行查詢的集算器指令碼為:

  A B
1 =10000.(1000000000000+rand(600000000)+1).sort()  
2 =A1.group((#-1)\1000)  
3 =now()  
4 fork A2 =connect("oracle12c")
5   =B4.query("select * from ctx_600m where id in (?)",A4)
6   >B4.close()
7 =A4.conj()  
8 [email protected](A3,now())  

 

使用單執行緒對行存組表進行查詢的集算器指令碼為:

  A
1 =file("id_600m.ctx").create()
2 =now()
3 [email protected](id_idx)
4 [email protected](A2,now())
5 =10000.(1000000000000+(rand(600000000)+1)).sort()
6 =now()
7 =A1.icursor(A5.contain(id),id_idx).fetch()
8 [email protected](A6,now())

 

使用 10 執行緒對行存組表進行查詢的集算器指令碼為:

  A B
1 =file("id_600m.ctx").create()  
2 =now()  
3 [email protected](id_idx)  
4 [email protected](A2,now())  
5 =10000.(1000000000000+(rand(600000000)+1)).sort()  
6 =A5.group((#-1)\1000)  
7 =now()  
8 fork A6 =A1.icursor(A8.contain(id),id_idx)
9   =B8.fetch()
10   return B9
11 =A8.conj()  
12 [email protected](A7,now())  

 

從 6 億條資料總量中取 1 萬條批量隨機鍵值,在都建立索引的測試結果:

耗時(毫秒)
單執行緒 多執行緒(10 執行緒)
Oracle 集算器組表 Oracle 集算器組表
117322 20745 39549 10975

五、列存索引測試

集算器列存採用了資料分塊並壓縮的演算法,這樣對於遍歷運算來講,訪問資料量會變小,也就會具有更好的效能。但對於基於索引隨機取數的場景,由於要有額外的解壓過程,而且每次取數都會針對整個分塊,運算複雜度會高很多。因此,從原理上分析,這時候的效能應當會比行存要差。

上述程式碼中把生成組表的 create() 函式不用 @r 選項,即可生成列存檔案。重複上面的運算,單執行緒情況下 6 億行中取 1 萬行耗時為 129120 毫秒,比行存方式慢了 6 倍多。不過平均到一行也只有 13 毫秒,對於大多數單條取數的場景仍然有足夠的實用性。

同一份資料不能在遍歷運算和隨機取數這兩方面都達到最優效能,在實際應用中就要根據需求做一下取捨了,一定要追求各種運算的極限效能時,可能就要把資料冗餘多份了。

六、索引冗餘機制

集算器確實也提供了冗餘索引機制,可以用於提高列存資料的隨機訪問效能,程式碼如下:

  A
1 =file("id_600m.ctx")
2 =A1.create().index(id_data_idx;id;data)

在對組表建立索引時,當 index 函式有資料列名引數,如本例 A2 中的 data,就會在建索引時把資料列 data 複製進索引。當有多個數據列時,可以寫為:index(id_idx;id;data1,data2,…)

因為在索引中做了冗餘,索引檔案也自然會較大,本文中測試的列存組表和索引冗餘後的檔案大小為:

型別 檔名 大小
組表 id_600m.ctx 105G
索引 id_600m.ctx__id_data_idx 112G

當資料複製進索引後,實際上讀取時不再訪問原資料檔案了。

從 6 億條資料總量中取 1 萬條批量隨機鍵值,完整的測試結果對比:

 

耗時(毫秒)
單執行緒 多執行緒(10 執行緒)
Oracle 行存索引 冗餘索引 Oracle 行存索引 冗餘索引
117322 20745 19873 39549 10975 9561



作者:tossman
連結:http://c.raqsoft.com.cn/article/1543371487193
來源:乾學院
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。