存儲與索引------《Designing Data-Intensive Applications》讀書筆記3


在上一篇的筆記之中,我們討論了數據模型和查詢語言。在第三章之中我們來聊一聊不同的數據引擎內部是如何實現存儲和檢索的,以及不同設計之間的折中與妥協。

1.鍵值對數據庫

鍵值對數據庫是數據庫形式之中最簡單的一種模式,我們可以把它簡化的實現為下面兩個函數:
通過兩個Shell函數就可以實現簡易的鍵值對數據庫
底層存儲格式也十分簡單:一個文本文件,其中每行包含一個鍵值對,用逗號分隔(類似於CSV文件,忽略轉義問題)。每一次調用 db_set 會追加鍵值對到文件的末尾,如果你更新的一個鍵值對的舊版本不會覆蓋之前的鍵值對,但是 db_get會利用 tail -n 1 in 語句讀取最新的鍵值對。但是真正的數據庫需要處理更多的問題(例如並發控制、回收磁盤空間、使日志不能永久增長、處理錯誤和部分寫的問題),但基本設計思路和原則是相同的。

但是,db_get 在性能上表現的很糟糕,每一次需要查找一個key,db_get 會掃描整個數據庫文件來查找Key。在算法定義之中,查找的時間復雜度是O(n)。為了有效地查找數據庫中某個特定鍵的值,我們需要一個不同的數據結構:索引

2.索引

索引是從原始數據派生出來的附加結構。在添加和刪除索引時,不會影響數據存儲的內容,它只會影響查詢的性能。但是維護額外的結構會導致開銷,尤其是寫操作。任何類型的索引都會減慢寫速度,因為每次寫入數據時也需要更新索引。
在存儲系統的有一個重要的權衡:精心挑選的索引加快了讀取的速度,但是每個索引都會減慢寫入速度。由於這個原因,數據庫通常不會默認索引所有內容,但要求應用程序開發人員或數據庫管理員手動地選擇索引,可以選擇使應用程序受益最大的索引,而不需要引入更多的開銷。

  • 哈希索引
    這里我們通過哈希索引來分析一下上文提及的那個簡易的鍵值數據庫。最簡單的索引策略是:保持一個內存的哈希映射,其中每一個鍵都映射到數據文件中的字節偏移量,通過偏移量可以找到該值的位置,如下圖所示:
    內存哈希映射索引
    每當向文件追加一個新的鍵值對時,也會同時更新哈希映射以反映剛才寫入的數據的偏移量(這既可以用於插入新的鍵值對,也可以用於更新現有的鍵值對)。在查找值時,使用哈希映射查找數據文件中的偏移量,查找該位置並讀取該值。
    那么我們如何避免最終耗盡磁盤空間呢?一個好的解決方案是,我們可以對這些文件執行壓縮,如下圖所示。壓縮意味着在文件中扔掉重復的鍵,並且只保留每個鍵的最新更新。
    文件的壓實操作.png
    合並和壓縮可以由后台線程完成,並且在進行合並和壓縮操作時,我們仍然可以使用舊的文件繼續正常地服務讀寫請求。在合並過程完成后,我們將讀取請求轉換為使用新合並的文件,然后舊的文件可以簡單地刪除。
    缺點
    (1)哈希索引嚴重依賴於內存,所以如果Key的數量龐大,需要匹配足夠的內存空間。
    (2)范圍查詢效率不高,每查找一個值都需要一次鍵值對映射。
  • SSTable
    由哈希索引我們可以引申出更加高效的索引結構:SSTable(Sorted String Table),SSTable要求鍵值對序列按照鍵來排序。乍一看,這個要求似乎破壞了順序寫的性能,但是它大大提高了維護數據以及索引結構的效率。
    • 合並文件既簡單又高效,使用簡單的歸並排序算法。
      使用歸並排序合並SSTable
      • 不再需要保留所有鍵在內存中的索引,只需要保留部分鍵的索引,利用鍵在SSTable之中有序的特點。
        只需要保留部分鍵的索引
      • 可以進行分組壓縮,每個索引可以指向壓縮塊的起始點,來節省存儲空間與減少I/O帶寬的使用。

但是,如何讓我們寫入的鍵值對有序呢?這個問題在內存之中並不是什么難事,如紅黑樹AVL樹這些數據結構,可以按任何順序插入鍵,並按排序順序讀取它們。所以我們在使用SSTable時,會維護一個MemTable的數據結構在內存之中,當MemTable達到閥值時,我們將MemTable作為一個新的SSTable序列化到磁盤之上。同時利用后台線程,不斷壓縮刪除舊的SSTable,來維護一個可循環運行的存儲系統。由於兩次寫入一次是在內存,一次磁盤寫入是連續的(append日志),因此SSTable可以支持非常高的寫入吞吐量。

許多數據庫都是采用這樣的思路來高效的處理數據,如CassandraHBaseLevelDBBitcask等。Lucene的全文搜索的使用ElasticsearchSolr索引引擎,也采用了類似的方法來存儲它的詞典,當然,全文索引比鍵值索引復雜得多,但基於一個類似的想法:給定搜索查詢中的一個詞,查找提及該詞的所有文檔(Web頁面、產品描述等)。這同樣是一個鍵值結構實現的,其中鍵是一個詞,而這個值是包含該詞的所有文檔的ID列表。

  • B樹索引
    這個索引結構大家應該非常熟悉了,在關系型數據庫(如:MySQL,Oracle)與非關系型數據庫(如:MongoDB)之中都大量應用。B樹也把鍵值對進行了排序,它既允許高效的值查詢也允許高效的范圍查詢。
    哈希索引結構將數據分解成可變大小的段,通常是幾個兆字節或更多的大小。而相比之下,樹型索引將數據分成固定大小的塊或頁,通常為4KB大小(有時更大),每次讀或寫都基於頁的大小。這種設計更接近於底層硬件,因為磁盤也是按固定大小的頁來排列的。B樹索引保證了:N個鍵總是有深度的O(log n)樹,大多數數據都可以放入到一個三或四層的B樹之中,(一個4頁的四級樹,分支系數為500,可以存儲256TB)。下圖展示了我們怎么樣使用B樹來查找“251”這個鍵:
    利用B樹索引的存儲結構
    基本寫的操作是覆蓋舊數據的數據頁,重寫不會改變頁的位置;即,當頁被覆蓋時,對該頁的所有引用都保持不變。這與SSTable這樣的哈希索引結構形成鮮明對比,它有附加操作,但從不修改文件。
    而B樹索引的並發控制相對復雜,當多個線程會對樹進行訪問時,需要通過用鎖存器(輕量級鎖)保護樹的數據結構來完成。(這里也可以用Copy on Write來快照隔離)而哈希索引結構的壓縮,合並則不會影響查詢,寫入等操作。

  • 一些優缺點的探討

    (1)順序寫入通常比隨機寫入快得多,所以SSTable通常的寫入性能是相對優秀的。
    (2)由於SSTable壓縮與清理的線程存在,通常會有較低的存儲開銷。但是壓縮和清理磁盤的過程之中會與正常的請求服務產生磁盤競爭,導致吞吐量的下降。
    (3)由於SSTable會存在同一個鍵值的多個副本,對於實現事務等對於一致性要求更高的場景,樹型索引會表現的更加出色。

3.小結

樹型索引在數據庫架構是非常根深蒂固的,對於很多的工作負載提供始終如一的良好性能。而以SSTable為代表的哈希索引也越來越受歡迎。確定哪種類型的存儲引擎更適合應用場景,並沒有一個簡單易用的規則,因此需要我們對業務邏輯有更深層次的理解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM