長期以來,Lucene在搜索領域的壟斷地位無人能及,基於Lucene之上的Elastic Search與Solr 也是家喻户曉的產品;錄信數據庫最初的版本也是基於Lucene來實現的,在設計之初經常會遇到跟solr、es同樣的問題。如面對幾十億的數據就遇到內存爆掉的問題,此時CPU與IO都飆到系統極限;100億的數據就需要100多台512G的大內存與SSD盤的硬件支撐,內存參數略微調不好就出現節點掉片,內存OOM的情況。

Lucene在檢索上的設計缺點

目前Lucene只能對單列建立倒排索引,並不支持多列聯合索引,所以在搜索多個條件進行匹配的時候,需要多個查詢Query一起進行檢索,最終通過doclist與bitset 方式實現多條件查詢結果的AND與OR關係的歸併。跳躍表歸併存在較大的io浪費,也會因跳躍表的隨機讀降低檢索性能。

其次Lucene倒排索引的實現是基於FST的BLOCK TREE,這個數據結構官方給的描述是為了節省內存,實則只是相對而言,因每次打開索引就需要將tip文件全部加載到內存裏,在萬億規模下,一個表會有很多個數據列,這種加載方式非常的慢,而且內存使用量也非常高;另外在檢索的時候FST這種類型需要申請大量的數組,雖然是臨時使用,但沒有采用對象池,每次垃圾回收,會造成非常大的GC壓力,GC停頓時間很長。而錄信數據庫需要在一百台128G內存的硬件上支撐上萬億的數據,內存使用規模上行不通。

Lucene在統計上的設計缺點

Lucene進行分組統計時就需要依賴Lucene的docvalue;docvalue不是有序存儲,需將ord文件加載到內存裏,做ord映射;這種設計只要數據規模一大,效率就很差,就會出現爆內存的問題;而且因為是隨機讀,磁盤IO會飆的非常的高;ES經常遇到的頻繁掉片和崩掉的問題都跟docvalue自身的設計有很大的關係。

Lucene的倒排表雖然也可以做單列統計,但歸根結底是為了用於實現檢索功能,其本身的設計並不是為了大範圍掃描(Scan)與統計而設計的;

首先倒排表的TermStat因為要引用doc,pos,pay等一系列文件,沒有任何的壓縮,如果數據的值太多(Term太多),TermStat本身的IO會高於原始數據很多倍,在統計的時候會變得更慢,如果出現大範圍的掃描將會出現致命的問題(如掃描一個大範圍的手機號139*)。

最後Lucene還有另外一個功能,可以用來實現數據統計加速,即payload。這個設計非常巧妙,但缺點是數據沒有經過任何壓縮,會導致一旦使用了payload,數據膨脹率很高,雖然在某些程度上能構造一些順序讀取,但是文件體積過大,一般不適合存儲太大的數據。

其他列統計場景存在的問題

面對海量數據的OLAP需求,為了減少存儲費用、提高統計性能,數據庫系統採用壓縮和列存儲的方法來保存數據。

傳統的列存儲是針對單列存儲,多列之間並未建立關聯,數據也沒有按照順序排列,數據庫壓縮比不高;

因數據存儲不連續,存在大量的跳躍訪問和隨機訪問,嚴重影響查詢性能。統計的時候,也會因數據不連續、同組數據不相鄰,需每條數據一條一條的計算,效率較差。

因此需對數據進行存儲干預,並支持多列聯合索引,數據經過多列排序干預後,查詢可以變為連續讀取,這樣壓縮比會非常高;在統計計算的時候可以一批一批的算;且由於數據之間存在關聯,在多條件檢索與關係分析上也可以節省很多IO。

多列聯合索引

鑑於上述原因,我們重新實現了Lucene的索引部分,以解決上面存在的問題。

1. 多列聯合倒排索引的設計目標

  • 加快統計與檢索場景的速度。
  • 實現多層次關係分析。
  • 提升Top N排序速度。
  • 加快數據導出的速度。

