第8期:列式存儲(chǔ)的另一面
列存是常見(jiàn)的數(shù)據(jù)存儲(chǔ)技術(shù),在許多場(chǎng)景下也確實(shí)很有效,因而也被不少數(shù)據(jù)倉(cāng)庫(kù)類產(chǎn)品采用,在業(yè)內(nèi)列存也常常就意味著高性能。
可是,列存真有這么好嗎?搜索一下,容易找到的列存缺點(diǎn)一般是針對(duì)數(shù)據(jù)修改的,而對(duì)于只讀的分析計(jì)算任務(wù),卻很少能見(jiàn)到較詳細(xì)的討論。我們?cè)谶@里來(lái)研究一下這個(gè)問(wèn)題。
對(duì)內(nèi)存計(jì)算意義不大
列存的原理很簡(jiǎn)單:由于磁盤(pán)不適合跳動(dòng)式讀取,采用行式存儲(chǔ)時(shí)在讀取數(shù)據(jù)時(shí)會(huì)掃描所有列,而一次運(yùn)算可能只涉及很少的列,這樣就會(huì)多讀很多用不上的數(shù)據(jù)。采用列存則只需要讀取需要用到的列,數(shù)據(jù)訪問(wèn)量大概率會(huì)大幅減少,而大數(shù)據(jù)計(jì)算中磁盤(pán)掃描時(shí)間的占比很大,減少訪問(wèn)量就能節(jié)約大量時(shí)間。另外,同一列數(shù)據(jù)相同值情況較多,采用列存更容易做合并壓縮,從而進(jìn)一步減少數(shù)據(jù)存儲(chǔ)量,提高性能。
從原理可以看出,列存能提高性能主要是因?yàn)闇p少了磁盤(pán)訪問(wèn)量,但對(duì)于計(jì)算量減少并沒(méi)有幫助。如果數(shù)據(jù)已經(jīng)被加載進(jìn)內(nèi)存,再采用列存就沒(méi)多大意義了。普通結(jié)構(gòu)化數(shù)據(jù)運(yùn)算都是以行為單位的,在內(nèi)存中使用列存反而會(huì)加大構(gòu)造完整記錄的復(fù)雜度,降低性能。所以,除了專業(yè)的向量式運(yùn)算(數(shù)據(jù)挖掘中常用,運(yùn)算本身就是以列為單位的)外,類似關(guān)系數(shù)據(jù)庫(kù)型的內(nèi)存運(yùn)算(包括內(nèi)存數(shù)據(jù)庫(kù))并不合適采用列式存儲(chǔ)。
加劇硬盤(pán)的不連續(xù)訪問(wèn)程度
列式存儲(chǔ)時(shí),各列是連續(xù)存儲(chǔ)的,這樣同時(shí)訪問(wèn)多個(gè)列進(jìn)行計(jì)算時(shí),就會(huì)導(dǎo)致造成不連續(xù)的隨機(jī)訪問(wèn),訪問(wèn)的列越多造成的不連續(xù)性就越強(qiáng)。而針對(duì)機(jī)械硬盤(pán)的不連續(xù)讀取會(huì)嚴(yán)重影響性能,在訪問(wèn)列數(shù)較多或總列數(shù)并不多時(shí),就可能發(fā)生還不如行存的性能好的現(xiàn)象,因?yàn)樾写媸沁B續(xù)訪問(wèn)的,跳動(dòng)的成本有可能超過(guò)。如果有并發(fā)任務(wù)(以及下面會(huì)說(shuō)到的并行計(jì)算)還會(huì)嚴(yán)重加劇這一問(wèn)題,當(dāng)然行存并發(fā)時(shí)也會(huì)發(fā)生磁盤(pán)跳動(dòng),但程度比列存輕得多,列存時(shí)每多一個(gè)并發(fā)計(jì)算任務(wù)會(huì)多出幾個(gè)(涉及列數(shù))對(duì)磁盤(pán)的并發(fā)訪問(wèn)請(qǐng)求,行存則只會(huì)多一個(gè)磁盤(pán)并發(fā)請(qǐng)求。
一個(gè)辦法是加大讀取緩存區(qū)以減少磁盤(pán)尋道時(shí)間的占比,但這樣為每個(gè)涉及列都設(shè)置緩存區(qū),列較多時(shí)會(huì)占用大量?jī)?nèi)存。另一個(gè)辦法是增加磁盤(pán)數(shù)量,把不同的列存儲(chǔ)到不同的磁盤(pán)上,不過(guò)列存一般應(yīng)用場(chǎng)景都是總數(shù)列很多的情況,常常遠(yuǎn)大于機(jī)器可以接受的硬盤(pán)數(shù)量,還會(huì)較大概率地造成磁盤(pán)隨機(jī)訪問(wèn)沖突。
固態(tài)硬盤(pán)沒(méi)有尋道時(shí)間的問(wèn)題,列式存儲(chǔ)更適合采用固態(tài)硬盤(pán)。
索引效率低
索引也是常用技術(shù),用于從大數(shù)據(jù)集中按鍵值找出指定記錄。我們?cè)谝郧拔恼轮兄v過(guò),索引的本質(zhì)是排序,索引表中將存儲(chǔ)有序的鍵值及該鍵值對(duì)應(yīng)的原表記錄位置。對(duì)于行式存儲(chǔ)來(lái)說(shuō),整條記錄的位置可以用一個(gè)數(shù)表示;但列存就不一樣了,整條記錄的每個(gè)列分別有各自的位置,原則上需要都記錄下來(lái),這樣一來(lái),索引表幾乎和原表一樣大,訪問(wèn)成本變高很多,空間占用也太大,這和復(fù)制原表后排序區(qū)別并不大了。
每條記錄只存儲(chǔ)一個(gè)序號(hào),然后用乘法計(jì)算出位置,這樣可以嗎?有些數(shù)據(jù)類型的字段值的長(zhǎng)度本身就是不固定的(串型),而固定長(zhǎng)度的字段值(整數(shù)、日期)也可能因?yàn)橐獕嚎s編碼(列存中常用的技術(shù))而變成不固定,一定要用定長(zhǎng)方式存儲(chǔ),索引倒是簡(jiǎn)單了,訪問(wèn)也很快,但會(huì)加大存儲(chǔ)量,遍歷時(shí)又不劃算了,而這是列存更主要的應(yīng)用場(chǎng)景。
實(shí)際常用的手段是把數(shù)據(jù)分塊,塊內(nèi)數(shù)據(jù)采用列存,索引只建立在塊上。這樣可以用索引迅速定位中所需要的數(shù)據(jù)在哪個(gè)塊中,然后只要塊內(nèi)進(jìn)行掃描即可。
這種索引比行存索引會(huì)多一個(gè)塊內(nèi)掃描的過(guò)程,性能要低一些。如果原數(shù)據(jù)按索引鍵值有序(索引鍵常常就是原表主鍵),那可以很容易地定位出目標(biāo)數(shù)據(jù)所在的少量的幾個(gè)塊(大概率只在一塊中),這時(shí)性能損失還可以容忍,可適用于按***ID值找出指定記錄的場(chǎng)景。但如果原數(shù)據(jù)對(duì)索引鍵無(wú)序,那這個(gè)索引幾乎沒(méi)有用處,目標(biāo)數(shù)據(jù)可能落在幾乎所有的塊中,這就和全表掃描區(qū)別不大了。
分段并行麻煩
要充分利用多CPU(核),多線程并行能力是個(gè)必須考慮的問(wèn)題,而要并行這就需要先把數(shù)據(jù)分段。
分段有兩個(gè)基本需求:每段數(shù)據(jù)量基本相同(每線程處理能力相當(dāng)),可以較靈活的分段(事先不能預(yù)測(cè)線程數(shù))。行式存儲(chǔ)時(shí)相對(duì)容易實(shí)現(xiàn)分段,只要每條(也可以每N條)記錄后做一個(gè)結(jié)束標(biāo)記,在分段時(shí)按字節(jié)數(shù)平均分成K段,然后在每段中尋找到結(jié)束標(biāo)記后作為開(kāi)始點(diǎn)即可。但列式存儲(chǔ)不能采用同樣的辦法,由于前述原因,字段值是不定長(zhǎng)的,某個(gè)列的分段點(diǎn)未必和另一個(gè)列的同樣的分段點(diǎn)同步落在同一條記錄上,這會(huì)錯(cuò)位導(dǎo)致錯(cuò)誤的數(shù)據(jù)。
列式存儲(chǔ)的分段一般也是采用前述的分塊方案:分段必須以塊為單位,在塊內(nèi)不再分段并行。這樣就會(huì)有一個(gè)矛盾,首先,分塊數(shù)不能太少了,否則就無(wú)法做到靈活分段了(只有5個(gè)分塊時(shí)不可能做出10個(gè)分段),按現(xiàn)代服務(wù)器的CPU(核)數(shù),要有上百個(gè)分塊才能比較自由地平衡分段;但是,分塊數(shù)又不能太多,列數(shù)據(jù)在物理上會(huì)被拆成多個(gè)不連續(xù)的小塊,不僅使得遍歷代碼復(fù)雜很多,而且還會(huì)多讀入少量?jī)蓧K之間的無(wú)用數(shù)據(jù),對(duì)于機(jī)械硬盤(pán)還有尋道時(shí)間問(wèn)題,分塊數(shù)越多這些問(wèn)題就越嚴(yán)重。只有分塊內(nèi)列數(shù)據(jù)占用空間比讀入緩沖區(qū)大很多時(shí),無(wú)用數(shù)據(jù)讀入時(shí)間和尋道時(shí)間的占比才會(huì)比較小,這就要求每個(gè)分塊中有足夠多的記錄數(shù),也就是說(shuō),實(shí)現(xiàn)列存并行,數(shù)據(jù)量要足夠大才有意義,對(duì)于機(jī)械硬盤(pán)(包括用機(jī)械硬盤(pán)構(gòu)成的陣列)上一般得達(dá)到單機(jī)單表十億記錄、空間約在百G以上。規(guī)模較小的數(shù)據(jù)量就不容易獲得并行計(jì)算的性能提升,而特別適合使用列存的多維分析業(yè)務(wù)的數(shù)據(jù)量就處于這種尷尬的規(guī)模中。另外,分塊容量在數(shù)據(jù)追加前就要確定下來(lái),隨著數(shù)據(jù)的不斷追加,相鄰分塊卻不能物理上合并,分塊數(shù)就會(huì)越來(lái)越多,這將給管理造成不少麻煩,需要可擴(kuò)展的空間專門(mén)存儲(chǔ)分塊的索引信息。
我們?cè)谶@里介紹列存的另一面,并非要否定列存在許多計(jì)算場(chǎng)景時(shí)的巨大優(yōu)勢(shì) ,但完全不區(qū)分情況地全面采用列存也是不負(fù)責(zé)任的。對(duì)于數(shù)據(jù)倉(cāng)庫(kù)類產(chǎn)品,正確的做法應(yīng)當(dāng)將這個(gè)自由度留給系統(tǒng)管理員,由用戶來(lái)決定是否采用列存、如何分塊、哪些數(shù)據(jù)采用列存、有些數(shù)據(jù)甚至?xí)写婧土写婀泊?,以冗余換取更高的性能。