1、介紹
RocksDB 項目最開始是在 Facebook 作為一個試驗項目開發的高效的數據庫軟件,可以實現在服務器負載下快速存儲(特別是閃存存儲)的數據存儲的全部潛力。它是一個 C++ 庫,可以用於存儲 KV,包括任意大小的字節流。它支持原子讀寫。
RocksDB 具有高度靈活的配置設置,可以調整為在各種生產環境(包括純內存,閃存,硬盤或 HDFS)上運行。它支持各種壓縮算法,並且有生產和調試環境的各種便利工具。
RocksDB 借用了來自開源 leveldb 項目的核心代碼,以及來自 Apache HBase 的重要思想。初始代碼是從開源 leveldb 1.5 fork 的。它還融入了 facebook 團隊在開發 RocksDB 之前的若干代碼及想法。
2、假設和目標
性能
RocksDB 的主要設計點是,它應該是快速存儲和服務器工作負載的性能而設計。它應充分利用 Flash 或 RAM 提供的高速讀/寫速率的全部潛力。它應該支持高效的點查找以及範圍掃描。它應該可配置為支持高隨機讀取工作負載,高更新工作負載或兩者的組合。其架構應支持輕鬆調整參數,支持讀取放大,寫入放大和空間放大場景。
生產環境支持
RocksDB應該以這樣一種方式設計,即它具有內置的工具支持,有助於在生產環境中部署和調試。大多數主要參數應該是完全可調的,以便它可以被不同硬件上被不同應用使用。
向後兼容性
軟件的較新版本應向後兼容,以便在升級到較新版本的 RocksDB 時,現有應用程序不需要更改。
3、架構概述
RocksDB 是一個嵌入式 kv 存儲,key 和 value 是任意字節流。RocksDB 按順序組織所有數據,常用操作是 Get(key) ,Put(key) ,Delete(key) 和 Scan(key) 。
RocksDB 的三個基本結構是 memtable, sstfile 和 logfile。
memtable 是一個內存數據結構,新寫入的數據被插入到 memtable 中,並可選地寫入日誌文件。
日誌文件是存儲上順序寫入的文件。當 memtable 填滿時,它被 flush 到存儲上的 sstfile ,然後可以被安全地刪除。sstfile 中的數據順序存放,以方便按 key 進行查找。
在此更詳細地描述默認 sstfile 的格式 [2]。
4、特性
Get,Interator(迭代器)和快照
Key 和 value 被視為純字節流。對 key 或 value 的大小沒有限制。Get API 允許應用程序從數據庫中提取單個 key。MultiGet API 允許應用程序從數據庫中檢索一堆 key。通過 MultiGet 調用返回的所有 key-value 彼此一致。
數據庫中的所有數據按照排序順序進行邏輯排列。應用程序可以定義 key 的排序比較方法。Iterator API 允許應用程序對數據庫執行 RangeScan。Iterator 可以尋找指定的 key,然後應用程序可以從該點開始一次掃描一個 key。Iterator API 也可以用於對數據庫中的 key 進行反向迭代。創建 Iterator 時,將創建數據庫的一致時間點視圖。因此,通過 Iterator 返回的所有 key 都來自數據庫的一致視圖。
Snapshot API 允許應用程序創建數據庫的時間點視圖。Get 和 Iterator API 可用於從指定的快照讀取數據。在某種意義上,Snapshot 和 Iterator 都提供了數據庫的時間點視圖,但它們的實現是不同的。短期掃描最好通過迭代器完成,而長時間運行的掃描最好通過快照完成。迭代器對與數據庫的該時間點視圖相對應的所有底層文件保持引用計數 - 這些文件在 Iterator 被釋放之前不會被刪除。另一方面,快照不會防止文件被刪除; 但在壓縮過程中,壓縮程序能夠判斷快照的存在,它不會刪除在任何現有快照中可見的 key。
快照不會在數據庫重新啟動後保持持久化,因此重新加載 RocksDB 庫(通過服務器重新啟動)會釋放所有預先存在的快照。
前綴迭代器
大多數 LSM 引擎不能支持高效的 RangeScan API,因為它需要查看每個數據文件。但大多數應用程序不需要對數據庫中的 key 範圍進行純隨機掃描; 而應用程序通常通過 key 前綴進行掃描。
RocksDB 使用這個方法來體現了它的優勢。應用程序可以配置 prefix_extractor 以指定 key 前綴。RocksDB 使用它來存儲每個 key 前綴的 blooms。指定前綴(通過 ReadOptions)的迭代器將使用這些 bloom 位來避免查找不包含具有指定的 key 前綴的數據文件。
更新
Put API 將單個 key-value 插入數據庫。如果 key 已經存在於數據庫中,則以前的值將被覆蓋。Write API 允許將多個 key-value 原子地插入到數據庫中。數據庫保證要麼單個 Write 調用中的所有 key-value 將被插入數據庫,要麼它們都不會插入數據庫。
持久化
RocksDB 有一個事務日誌。所有 Put 都存儲在稱為 memtable 的內存中緩衝區中,並可選擇插入到事務日誌中。每個 Put 都有一組通過 WriteOptions 設置的標誌,它們指定是否將 Put 插入到事務日誌中。WriteOptions 還可以指定在 Put 被提交之前,是否向事務日誌發出 sync 調用。
在內部,RocksDB 使用批量提交機制將多個事務寫入到事務日誌中,以便它可以使用單個 sync 調用提交多個事務。
容錯
RocksDB 使用校驗和來檢測存儲中的損壞。這些校驗和針對每個塊(通常在 4K 到 128K 之間)。塊一旦寫入存儲,就不會被修改。RocksDB 動態檢測硬件對校驗和計算的支持,並在可用時自動提供該支持。
多線程壓縮
需要壓縮才能刪除同一 key 的多個副本,如果調用者曾經多次覆蓋同一 key 的值,則會出現同一 key 的多個副本。壓縮還會處理 key 的刪除。通過配置,壓縮支持多線程進行。
LSM 數據庫的總寫入吞吐量直接取決於壓縮可能發生的速度,特別是當數據存儲在諸如 SSD 或 RAM 的快速存儲器中時。RocksDB 可以配置為多線程壓縮。可以看出,與單線程壓縮相比,當數據庫在 SSD 上時,多線程壓縮持續寫入速率可以增加多達 10 倍。
整個數據庫存儲在一組 sstfile 中。當 memtable 已滿時,其內容寫入 Level-0(L0)中的文件。當它被刷新到 L0 中文件時,RocksDB 刪除 memtable 中的重複和覆蓋的 key。一些文件會定期讀入併合並形成較大的文件,這稱為壓縮。
RocksDB 支持兩種不同的壓縮方式。
通用壓縮(Universal Style Compaction )存儲 L0 中的所有文件,所有文件按時間順序排列。壓縮拾取一些在時間上彼此相鄰的文件,並將它們合併回新的文件存回 L0。所有文件可以具有重疊的 key。
級別樣式壓縮(Level Style Compaction)在數據庫中以多個級別存儲數據。較新的數據存儲在 L0 中,最舊的數據存儲在 Lmax 中。L0 中的文件可能具有重疊的鍵,但其他圖層中的文件不能。壓縮過程選擇 Ln 中的一個文件及其在 Ln + 1 中的所有重疊文件,並用 Ln + 1 中的新文件替換它們。通用樣式壓縮通常導致較低的寫入放大,但比水平樣式壓縮更高的空間放大。
數據庫中的 MANIFEST 文件記錄數據庫狀態。壓縮過程會添加新文件並從數據庫中刪除舊文件,並通過將它們記錄在 MANIFEST 文件中使這些操作持久化。要記錄在 MANIFEST 文件中的事務使用批量提交算法,來將重複 sync 的請求合併到 MANIFEST 文件。
避免停頓
後臺壓縮線程也負責將 memtable 內容刷新到存儲上的文件。如果所有後臺壓縮線程都忙於執行長時間運行的壓縮,那麼突然的寫入操作可以快速填滿memtable ,從而新的寫入操作將會卡頓。這種情況可以通過配置 RocksDB 保留一小段線程來避免,這些線程顯式保留用於將 memtable 刷新到存儲器的唯一目的。
壓縮過濾器
一些應用程序可能希望在壓縮時對數據做一些處理。例如,具有對生存時間(TTL)的固有支持的數據庫,可以移除過期的 key。這可以通過應用程序定義的壓縮過濾器來完成。如果應用程序想要連續刪除超過特定時間的數據,它可以使用壓縮過濾器刪除已過期的記錄。RocksDB 壓縮過濾器讓應用程序修改 key 的值或完全刪除 key 作為壓縮過程的一部分。例如,應用程序可以作為壓縮的一部分連續運行數據清理程序。
ReadOnly 模式
數據庫可以以只讀模式打開,其中數據庫保證應用程序不會修改數據庫中的任何內容。這導致高得多的讀取性能,因為被橫穿的代碼路徑完全避免了鎖的開銷。
數據庫調試日誌
RocksDB 將詳細日誌寫入名為 LOG* 的文件。這些主要用於調試和分析正在運行的系統。該日誌可以被配置為以指定的週期滾動。
數據壓縮
RocksDB 支持 snappy,zlib,bzip2,lz4 和 lz4_hc 壓縮。RocksDB 可以配置為在不同級別的數據上支持不同的壓縮算法。通常 90% 的數據在 Lmax 級別。
典型的安裝可能配置無壓縮級別 L0-L2,snappy 壓縮中級和 zlib 壓縮 Lmax。
事務日誌
RocksDB 將事務存儲到日誌文件中以防止系統崩潰。在重新啟動時,它會重新處理日誌文件中記錄的所有事務。日誌文件可以配置為存儲在與 _sstfile_s 不同的目錄中,比如某些場景,你可能會將所有數據文件存儲在非持久性快速存儲器中,同時,您可以通過將所有事務日誌放在較慢但持久的存儲上確保不會有數據丟失。
完全備份,增量備份和複製
RocksDB 支持完全備份和增量備份。RocksDB 是一個 LSM 數據庫引擎,因此,一旦創建,數據文件就不會被覆蓋,這使得很容易提取與數據庫內容的時間點快照相對應的文件名列表。API DisableFileDeletions 指示 RocksDB 不要刪除數據文件。壓縮將繼續發生,但數據庫不需要的文件將不會被刪除。然後,備份應用程序可以調用 API GetLiveFiles / GetSortedWalFiles 以檢索數據庫中的活動文件列表,並將它們複製到備份位置。備份完成後,應用程序可以調用 EnableFileDeletions ; 數據庫現在可以自由回收所有不再需要的文件。
增量備份和複製需要能夠找到並 tail 數據庫的所有最近更改。API GetUpdatesSince 允許應用程序在 RocksDB 事務日誌上執行 tail 操作。它可以從RocksDB 事務日誌中連續獲取事務,並將它們應用到遠程複製副本或遠程備份。
複製系統通常希望用一些元數據註釋每個 Put。該元數據可以用於檢測複製管道中的循環。它也可以用於時間戳和順序事務。為此,RocksDB 支持一個稱為 PutLogData 的 API,應用程序可以使用該 API 來為每個 Put 添加元數據。此元數據僅存儲在事務日誌中,不存儲在數據文件中。通過 PutLogData 插入的元數據可以通過 GetUpdatesSince API 來獲取。
RocksDB 事務日誌在數據庫目錄中創建。當不再需要日誌文件時,將其移動到歸檔目錄。留在歸檔目錄的原因是落後的複製流可能需要從日誌文件中檢索過去的事務。API GetSortedWalFiles 返回所有事務日誌文件的列表。
在同一個進程中支持多個嵌入式數據庫
RocksDB 的一個常見用例是應用程序固有地將其數據集分區為邏輯分區或分片。這種技術有利於應用程序負載平衡和從故障快速恢復。這意味著單個服務器進程需要能夠同時操作多個 RocksDB 數據庫。這通過名為 Env 對象完成。除此之外,線程池也與 Env 關聯。如果應用程序想要在多個數據庫實例之間共享公共線程池(用於後臺壓縮),那麼它應該使用相同的 Env 對象來打開這些數據庫。
類似地,多個數據庫實例可以共享相同的塊高速緩存。
塊緩存 - 壓縮和未壓縮數據
RocksDB 使用 LRU 緩存來提供讀取。塊高速緩存被分割成兩個單獨的高速緩存:第一高速緩存是未壓縮塊,第二高速緩存是壓縮塊,它們都存在 RAM 中。如果配置了壓縮塊高速緩存,則數據庫智能地避免在 OS buffer 中緩存數據。
表緩存
表緩存是一種用於緩存打開的文件描述符的結構。這些文件描述符用於 sstfile。應用程序可以指定表緩存的最大大小。
外部壓縮算法
LSM 數據庫的性能在很大程度上取決於壓縮算法及其實現。RocksDB 有兩個支持的壓縮算法:LevelStyle 和 UniversalStyle。我們還希望使大型開發人員能夠開發和實驗其他壓縮策略。因此,RocksDB 有適當的鉤子關閉內置的壓縮算法,並提供 API 允許應用程序操作自己的壓縮算法。
Options.disable_auto_compaction(如果設置)禁用本機原生的壓縮算法。GetLiveFilesMetaData API 允許外部組件訪問數據庫中的每個數據文件,並決定哪個文件可以合併和壓縮。DeleteFile API 允許應用程序刪除被視為已過期的數據文件。
非阻塞數據庫訪問
一些應用程序希望他們僅在數據調用是非阻塞的時候,才從數據庫獲取數據,即數據獲取調用不需要從存儲器中讀取數據。RocksDB 將數據庫的一部分緩存在塊緩存中,因此這些應用程序希望僅在該塊緩存中找到數據時才訪問數據。如果這個調用沒有在塊緩存中找到數據,那麼 RocksDB 會嚮應用程序返回一個適當的錯誤代碼。應用程序然後可以調度正常的 Get / Next 操作,並且理解該數據訪問調用可能潛在地被訪問存儲器(可能在不同的線程上下文中)的 IO阻塞。
可堆疊 DB
RocksDB 有一個內置的包裝機制,可以在數據庫內核之上添加功能。此功能由 StackableDB API 提供。例如,TTL 功能由 StackableDB 實現,而不是核心 RocksDB API 的一部分。這種方法保持代碼模塊化和乾淨。
可備份數據庫
使用 StackableDB 接口實現的一個功能是 BackupableDB,這使得 RocksDB 的備份變得簡單。參看附錄鏈接更多瞭解如何備份 RocksDB [3]。
Memtable
可插拔 memtable
RocksDB 的 memtable 的默認實現是一個 skiplist。skiplist 是一個有序集,當工作負載使用 range-scans 並且交織寫入時,這是一個必要的結構。
然而,一些應用程序不交織寫入和掃描,而一些應用程序根本不執行範圍掃描。對於這些應用程序,排序集可能無法提供最佳性能。因此,RocksDB 支持可插拔的 API,允許應用程序提供自己的 memtable 實現。
開發庫提供了三個 memtable:skiplist memtable,vector memtable 和前綴散列(prefix-hash) memtable。
Vector memtable 適用於將數據批量加載到數據庫中。每個寫入在向量的末尾插入一個新元素; 當它是刷新 memtable 到存儲的時候,向量中的元素被排序並寫出到 L0 中的文件。
前綴散列 memtable 允許對 gets,puts 和 scans-within-a-key-prefix 進行有效的處理。
Memtable 管道
RocksDB 支持為數據庫配置任意數量的 memtable。當 memtable 已滿時,它變成不可變的 memtable,後臺線程開始將其內容刷新到存儲。同時,新的寫入繼續累積到新分配的 memtable。如果新分配的 memtable 被填充到其限制,它也被轉換為不可變的 memtable 並被插入到 flush 管道中。後臺線程繼續將所有流水線不可變的 memtables 刷新到存儲。這種流水線提高了 RocksDB 的寫吞吐量,尤其是在慢速存儲設備上運行時。
Memtable 壓縮
當 memtable 被 flush 到存儲時,內聯壓縮過程從輸出流中刪除重複記錄。類似地,如果較早的 put 被稍後的刪除隱藏,那麼 put 根本不會寫入輸出文件。此功能大大減少了存儲和寫入放大數據的大小。這是 RocksDB 用作生產者 - 消費者隊列時的一個基本特徵,特別是當隊列中的元素的生命週期非常短的時候。
合併 Merge 操作
RocksDB 本地支持三種類型的記錄:Put 記錄,Delete 記錄和 Merge 記錄。當壓縮過程遇到 Merge 記錄時,它調用應用程序指定的稱為 Merge 的方法。合併可以將多個 Put 和 Merge 記錄合併成一個。這個強大的功能允許通常執行讀 - 修改 - 寫的應用程序完全避免讀。它允許應用程序將操作意圖記錄為合併記錄,RocksDB 壓縮過程將該意圖延遲應用於原始值。此功能在合併運算符中詳細描述。
5、工具
有許多好玩的工具用於支持生產中的數據庫。sst_dump 工具可以導出 sst 文件中的所有鍵值對。ldb 工具可以 put,get,scan 數據庫的內容。ldb 也可以dump MANIFEST 內容、更改數據庫的配置級別數或用於手動壓縮數據庫。
6、測試
有一堆單元測試來測試數據庫的特定功能。make check 命令運行所有單元測試。單元測試觸發 RocksDB 的特定功能,而不是設計用來測試大規模的數據正確性。db_stress 測試用於大規模上驗證數據正確性。
7、性能
RocksDB 通過一個名為 db_bench 的實用程序進行性能測試。db_bench 是 RocksDB 源代碼的一部分。這裡 [4] 介紹了使用閃存存儲的幾個典型工作負載的性能結果。您還可以在這裡 [5] 找到RocksDB性能結果的內存中工作負載。