MySQL索引背后的數據結構及算法原理, http://www.codinglabs.org/html/theory-of-mysql-index.html
HBase Architecture, http://duanple.blog.163.com/blog/static/70971767201191661620641/
數據庫如何抵抗隨機IO:問題、方法與現實, http://wangyuanzju.blog.163.com/blog/static/13029201132154010987/?utm_source=twitterfeed&utm_medium=twitter
我們要討論的是大數據的索引存儲和數據結構問題...
首先要看看關系型數據庫的B樹索引, 關於這個問題MySQL索引背后的數據結構及算法原理講的比較清楚, 這兒我簡單的概述一下,
什么是索引?
在數據之外,數據庫系統還維護着滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引。
什么數據結構可以作為數據庫索引?
對於數據庫而言,使用樹系列, 二叉樹, 紅黑樹, B樹, B+樹, 因為要考慮到range查詢, 所以hash索引不行
對於關系數據庫, 基本都是用B+樹作為索引機制, 而沒有用二叉樹或他的變種紅黑樹的, 為什么了?
一般來說,索引本身也很大,不可能全部存儲在內存中,因此索引往往以索引文件的形式存儲的磁盤上。這樣的話,索引查找過程中就要產生磁盤I/O消耗,相對於內存存取,I/O存取的消耗要高幾個數量級,所以評價一個數據結構作為索引的優劣最重要的指標就是在查找過程中磁盤I/O操作次數的漸進復雜度。換句話說,索引的結構組織要盡量減少查找過程中磁盤I/O的存取次數。
那么為什么B+樹, 能在盡量少的磁盤I/O的情況下進行檢索了?
主存和磁盤以頁為單位交換數據(在許多操作系統中,頁得大小通常為4k), 頁是計算機管理存儲器的邏輯塊, 原因就是磁盤一次讀, 需要一系列機械操作, 而根據局部性原理: 當一個數據被用到時,其附近的數據也通常會馬上被使用。
所以, 操作系統會進行預讀, 這就意味着, 就算你只需要1byte的數據, 每次也要讀一頁出來. 所以如果想要減少磁盤讀取次數, 就需要合理的組織存儲結構, 使每次讀出的頁中包含更多我們需要的信息.
可以想象在遍歷索引樹的時候, 如果所有的樹節點都是存在磁盤上的, 那么我們需要訪問節點的個數, 就是我們實際需要的磁盤I/O次數. 因為你無法保證你讀出一個page里面包含你想遍歷的多個節點.
對於索引樹而言, 訪問次數等於樹高, 那么即樹高越高的樹型結構, 效率越低.
所以對於平衡二叉樹, 樹高等於log2N, 明顯效率太低.
於是產生了B樹, B樹就是增加每個節點的度, 度由2變成n, 這樣樹高大大降低, 一般實際只有3左右.
這個想法很自然, 我們使一個節點包含盡可能多的信息, 由2個分支到n個分支, 但是又要保證一個節點的信息必須在一個page中, 不能超出page大小.
數據庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。為了達到這個目的,在實際實現B-Tree還需要使用如下技巧:
每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存儲在一個頁里,加之計算機存儲分配都是按頁對齊的,就實現了一個node只需一次I/O。
這樣就充分利用了每次page讀, 不會出現之前的讀4k, 而只有1byte有用的情況,
在page大小固定的情況下, B樹的度是由每個度的大小(keysize + datasize + pointsize)決定的, 當然希望B樹的度盡量的大, 這樣樹高就越低.
這個就是B+樹產生的原因, 因為在B樹中節點是存放data的, 而在B+樹中所以data都放到了leaf節點, 這樣就是樹節點的度得到了大大的提高.
而數據庫實際使用的是帶有順序訪問指針的B+Tree, 如圖
在B+Tree的每個葉子節點增加一個指向相鄰葉子節點的指針,就形成了帶有順序訪問指針的B+Tree。做這個優化的目的是為了提高區間訪問的性能
面對海量隨機更新的索引問題?
接着, 既然B+樹挺好, 為啥需要LSM-trees技術 (見后面的詳細補充)
如果沒有太多的隨機更新操作,B+樹可以工作地很好.Random IO for writes is bad
LSM Trees convert random writes to sequential writes
Writes go to a commit log and in-memory storage (Memtable)
The Memtable is occasionally flushed to disk (SSTable)
The disk stores are periodically compacted
但是當越快越多地將數據添加到隨機的位置上, 會導致無法保證單個B+ tree節點能夠存在一個disk page上, 頁面就會變得碎片化.
這樣讀取一個tree node需要多次磁盤seek, 效率會極大的下降.
當然可以使用優化進程不斷進行優化, 所以少量的隨機更新是沒有問題的
但對於海量隨機更新, 比如HBase的case, 數據傳入的速度可能會超過優化進程重寫現存文件的速度, 導致效率底下.
HBase definite guide 中architecture章節中,
B樹和LSM-tree本質上的不同點,實際上在於它們使用現代硬件的方式,尤其是磁盤。
對於大規模場景,計算瓶頸在磁盤傳輸上。CPU RAM和磁盤空間每18-24個月就會翻番,但是seek(磁盤尋道)開銷每年大概才提高5%。
如前面所討論的,有兩種不同的數據庫范式,一種是Seek,另一種是Transfer。RDBMS通常都是Seek型的,主要是由用於存儲數據的B樹或者是B+樹結構引起的,在磁盤seek的速率級別上實現各種操作,通常每個訪問需要log(N)個seek操作。另一方面,LSM-tree則屬於Transfer型。在磁盤傳輸速率的級別上進行文件的排序和merges以及log(對應於更新操作)操作。
LSM-tree工作在磁盤傳輸速率的級別上,同時可以更好地擴展到更大的數據規模上。同時也能保證一個比較一致的插入速率,因為它會使用日志文件+一個內存存儲結構把隨機寫操作轉化為順序寫。讀操作與寫操作是獨立的,這樣這兩種操作之間就不會產生競爭。
如何解決高速讀寫的balance問題?
http://blog.csdn.net/anderscloud/article/details/7181085
其實從本質來說,k-v存儲要解決的問題就是這么一個:盡可能快得寫入,以及盡可能快的讀取
盡可能快得寫
對磁盤來說,最快的寫入方式一定是順序的將每一次寫入都直接寫入到磁盤中即可。
但這樣帶來的問題是,我沒辦法查詢,因為每次查詢一個值都需要遍歷整個數據才能找到. 典型的例子是HDFS, 支持海量寫和順序讀, 不支持隨機讀
盡可能快的讀
如果需要盡可能快的讀到, 保持所有數據都是有序的, 就可以很快的讀到. 典型的例子就是B+樹.
但是需要保持全局有序, 必然會比較大的影響寫的效率, 這就是B+樹的問題. 如果有大量的隨機寫, 每個寫都可能需要操作不同的磁盤文件, 效率很低, 而且影響磁盤利用率, 大量磁盤碎片.
所以不可能同時達到讀和寫的快速, 最終的方案就是折衷...犧牲部分讀速度, 來保證寫速度
這個就是LSM-tree和SSTable的原理,
保持部分有序, 並將部分有序的集合批量寫入磁盤
B+樹的讀復雜度, 是log2n
而SSTable的讀復雜度, (n/m)log2m. 把n分為大小為m的(n/m)個小集合, 每個集合都是有序的.
可以看出讀的效率是低於B+ tree的, 但是由於寫的效率大大提高, 因為總是順序寫.
當然這兒有其他方法提升讀效率,
1. bloom filter
2. 小集合並成大集合, compact的過程
另一篇blog上有更多的介紹,
索引的隨機IO問題要更復雜一點。我們簡單點,只說涉及到單個索引項的操作。傳統的B+樹,無論是搜索、插入還是刪除(更新相當於插入+刪除,就不額外討論了),理論上都是O(log(B)(N))次IO(其中B是頁面包含的鍵值數,N是總鍵值數),但實際情況下可以假設非葉節點都在內存中,因此是1次 IO。磁盤一般只能有每秒幾百次隨機IO,因此對大的索引,每秒只能有幾百次操作,這個性能真是低的可憐。B+樹是70年代的老怪物,但直到今天,大多數數據庫里仍然用得是它,但實際上,有比傳統B+樹更能對付隨機IO的東西。
1996年,P O'Neil等提出的LSM-Tree是一個重大突破。
LSM-Tree主要有兩種變形,最簡單的LSM-Tree,是一個內存中的小索引加上外存中的大索引,更新先緩存在小索引中,再批量更新到大索引,這樣就有望合並對屬性同一頁面的多次更新的IO。
復雜的LSM-Tree,是划分為多個level的很多的小索引,每個level的大小,近似的是前一個 level大小的r倍,如果一個level有r個小索引,則合並形成一個下一level的較大的索引,這樣隨機插入或刪除的平均IO開銷可以降低到 log(N)/B次,是一個很大的提升。
但帶來的問題是,搜索的時候,就要搜索這么多個小索引,而這樣的索引會有O(log(N/B))個,那是可能有幾十個,搜索的性能就可能下降幾十倍,這往往也帶來問題。LSM-Tree已經有不少的現實應用,BigTable、Cassandra、Lucene等這些用的是復雜的那種LSM-Tree,InnoDB的change buffer可以說是那種一大一小的簡單LSM-Tree。NTSE想在做多版本事務的時候順便實現change buffer。
2000年,MA Bender等提出的Cache Oblivious B-Tree是第二個重大突破。這個跟LSM-Tree有些類似,也是索引從小到大分成相鄰大小翻倍的多個索引,因此隨機插入或刪除的平均IO開銷也是log(N)/B次,但它用了Fractional Cascading的技術,使得搜索的性能較傳統B+樹相關不多。雖然論文發表了10年了,這種索引似乎現在只有TokuDB一家實現,它是稱之為Fractal Tree。我們拿來試了試,效果果然出奇的好。
有沒有可能將來搞出一個比Fractal Tree更好的東西呢,遺憾的是如果硬件不發生根本改變,已經證明Fractal Tree已經是最理想的了。