2. 多列聯合倒排索引的實現特點

  • 每列之間採用列存儲,會根據數據特點自動選擇合適的壓縮算法。
  • 預先干預數據的排序分佈,讓列存儲的壓縮更有效。
  • 依據查詢構造順序讀取。
  • 多個列之間存在層次關係。
  • 結合分塊存儲,可以隨機訪問。
  • 使用極少的內存。
  • 對象內存重用,減少GC壓力。
  • 可以壓縮的payloads。

3. 應用場景

      1:統計與檢索。

       2:多層次的關係分析。

      3:TOP N排序。

      4:因存儲連續,適合數據導出。

4. 基本原理圖

多索引組合值不可以唯一嘛_多索引組合值不可以唯一嘛

使用方法

1. 基本概念

(1) olap_key:

1) 按照olap_key預先干預數據的分佈

2) olap_key的每一列均是列存儲

3) olap_key的每一列採用多種不同的壓縮算法

4) 儘量將識別率高的列排在前面

① 如用來過濾篩選的手機號,雖然壓縮比一般,可以有效利用索引先過濾;

② 或者那些重複值很高的列,如性別,省份,協議等 可以有非常高的壓縮比。

(2) olap_value:

1) 跟隨着olap_key後面存儲,連續存儲

2) 可以存儲多個值,但並非按列存儲,壓縮方法採用lz4

3) 一般用來存儲一些長文本數據

4) 不可以進行scan過濾

2. 建表方法

create table olap_test(
     s_high y_string_is,
     s_middle y_string_is,
     s_low y_string_is,

     l_high y_long_is,
     l_middle y_long_is,
     l_low y_int_is,

     d_high y_double_is,
     d_middle y_double_is,
     d_low y_double_is,

     hlll_1 y_ldrill_imp 'olap_key@s_high,s_low,l_low,d_low' 'olap_value@l_high,d_high',
     hmll_2 y_ldrill_imp 'olap_key@l_high,l_middle,l_low,s_low' ,
     mlll_1 y_ldrill_imp 'olap_key@s_middle,s_low,l_low,d_low' 'olap_value@l_high,d_high',
     mmll_2 y_ldrill_imp 'olap_key@d_middle,l_middle,l_low,s_low' ,

     lllh_1 y_ldrill_imp 'olap_key@s_low,l_low,d_low,s_high' 'olap_value@s_low,d_high',
     llmh_2 y_ldrill_imp 'olap_key@l_low,s_low,l_middle,l_high'  ,
     lllm_1 y_ldrill_imp 'olap_key@s_low,l_low,d_low,s_middle' 'olap_value@s_low,d_high',
     llmm_2 y_ldrill_imp 'olap_key@l_low,s_low,d_middle,l_middle'  ,

     hl_1 y_ldrill_imp 'olap_key@s_high,s_low' 'olap_value@l_high,d_high',
     hm_2 y_ldrill_imp 'olap_key@l_high,l_middle'  ,
     ml_1 y_ldrill_imp 'olap_key@s_middle,s_low' 'olap_value@l_high,d_high',
     mm_2 y_ldrill_imp 'olap_key@d_middle,l_middle'  ,

     ll_1 y_ldrill_imp 'olap_key@s_low,l_low' 'olap_value@d_low',
     ll_2 y_ldrill_imp 'olap_key@l_low,s_low' 

     );

也可以結合列簇,將不同的列分開,存儲到不同的存儲介質中:

create columnfamily olap_test (
        default at 'index@true' 'store@false' 
        ,hlll_1 at 'fields@hlll_1' 'index@true' 'store@false' 
        ,hmll_2 at 'fields@hmll_2' 'index@true' 'store@false' 
        ,mlll_1 at 'fields@mlll_1' 'index@true' 'store@false' 
        ,mmll_2 at 'fields@mmll_2' 'index@true' 'store@false' 
        ,lllh_1 at 'fields@lllh_1' 'index@true' 'store@false' 
        ,llmh_2 at 'fields@llmh_2' 'index@true' 'store@false' 
        ,lllm_1 at 'fields@lllm_1' 'index@true' 'store@false' 
        ,llmm_2 at 'fields@llmm_2' 'index@true' 'store@false' 
        ,hl_1 at 'fields@llmm_2' 'index@true' 'store@false' 
        ,hm_2 at 'fields@llmm_2' 'index@true' 'store@false' 
        ,ml_1 at 'fields@llmm_2' 'index@true' 'store@false' 
        ,mm_2 at 'fields@llmm_2' 'index@true' 'store@false' 
        ,ll_1 at 'fields@llmm_2' 'index@true' 'store@false' 
        ,ll_2 at 'fields@llmm_2' 'index@true' 'store@false' 
        );

