1. 程式人生 > >獲得PCC效能大賽背後的RocksDB引擎:5分鐘全面瞭解其原理

獲得PCC效能大賽背後的RocksDB引擎:5分鐘全面瞭解其原理

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效能結果的記憶體中工作負載。

相關連結:

  1. 原文:https://github.com/facebook/rocksdb/wiki/RocksDB-Basics

  2. https://github.com/facebook/rocksdb/wiki/Rocksdb-BlockBasedTable-Format

  3. https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB

  4. https://github.com/facebook/rocksdb/wiki/Performance-Benchmarks

  5. https://github.com/facebook/rocksdb/wiki/RocksDB-In-Memory-Workload-Performance-Benchmarks

  6. 本文圖片來自Slides: https://www.percona.com/live/data-performance-conference-2016/sessions/rocksdb-key-value-store-optimized-flash-based-ssd