- 基于apache-druid-0.17
概述
- Druid將索引存儲(chǔ)在按時(shí)間分區(qū)的Segment文件中。在基本的設(shè)置中或舞,會(huì)為每個(gè)時(shí)間間隔(
time interval
)創(chuàng)建一個(gè)segment文件荆姆。time interval
可以由granularitySpec
中的參數(shù)segmentGranularity
決定。對(duì)于Druid來(lái)說(shuō)嚷那,在大量的查詢之下依舊表現(xiàn)優(yōu)秀胞枕。Segment的文件大小推薦在300MB-700MB。如果你的Segment文件大于這個(gè)范圍魏宽,那么可以考慮更改時(shí)間間隔的粒度腐泻,或者對(duì)數(shù)據(jù)進(jìn)行分區(qū)决乎,并在partitionsSpec
中調(diào)整targetPartitionSize
(此參數(shù)的一個(gè)良好起點(diǎn)是500萬(wàn)行)。
Segment文件核心的數(shù)據(jù)結(jié)構(gòu)
-
描述Segment文件的內(nèi)部結(jié)構(gòu)派桩,它本質(zhì)上是柱狀的:每一列的數(shù)據(jù)以單獨(dú)的數(shù)據(jù)結(jié)構(gòu)布局构诚。通過(guò)單獨(dú)存儲(chǔ)每一列,Druid可以通過(guò)只掃描那些確實(shí)需要的列來(lái)減少查詢延遲铆惑。有三種基本的列類(lèi)型:時(shí)間戳列范嘱、維度列和度量列,如下圖所示:
- 時(shí)間戳和度量列很簡(jiǎn)單:在數(shù)據(jù)內(nèi)部员魏,它們都是用LZ4壓縮的整數(shù)或浮點(diǎn)值數(shù)組丑蛤。一旦查詢知道需要選擇哪些行,它只需對(duì)這些行進(jìn)行解壓撕阎,提取相關(guān)行受裹,并應(yīng)用所需的聚合函數(shù)操作。與所有列一樣虏束,如果查詢不需要列棉饶,則跳過(guò)該列的數(shù)據(jù)。
- 維度列是不同的镇匀,因?yàn)樗鼈冎С趾Y選和分組操作照藻,所以每個(gè)維度需要以下三個(gè)數(shù)據(jù)結(jié)構(gòu):
- 1-將值(通常被視為字符串)映射為整數(shù)id的字典;
- 2-列值的列表汗侵,使用1中的字典進(jìn)行編碼幸缕;
- 3-對(duì)于列中的每個(gè)不同的值,使用bitmap指示哪些行包含該值晃择。
- 為什么是這三種數(shù)據(jù)結(jié)構(gòu)?字典簡(jiǎn)單地將字符串值映射為整數(shù)id冀值,以便(2)和(3)中的值能夠緊湊地表示。(3)中的bitmap——也稱為反向索引宫屠,允許快速過(guò)濾操作(具體來(lái)說(shuō)列疗,位圖便于快速應(yīng)用AND和OR操作符)。最后浪蹂,group by和TopN查詢需要(2)中的值列表抵栈。換句話說(shuō),僅基于過(guò)濾器聚合度量的查詢不需要觸及存儲(chǔ)在(2)中的維值列表坤次。
- 要獲得這些數(shù)據(jù)結(jié)構(gòu)的具體信息古劲,請(qǐng)考慮上面示例數(shù)據(jù)中的“page”列。表示此維度的三個(gè)數(shù)據(jù)結(jié)構(gòu)在下面的關(guān)系圖中進(jìn)行了說(shuō)明缰猴。
1: Dictionary that encodes column values
{
"Justin Bieber": 0,
"Ke$ha": 1
}
2: Column data
[0,
0,
1,
1]
3: Bitmaps - one for each unique value of the column
value="Justin Bieber": [1,1,0,0]
value="Ke$ha": [0,0,1,1]
- 注意产艾,bitmap與前兩個(gè)數(shù)據(jù)結(jié)構(gòu)不同:前兩個(gè)數(shù)據(jù)結(jié)構(gòu)在數(shù)據(jù)大小上是線性增長(zhǎng)的(在最壞的情況下),而bitmap部分的大小是數(shù)據(jù)大小*列基數(shù)的乘積。壓縮將在這里幫助我們闷堡,因?yàn)槲覀冎腊欤瑢?duì)于“列數(shù)據(jù)”中的每一行,將只有一個(gè)非零項(xiàng)的bitmap杠览。這意味著高基數(shù)列將具有非常稀疏的bitmap弯菊,因此具有高度可壓縮的bitmap。Druid利用這種壓縮算法踱阿,特別適合bitmap管钳,例如
roaring bitmap compression
。
Multi-value列
- 如果一個(gè)數(shù)據(jù)源使用了多值列软舌,那么段文件中的數(shù)據(jù)結(jié)構(gòu)看起來(lái)就有些不同才漆。讓我們想象一下,在上面的例子中葫隙,第二行被標(biāo)記為“Ke$ha”和“Justin Bieber”主題栽烂。在這種情況下,三個(gè)數(shù)據(jù)結(jié)構(gòu)現(xiàn)在看起來(lái)如下:
1: Dictionary that encodes column values
{
"Justin Bieber": 0,
"Ke$ha": 1
}
2: Column data
[0,
[0,1], <--Row value of multi-value column can have array of values
1,
1]
3: Bitmaps - one for each unique value
value="Justin Bieber": [1,1,0,0]
value="Ke$ha": [0,1,1,1]
^
|
|
Multi-value column has multiple non-zero entries
- 注意對(duì)列數(shù)據(jù)中的第二行和
Ke$ha
bitmap數(shù)據(jù)結(jié)構(gòu) 的更改恋脚。如果一個(gè)行中有一個(gè)以上的值對(duì)應(yīng)一個(gè)列,那么它在“列數(shù)據(jù)”中的條目就是一個(gè)值數(shù)組焰手。此外糟描,在“列數(shù)據(jù)”中有n個(gè)值的行在bitmap中有n個(gè)非零值項(xiàng)。
Sql兼容null值處理
- 默認(rèn)情況下书妻,Druid的字符串維度列使用
' '
和null
船响,數(shù)值和度量列不能表示空值,而是強(qiáng)制空值為0躲履。但是见间,Druid也提供了一個(gè)SQL兼容的空處理模式,配置參數(shù)為:druid.generic.useDefaultValueForNull
工猜,必須在系統(tǒng)級(jí)啟用米诉,通過(guò)這個(gè)設(shè)置,當(dāng)設(shè)置為fasle時(shí)篷帅,將允許Druid在提取數(shù)據(jù)的時(shí)候創(chuàng)建Segment史侣,其字符串列可以區(qū)分' '
從null
值中,數(shù)字列可以代表null
值行魏身,而不是0惊橱。 - 在此模式下,字符串維度列不包含任何附加的列結(jié)構(gòu)箭昵,而只是為空值保留一個(gè)附加的字典項(xiàng)税朴。然而,數(shù)值列將與一個(gè)額外的bitmap一起存儲(chǔ)在Segment中,Bitmap的集合位表示空值行正林。除了稍微增加了Segment大小之外茧跋,由于需要檢查空值Bitmap,SQL兼容的空處理還會(huì)在查詢時(shí)產(chǎn)生性能成本卓囚。這種性能開(kāi)銷(xiāo)只出現(xiàn)在實(shí)際包含null的列上瘾杭。
命名約定
- Segment的標(biāo)識(shí)符通常使用Segment數(shù)據(jù)源、間隔開(kāi)始時(shí)間(ISO 8601格式)哪亿、間隔結(jié)束時(shí)間(ISO 8601格式)和版本來(lái)構(gòu)造粥烁。如果將數(shù)據(jù)分片到時(shí)間范圍之外,則段標(biāo)識(shí)符也將包含分區(qū)號(hào)蝇棉。
- eg:
datasource_intervalStart_intervalEnd_version_partitionNum
Segment組成
- 一個(gè)Segment有以下幾個(gè)文件組成讨阻;
-
version.bin
:- 4個(gè)字節(jié)表示當(dāng)前段版本為整數(shù)。E.g., for v9 segments, the version is 0x0, 0x0, 0x0, 0x9
-
meta.smoosh
:- 帶有關(guān)于其他smoosh文件內(nèi)容的元數(shù)據(jù)(文件名和偏移量)的文件;
-
XXXXX.smoosh
:- 存在一些二進(jìn)制的文件:
- smoosh文件表示多個(gè)文件“smoosh”在一起篡殷,以最小化必須打開(kāi)來(lái)存放數(shù)據(jù)的文件描述符的數(shù)量钝吮。它們是最大2GB的文件(以匹配Java中映射的ByteBuffer的內(nèi)存限制)。smoosh文件包含數(shù)據(jù)中每個(gè)列的單獨(dú)文件和索引板辽。帶有關(guān)于Segment的額外元數(shù)據(jù)的drd文件奇瘦。
- 還有一個(gè)名為
_time
的特殊列,它引用段的時(shí)間列劲弦。隨著代碼的發(fā)展耳标,這將變得越來(lái)越不特別,但現(xiàn)在它就像我媽媽總是告訴我的那樣特別邑跪。
列的格式
- 每一個(gè)列存儲(chǔ)為兩部分:
- 1-A Jackson-serialized ColumnDescriptor次坡;
- 2-The rest of the binary for the column;
- ColumnDescriptor本質(zhì)上是一個(gè)對(duì)象画畅,它允許我們使用Jackson的多態(tài)反序列化來(lái)添加新的砸琅、有趣的序列化方法,并且對(duì)代碼的影響最小轴踱。它包括關(guān)于列的一些元數(shù)據(jù)(它是什么類(lèi)型的症脂,它是多值的,等等)寇僧,然后是一個(gè)序列化/反序列化邏輯列表摊腋,可以反序列化二進(jìn)制的其余部分。
分片數(shù)據(jù)創(chuàng)建Segment
分片
- 對(duì)于相同的datasource嘁傀,多個(gè)Segment可能在相同的時(shí)間間隔(interval time)兴蒸。這些Segment在一個(gè)區(qū)間內(nèi)構(gòu)成一個(gè)
block
。取決于用來(lái)切分?jǐn)?shù)據(jù)的shardSpec類(lèi)型细办,Druid的查詢只有在一個(gè)block
完成的情況下才能完成橙凳。也就是說(shuō)蕾殴,如果一個(gè)block包含三個(gè)segment,例如:
sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_0
sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_1
sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_2
- 在間隔為2011-01- 01t02:00:00:00的查詢完成之前岛啸,必須加載所有3個(gè)Segment钓觉。
- 這個(gè)規(guī)則的例外是使用線性分片規(guī)范。線性分片規(guī)范并不強(qiáng)制“完整性”坚踩,即使系統(tǒng)中沒(méi)有加載分片荡灾,查詢也可以完成。例如瞬铸,如果您的實(shí)時(shí)攝取創(chuàng)建了3個(gè)線性切分規(guī)范的Segment批幌,并且系統(tǒng)中只加載了其中的兩個(gè)Segment,那么查詢將只返回這兩個(gè)Segment的結(jié)果嗓节。
結(jié)構(gòu)變化
替換Segment
- Druid使用數(shù)據(jù)源荧缘、間隔、版本和分區(qū)號(hào)唯一地標(biāo)識(shí)Segment拦宣。只有在為某個(gè)時(shí)間粒度創(chuàng)建多個(gè)Segment時(shí)截粗,分區(qū)號(hào)才在Segment中id中可見(jiàn)。例如鸵隧,如果您有每小時(shí)的Segment绸罗,但是您在一個(gè)小時(shí)內(nèi)擁有的數(shù)據(jù)比單個(gè)Segment所能容納的數(shù)據(jù)更多,那么您可以為同一小時(shí)創(chuàng)建多個(gè)Segment掰派。這些Segment將共享相同的數(shù)據(jù)源从诲、時(shí)間間隔和版本,但分區(qū)數(shù)將線性增加靡羡。
foo_2015-01-01/2015-01-02_v1_0
foo_2015-01-01/2015-01-02_v1_1
foo_2015-01-01/2015-01-02_v1_2
- 上面案例中
dataSource = foo
,interval = 2015-01-01/2015-01-02
,version = v1
, andpartitionNum = 0
。如果在以后的某個(gè)時(shí)候俊性,您使用新的模式重新索引數(shù)據(jù)略步,那么新創(chuàng)建的Segment將具有更高的版本id。
foo_2015-01-01/2015-01-02_v2_0
foo_2015-01-01/2015-01-02_v2_1
foo_2015-01-01/2015-01-02_v2_2
- Druid批量索引(基于hadoop或基于indextquest)保證了每隔一段時(shí)間的原子更新定页。在我們的例子中趟薄,在所有的v2段(2015-01-01 - 2015-01-02)加載到一個(gè)Druid集群之前,查詢只使用v1段典徊。一旦所有的v2段被加載并可查詢杭煎,所有的查詢都會(huì)忽略v1段并切換到v2段。不久之后卒落,v1片段將從集群中卸載羡铲。
- 注意,跨越多個(gè)段間隔的更新只是每個(gè)間隔內(nèi)的原子更新儡毕。它們?cè)谡麄€(gè)更新中不是原子性的也切。例如,你有如下Segment:
foo_2015-01-01/2015-01-02_v1_0
foo_2015-01-02/2015-01-03_v1_1
foo_2015-01-03/2015-01-04_v1_2
-
v2
Segment將在構(gòu)建后立即加載到集群中,并在Segment重疊期間替換v1
Segment雷恃。在v2
Segment 完全加載之前疆股,您的集群可能混合了v1和v2 的Segment。
foo_2015-01-01/2015-01-02_v1_0
foo_2015-01-02/2015-01-03_v2_1
foo_2015-01-03/2015-01-04_v1_2
- In this case, queries may hit a mixture of
v1
andv2
segments.
Segment中不同的schema
- 同一DataSource在Druid的Segment可能有不同的schemas倒槐。如果一個(gè)字符串列(維度)存在于一個(gè)Segment中而不存在于另一個(gè)Segment中旬痹,則涉及兩個(gè)Segment的查詢?nèi)匀挥行А?duì)于缺少維度的Segment的查詢將表現(xiàn)為該維度只有空值讨越。類(lèi)似地两残,如果一個(gè)Segment有一個(gè)數(shù)字列(度量)而另一個(gè)沒(méi)有,那么缺少度量的段上的查詢通常會(huì)“do the right thing”谎痢。會(huì)有丟失度量的聚合行為磕昼。