多索引組合值不可以唯一嘛_#olap_02

3. 查詢使用方法

(1) 測試數據如下:

多索引組合值不可以唯一嘛_多索引組合值不可以唯一嘛_03

(2) 導入語句如下:

sh load.sh -t olap_test -p all_50yi_002 -tp txt -local -f /wyh/ldrill.log -sp , -fl s_high,s_middle,s_low,l_high,l_middle,l_low,d_high,d_middle,d_low

(3) 查看錶數據:

多索引組合值不可以唯一嘛_#數據庫_04

(4) 僅掃描olap_key(按列存儲,不需要的列不掃描)

select s_high,s_low,l_low from olap_test where partition like 'all_50yi_002'  and syskv='ldrill.name:hlll_1'    limit 20;

多索引組合值不可以唯一嘛_#數據庫_05

(5) 同時掃描olap_key與olap_value

注:olap_value不同的是整體壓縮,查一個列與查詢所有的列,IO是一樣的,且Olap_value裏面的列不能參與過濾,他也會加載doclist文件,速度略慢,但因為是順序讀,且有lz4壓縮,性能一般也比docvalues快。

select s_high,s_low,l_high from olap_test where partition like 'all_50yi_002'  and syskv='ldrill.name:hlll_1'    limit 20;

多索引組合值不可以唯一嘛_#索引_06

(6) 帶檢索條件 from to,如果from==to相當於等值查詢,儘量將需要過濾的列放在第一位,適合http協議那種根據key直接導出一批大的結果

--等值查詢
select  s_high,s_low,l_low from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:hlll_1'  
and syskv='ldrill.form:SYS_URL_ENCODE@['za']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_URL_ENCODE@['za']@SYS_URL_ENCODE'   
limit 20;

多索引組合值不可以唯一嘛_#索引_07

--範圍查詢
select  s_high,s_low,l_low from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:hlll_1'  
and syskv='ldrill.form:SYS_URL_ENCODE@['za']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_URL_ENCODE@['zd']@SYS_URL_ENCODE'   
limit 20;

多索引組合值不可以唯一嘛_#索引_08

(7) 通配符*的使用

select  s_high,s_low,l_low from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:hlll_1'  
and syskv='ldrill.form:SYS_URL_ENCODE@['*','zc']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_URL_ENCODE@['*','zc']@SYS_URL_ENCODE'   
limit 20;

多索引組合值不可以唯一嘛_#olap_09

select  s_high,s_low,l_low from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:hlll_1'  
and syskv='ldrill.form:SYS_URL_ENCODE@['*','*','9']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_UR L_ENCODE@['*','*','16']@SYS_URL_ENCODE'   
limit 20;

多索引組合值不可以唯一嘛_多索引組合值不可以唯一嘛_10

(8) 也可以與其他檢索條件組合使用,但是要藉助doclist進行bitset比對,會有doclist的額外開銷,性能相對於純粹的from to會差一些。

select  s_high,s_low,l_low from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:hlll_1'  
and syskv='ldrill.form:SYS_URL_ENCODE@['*','*','9']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_URL_ENCODE@['*','*','16']@SYS_URL_ENCODE'  
and s_low='zc'   
limit 20;

多索引組合值不可以唯一嘛_#索引_11

(9) 統計

select l_low,count(*) from olap_test where partition like 'all_50yi_002'  and syskv='ldrill.name:ll_1'  group by l_low  limit 20;

多索引組合值不可以唯一嘛_#數據庫_12

(10) ldrill的統計是可以多列的,這是它的特點

select s_low,l_low,count(*) from olap_test where partition like 'all_50yi_002'  and syskv='ldrill.name:ll_1'  group by s_low,l_low  limit 20;

多索引組合值不可以唯一嘛_#lucene_13

(11) 對於某一個篩選值下面的統計,如篩選某一手機號,統計某一局部數據,這樣篩選範圍數據量就少了,而且這些數據還是連續存儲

