您好,登錄后才能下訂單哦!
看起來很簡單的集合運算放在大數據的場景下,如果還想獲得高性能就需要充分了解數據特征和計算特征才能設計出高效算法。充分利用序運算就是一種好辦法!
交并差是常見的集合運算,SQL 中對應的 intersect/union/minus 計算也很簡單。不過當數據量較大時,這類集合運算性能往往偏低,尤其當參與計算的數據量超過內存容量時,性能表現會十分糟糕。
本文專門針對這種情況下的高性能計算(HPC)需求,討論如何使用集算器 SPL 語言通過有序計算思路顯著提高大數據量下交并差三類集合運算的性能。下面討論中使用了一個實際用戶在數據庫選型時的評測用例:數據基于數據庫的 2 個表,共計 105 億行數據,執行相關運算后,以輸出第一批 500 條記錄所用時間來衡量哪個數據庫性能更優。
類別 | 配置 |
機型 | X86 |
CPU | E5-2680 v4 @ 2.40GHz |
內存 | 512GB |
數據硬盤存儲 | SAS 3TB |
集群數量 | 4 臺 |
網絡環境 | 萬兆 |
MPP 數據庫資源配置(單節點) | 硬盤:SSD 1.9T 內存:20GB(JVM) + 12GB(分片庫) |
集算器資源配置(單節點) | 硬盤:SAS 1T 內存:120GB |
操作系統 | CentOS6.8(64 位) |
表名稱 | 數據量 | 數據結構 | 數據內容 |
A | 103.68億 | 52個字段,a1為timestamp。其他字段為字符型,長度=10 | a1為1天的時間均勻分布數據,時間跨度為5天(每秒24000條),其他字段數據隨機生成。 |
B | 1728.048萬 | 2個字符字段,長度=10 | 2個字段生成規律依照A表的a3和a7字段 |
數據表 | 數據庫索引 |
A表 | a1、a3、a7字段建立btree索引,a4建立基于btree的varchar_pattern_ops索引。 |
B表 | b1、b2字段建立btree索引 |
a1-a52 列值:
2018-01-07 00:00:00,8888888888,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt,MQJqxnXMLM,ccTTCC7755,aaa******8,ppppaaaavv,gggggttttt
2018-01-07 00:00:00,4444444444,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR,dv@bi-lyMF,qqoovv22ww,)))777FFF4,jjjjIIIIVV,aaaaaRRRRR
2018-01-07 00:00:00,9999999999,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP,bxk3J/2YDd,ppvv**--88,uuuNNNBBBA,BBBBhhhhjj,_____PPPPP
l 交集(intersect)
select * from A, B where a1>'2018-01-07 02:14:43' and a1 < '2018-01-07 04:14:43' and a3=b1 or a7 = b2
intersect
select * from A, B where a1>'2018-01-07 12:14:43' and a1 < '2018-01-07 14:14:43' and a3=b1 or a7=b2
l 并集(union)
select * from A, B where a1>'2018-01-07 02:14:43' and a1 < '2018-01-07 04:14:43' and a3=b1 or a7 = b2
union
select * from A, B where a1>'2018-01-07 12:14:43' and a1 < '2018-01-07 14:14:43' and a3=b1 or a7=b2
l 差集(minus)
select * from A, B where a1>'2018-01-07 02:14:43' and a1 < '2018-01-07 04:14:43' and a3=b1 or a7 = b2
minus
select * from A, B where a1>'2018-01-07 12:14:43' and a1 < '2018-01-07 14:14:43' and a3=b1 or a7=b2
分析上述 SQL 可以發現,此計算場景為大數據量的多對多集合運算。查詢條件的前半段(a1>'2018-01-07 02:14:43' and a1 < '2018-01-07 04:14:43' and a3=b1)是 A 表 2 個小時內的數據與 B 表進行多對多關聯;而后半段(or a7 = b2)則是 A 表全量數據和 B 表進行多對多關聯。因此,這個用例主要考察的是大表 A 和小表 B 多對多關聯后的集合運算性能。
實測時,該 SQL 使用 MPP 數據庫得不到查詢結果(運行時間超過 1 小時),因為數據量很大,內存無法容納全部數據,從而造成數據庫在運算時頻繁進行磁盤交互,導致整體性能極低。
按照我們一貫的思路,要實施高性能計算必須設計符合數據特征和計算特征的算法,而不是簡單地使用通用的算法。這里,為了避免過多的磁盤交互(這也是大數據規模計算的首要考慮目標),最好只遍歷一次 A 表就能完成計算。觀察數據可以發現,A 表包含時間字段(a1),而且在時間字段(a1)和關聯字段(a3、a7)上均建有索引,同樣 B 表的兩個字段(b1、b2)也建有索引,這樣,我們就可以設計出這樣的算法:
1) 根據 A 表數據生成的特點,逐秒讀取 A 表數據(每秒 24000 條);
2) 針對每秒的數據循環處理,根據過濾條件逐條與 B 表關聯,返回關聯后結果;
3) 對兩部分數據,即用于交并差的兩個集合進行集合運算。
通過以上三步就可以完成全部計算,而整個過程中對 A 表只遍歷了 2 次(分別得到用于交并差的兩個集合)。當然,整個過程中由于數據量太大,集算器將通過延遲游標的方式進行歸并,游標歸并時數據需要事先排序,所以在 1)和 2) 步之間還需要對每秒的 24000 條數據按照關聯字段和其他字段排序,會產生一些額外的開銷。下面是具體的集算器 SPL 腳本。
這里分主子兩個程序,主程序調用子程序分別獲得交 / 并 / 差運算的兩個集合并進行集合運算,子程序則根據參數計算集合,也就是說用例中的交并差三類計算可以使用同一個子程序腳本。
A | B | C | |
1 | =otherCols=["a2","a4","a5","a6"]|(to(8,52).("a"/~)) | ||
2 | =connect("apptest") | ||
3 | =sql="select a1,a3,a7,"+otherCols.string("||")+"aall from A where a1=?" | ||
4 | =B=A2.query("select b1,b2,count(1) b3 from B group by b1,b2 order by b1,b2") | ||
5 | for 5*24*60*60 | =A2.query(sql,elapse@s(datetime("2018-01-07 00:00:00"),A5-1)) | |
6 | =B5.sort(a3,a7,aall) | ||
7 | for B6 | =B.select(B7.a1>begin && B7.a1 < end && B7.a3 == B.b1 || B7.a7 == B.b2) | |
8 | return C7.news(b3;B7.a1:a1,B7.a3:a3,B7.a7:a7,B7.aall:aall,C7.b1:b1,C7.b2:b2) | ||
9 | =A2.close() |
A1:在 otherCols 中記錄 A 表 52 個字段中除參與運算的 a1,a3,a7 外其他所有字段名稱,用于生成 SQL 查詢
A2:連接數據庫
A3:SQL 語句串,用于根據條件查詢 A 表所有列數據
A4:查詢 B 表數據,針對 b1,b2 進行分組計數(以便在后續計算中減少比較次數),并按 b1,b2 排序(用于后續有序歸并)
A5:按照 5 天時間內的秒數進行循環
B5:每次循環中在起始時間(2018-01-07 00:00:00)上加相應的秒數,查詢那一秒產生的數據(24000 條)
B6:按照關聯字段以及其他字段排序
B7:循環處理一秒內的每條 A 表數據
C7:根據單條 A 表數據,在 B 表中查找符合條件的記錄
C8:返回計算后包含 A 表和 B 表所有字段值的結果集,這里使用了 A.news() 函數,用來計算得到序表 / 排列的字段值合并生成的新序表 / 排列,具體用法請參考http://doc.raqsoft.com.cn/esproc/func/news.html
A | |
1 | =cursor("case1_cursor.dfx",datetime("2018-01-07 02:14:43"),datetime("2018-01-07 04:14:43")) |
2 | =cursor("case1_cursor.dfx",datetime("2018-01-07 12:14:43"),datetime("2018-01-07 14:14:43")) |
3 | =[A1,A2].mergex@i(a1,a3,a7,aall,b1,b2) |
4 | =A3.fetch@x(500) |
5 | return A4 |
A1,A2:通過 cursor()函數調用子程序腳本,并傳入 2 個時間段參數;cursor() 函數原理請參考: 《百萬級分組大報表開發與呈現》
A3:根據子程序返回的游標序列進行歸并,使用 @i 選項完成交集運算
A4:從游標中取出 500 條記錄,并關閉游標(@x 選項)
A | |
1 | =cursor("case1_cursor.dfx",datetime("2018-01-07 02:14:43"),datetime("2018-01-07 04:14:43")) |
2 | =cursor("case1_cursor.dfx",datetime("2018-01-07 12:14:43"),datetime("2018-01-07 14:14:43")) |
3 | =[A1,A2].mergex@u(a1,a3,a7,aall,b1,b2) |
4 | =A3.fetch@x(500) |
5 | return A4 |
A3:使用 @u 選項完成并集計算,其他 SPL 腳本完全相同
A | |
1 | =cursor("case1_cursor.dfx",datetime("2018-01-07 02:14:43"),datetime("2018-01-07 04:14:43")) |
2 | =cursor("case1_cursor.dfx",datetime("2018-01-07 12:14:43"),datetime("2018-01-07 14:14:43")) |
3 | =[A1,A2].mergex@d(a1,a3,a7,aall,b1,b2) |
4 | =A3.fetch@x(500) |
5 | return A4 |
A3:使用 @d 選項完成并集計算,其他 SPL 腳本完全相同
下表對集算器 SPL 和數據庫 SQL 分別輸出第一個 500 條結果集的時間進行了比較:
計算類型 | 集算器SPL | 數據庫SQL |
交集(intersect) | 3.8秒 | >1小時 |
并集(union) | 3.9秒 | >1小時 |
差集(minus) | >1小時 | >1小時 |
顯然,交集和并集計算的性能得到了極大的提升。
差集運算依然很慢的原因是由數據特征所決定的。由于多對多關聯后重復記錄較多,要計算出符合條件的差集仍舊要遍歷完 A 表(而另外兩個計算獲得 500 條結果集就可以不再遍歷了),因此性能主要消耗在 IO 取數上。
高性能算法需要根據數據和計算特征進行針對性設計,這要求程序猿首先能夠想出高性能算法,然后以不太復雜的手段加以實現,否則就沒有可行性了。
對于 SQL 體系來說,由于其封閉性原因,一些高效算法可能即使能設計出來也很難,甚至無法實現。而集算器 SPL 則極大地改善了這個問題,使用者可以在設計出高性能算法后,基于 SPL 體系快速實現。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。