select d_middle,l_middle,count(*) from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:mm_2' 
and syskv='ldrill.form:SYS_URL_ENCODE@['30.0']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_URL_ENCODE@['50.0']@SYS_URL_ENCODE' 
group by d_middle,l_middle  
limit 20;

多索引組合值不可以唯一嘛_多索引組合值不可以唯一嘛_14

select l_middle,count(*),avg(d_middle),max(d_middle),min(d_middle),sum(d_middle) 
from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:mm_2' 
and syskv='ldrill.form:SYS_URL_ENCODE@['*','15256372889']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_URL_ENCODE@['*','15289790327']@SYS_URL_ENCODE' 
group by l_middle  
limit 20;

多索引組合值不可以唯一嘛_#數據庫_15

select d_middle,count(*),avg(l_middle),max(l_middle),min(l_middle),sum(l_middle) 
from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:mm_2' 
and syskv='ldrill.form:SYS_URL_ENCODE@['30.0']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_URL_ENCODE@['50.0']@SYS_URL_ENCODE' 
group by d_middle  limit 20;

多索引組合值不可以唯一嘛_#lucene_16

(12) 數據導出,僅導出olap_key

export json overwrite  /data/export/test2 select s_middle,s_low,l_low,d_low 
from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:mlll_1' 
and syskv='ldrill.form:SYS_URL_ENCODE@['zb']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_URL_ENCODE@['ze']@SYS_URL_ENCODE';

多索引組合值不可以唯一嘛_#索引_17

多索引組合值不可以唯一嘛_#數據庫_18

注:該處測試所用的LSQL為單機版,故導出後文件存放在本地指定路徑(如為集羣版,則存放在HDFS上)。導出的文件數量與executor數有關。

(13) 數據導出,很多時候,往往需要將olap_value中的大塊的值也導出去,這個時候只能讀doclist,但是好在是順序讀取

export json overwrite /wyh/export/test2  select s_middle,s_low,l_low,d_low,l_high,d_high 
from olap_test where partition like 'all_50yi_002'  
and syskv='ldrill.name:mlll_1' 
and syskv='ldrill.form:SYS_URL_ENCODE@['zb']@SYS_URL_ENCODE'  
and syskv='ldrill.to:SYS_URL_ENCODE@['ze']@SYS_URL_ENCODE';

多索引組合值不可以唯一嘛_#lucene_19

多索引組合值不可以唯一嘛_#olap_20

(14) group by只能做一種請求,如果一次請求要返回多種彙總數據,可以藉助facet

--不返回數據明細 
select * from olap_test where partition like 'all_50yi_002' 
    and syskv='cl.facet.ldrill:SYS_URL_ENCODE@{'name':'ll_1','fl':'s_low,l_low','from':['*'],'to':['*']}@SYS_URL_ENCODE' 
    and syskv='cl.facet.ldrill:SYS_URL_ENCODE@{'name':'ll_2','fl':'l_low,s_low','from':['*'],'to':['*']}@SYS_URL_ENCODE'   
 limit 0;

多索引組合值不可以唯一嘛_#索引_21

結合過濾篩選的facet:

select * from olap_test where partition like 'all_50yi_002' 
    and syskv='cl.facet.ldrill:SYS_URL_ENCODE@{'name':'ll_1','fl':'s_low,l_low','from':['zc','5'],'to':['zi','12']}@SYS_URL_ENCODE' 
    and syskv='cl.facet.ldrill:SYS_URL_ENCODE@{'name':'ll_1','fl':'s_low,l_low','from':['zc','12'],'to':['zf','18']}@SYS_URL_ENCODE' 

    and syskv='cl.facet.ldrill:SYS_URL_ENCODE@{'name':'ll_2','fl':'l_low','from':['9'],'to':['17']}@SYS_URL_ENCODE'   
 limit 0;

多索引組合值不可以唯一嘛_#索引_22

 

 

LSQL基於Lucene實現多列聯合索引機制,建立多個列之間的聯繫,在高緯值關係分析場景中大幅提升統計分析與top N排序性能,因多列數據連續存儲也可滿足大規模數據的快速導出。