作者 | 馬浩翔
日前,字節(jié)跳動(dòng)技術(shù)社區(qū) ByteTech 舉辦的第四期字節(jié)跳動(dòng)技術(shù)沙龍圓滿落幕,本期沙龍以《字節(jié)云數(shù)據(jù)庫架構(gòu)設(shè)計(jì)與實(shí)戰(zhàn)》為主題。在沙龍中,字節(jié)跳動(dòng)基礎(chǔ)架構(gòu)數(shù)據(jù)庫開發(fā)工程師馬浩翔,跟大家探討了 《從單機(jī)到分布式數(shù)據(jù)庫存儲系統(tǒng)的演進(jìn)》,本文根據(jù)分享整理而成。
存儲系統(tǒng)概覽
存儲系統(tǒng)是指能高效存儲,持久化用戶數(shù)據(jù)的一系列系統(tǒng)軟件。在眾多的存儲系統(tǒng)中,以下是三類比較主流的存儲產(chǎn)品及其特點(diǎn)分析:
塊存儲
- 底層語義,基于 block 編程;
- 接口樸素:在 Linux 的 IO 軟件棧中,要直接使用塊存儲的話就要基于 LBA 編程,因此接口較為簡單樸素,再加上塊存儲本身處于整個(gè)存儲軟件棧的底層,這導(dǎo)致塊存儲使用起來并不十分友好;
- 追求低時(shí)延、高吞吐:研發(fā)一個(gè)塊存儲系統(tǒng),在設(shè)計(jì)目標(biāo)上我們往往會追求超高的性能,體現(xiàn)在超低的時(shí)延和超高的吞吐。但考慮到塊存儲的接口確實(shí)過于樸素,往往只有一些追求超高性能的系統(tǒng)才會直接基于塊存儲構(gòu)建,然后自建應(yīng)用層 cache。
對象存儲
- 公有云上的王牌存儲產(chǎn)品:在 IT 時(shí)代,“Everything is data”的趨勢迅速催化了對象存儲系統(tǒng)。尤其對于字節(jié)跳動(dòng)的業(yè)務(wù)而言,使用對象存儲系統(tǒng)來處理視頻、圖片、音頻等非結(jié)構(gòu)化的數(shù)據(jù),語義最為自然;
- 非結(jié)構(gòu)化數(shù)據(jù),提供 immutable 語義:一旦圖片或視頻等非結(jié)構(gòu)化數(shù)據(jù)上傳至對象存儲系統(tǒng)成功,則無法對其進(jìn)行原地修改,只能通過刪除舊數(shù)據(jù),重新上傳新數(shù)據(jù)的方式完成“修改”邏輯;
- 成本優(yōu)先:一般不苛求單次操作的時(shí)延,但是非常注重系統(tǒng)吞吐 & 存儲成本。
文件系統(tǒng)
- 接口語義豐富,普適性強(qiáng):遵循 POSIX/弱 POSIX 語義,諸如 Open、Write、Read 等許多操作數(shù)據(jù)的接口都能在文件系統(tǒng)中被找到。
- 擁有較多開源的分布式實(shí)現(xiàn),生態(tài)良好。
- 一般也不苛求時(shí)延,注重系統(tǒng)吞吐 & 存儲成本。
單機(jī)數(shù)據(jù)庫存儲解析
單機(jī)數(shù)據(jù)庫存儲,要從內(nèi)存層和持久化層兩個(gè)方面來解析。在內(nèi)存層,僅說關(guān)系型數(shù)據(jù)庫,其內(nèi)存數(shù)據(jù)結(jié)構(gòu)特點(diǎn)可以總結(jié)為:一切都是“樹”。我們以最常見的 B+ 樹為例,B+ 樹具有以下突出的特點(diǎn):
- In memory 操作效率非常高:B+ 樹搜索時(shí)間復(fù)雜度是 log 級別;并且 B+ 樹的葉子節(jié)點(diǎn)構(gòu)成鏈表,非常有利于在內(nèi)存中對數(shù)據(jù)進(jìn)行 scan 操作。
- 磁盤操作效率高:B+ 樹的 Fanout 足夠大,樹的層級較少,呈矮胖狀,可以減少磁盤 IO 數(shù);同時(shí) B+ 樹的非葉子節(jié)點(diǎn)只存索引數(shù)據(jù),葉子節(jié)點(diǎn)存實(shí)際數(shù)據(jù),能大大壓縮樹高,進(jìn)一步減少磁盤 IO 數(shù)。
- 數(shù)據(jù)結(jié)構(gòu)高度統(tǒng)一:數(shù)據(jù) & 索引都可以直接組織成 B+ 樹,因此代碼的可維護(hù)性、可讀性和開發(fā)效率都比較好。
僅有內(nèi)存數(shù)據(jù)結(jié)構(gòu)當(dāng)然是不夠的,我們還需要設(shè)計(jì)高效的磁盤數(shù)據(jù)結(jié)構(gòu),下圖展示了從內(nèi)存數(shù)據(jù)結(jié)構(gòu)到磁盤數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)持久化過程:
左邊虛線框描繪的是 In Memory 結(jié)構(gòu)示意圖。舉個(gè)例子,如果我們要修改 Page A 的某一行數(shù)據(jù),對其中的一個(gè)字段進(jìn)行自增,自增值是 2。自然而然會產(chǎn)生一個(gè)數(shù)據(jù)庫的物理操作日志,即 Redo Log,用來描述我們對 Page A 的修改。同時(shí),在數(shù)據(jù)庫的事務(wù)執(zhí)行過程中,可能還會產(chǎn)生大量臨時(shí)數(shù)據(jù)(圖里的 Temp data),當(dāng)內(nèi)存不夠用的時(shí)候也需要將其持久化。
右邊虛線框描繪的是 In Persistent Layer 的示意圖。假設(shè)我們使用比較友好的文件系統(tǒng)來將內(nèi)存數(shù)據(jù)持久化,我們需要設(shè)置不同的文件,讓它們各司其職。例如圖里的藍(lán)色文件存儲 Page,綠色文件存儲 Redo Log,粉色文件存儲臨時(shí)數(shù)據(jù)。如果數(shù)據(jù)庫發(fā)生 crash ,在恢復(fù)階段我們就在各類文件中進(jìn)行數(shù)據(jù)定位,結(jié)合 Redo Log File 和 Page File ,進(jìn)行數(shù)據(jù)庫的數(shù)據(jù)恢復(fù)。除此之外,如果直接基于塊存儲進(jìn)行持久化,就需要數(shù)據(jù)庫本身的存儲引擎管理好 LBA,需要在用戶態(tài)里面實(shí)現(xiàn) buffer cache 等邏輯,這也是可行的。
那么基于單機(jī)的 FS / 塊存儲去做持久化,我們會遇到哪些問題呢?通過下面的單機(jī)數(shù)據(jù)庫系統(tǒng)的典型架構(gòu)圖,我們可以發(fā)現(xiàn)三個(gè)問題:
- 單機(jī)容量瓶頸:在 Database 層的單機(jī)服務(wù)器上運(yùn)行著 database 進(jìn)程,服務(wù)器上掛載了大量本地磁盤用作數(shù)據(jù)持久化。但一臺物理服務(wù)器能掛載的磁盤容量總是有限的,這就導(dǎo)致了單機(jī)的容量瓶頸問題。
- 擴(kuò)縮容困難:當(dāng)容量、CPU 或者內(nèi)存等資源不夠時(shí),我們需要進(jìn)行擴(kuò)容。在單機(jī)時(shí)代,擴(kuò)容意味著將數(shù)據(jù)從這個(gè)磁盤搬遷到另一個(gè)磁盤。但不管我們是通過網(wǎng)絡(luò)還是有線連接手段,都需要花費(fèi)一定的時(shí)間,這可能導(dǎo)致業(yè)務(wù)較長時(shí)間的停寫(不可用),因此擴(kuò)縮容是非常困難的。
- 多份獨(dú)立數(shù)據(jù),成本高:如果我們要在“復(fù)制集”之間或者主備機(jī)之間去做數(shù)據(jù)冗余或數(shù)據(jù)同步,那么每新增一分計(jì)算能力(新增一個(gè)計(jì)算節(jié)點(diǎn)),就要新增一分存儲冗余,就會導(dǎo)致存儲成本提高。
分布式數(shù)據(jù)庫存儲解析
為了解決單機(jī)數(shù)據(jù)庫存儲系統(tǒng)面臨的問題和挑戰(zhàn),字節(jié)跳動(dòng)的數(shù)據(jù)庫團(tuán)隊(duì)調(diào)研了一些業(yè)界主流的分布式數(shù)據(jù)庫方案。
MyRocks: MySQL + RocksDB
需要說明的是,MyRocks 不是分布式數(shù)據(jù)庫或者分布式解決方案,它是單機(jī) SQL over kv 的典型代表。
- 核心理念:用 RocksDB 替換 InnoDB 。使用 RocksDB 能夠有效緩解單機(jī)容量瓶頸的問題;
- 特點(diǎn):一是:數(shù)據(jù)可壓縮比例較高。RocksDB 實(shí)現(xiàn)了一種比較優(yōu)秀的壓縮算法,根據(jù)實(shí)際調(diào)研結(jié)果顯示,在關(guān)系型數(shù)據(jù)庫場景,基本上它能實(shí)現(xiàn) 2-4 倍的壓縮比,能有效緩解單機(jī)的容量瓶頸問題。例如,單機(jī)原本掛載了 10 塊磁盤,只能承載 10 TB 數(shù)據(jù),使用 RocksDB 就能在不改變硬件條件下幫助單機(jī)承載 20 TB 或 30 TB 等更多的數(shù)據(jù);二是,順序?qū)懶阅茌^好,這也是 LSM-Tree 這種數(shù)據(jù)結(jié)構(gòu)在 HDD 年代出現(xiàn)的核心原因。
- 難點(diǎn):Compaction 會導(dǎo)致性能抖動(dòng),且兼容性一般。眾所周知,RocksDB 基于 LSM-Tree 構(gòu)建,必然會遇到一些典型的 LSM-Tree-based 系統(tǒng)的問題。雖然 RocksDB 對順序?qū)懱貏e友好,但它一定程度上犧牲了讀性能—— RocksDB 在讀的過程中會觸發(fā) Compaction,可能引發(fā)性能抖動(dòng),導(dǎo)致前臺的寫出現(xiàn)卡頓現(xiàn)象;同時(shí),這一類 SQL over kv 解決方案的兼容性能表現(xiàn)較為一般。
Amazon Aurora: 計(jì)算存儲分離
- 核心理念:計(jì)算存儲分離,Log is Database。
- 特點(diǎn):架構(gòu)靈活,存儲層帶有特定的數(shù)據(jù)庫計(jì)算邏輯,除了具備存儲能力之外,還具備 Redo Log 解析、回放生成數(shù)據(jù)庫 Page、維護(hù)多版本數(shù)據(jù)的能力。
- 優(yōu)勢:兼容性強(qiáng)、讀擴(kuò)展能力較優(yōu)。基于共享存儲系統(tǒng)的特點(diǎn),Amazon Aurora 的讀計(jì)算節(jié)點(diǎn)具備較好的擴(kuò)展性,能夠?qū)崿F(xiàn)一主 15 備的部署形態(tài),其兼容性、讀擴(kuò)展性表現(xiàn)較好。
Spanner 系: Shared-Nothing
- 核心理念:計(jì)算存儲分離,且 Share-Nothing。
- 特點(diǎn):Spanner 系的數(shù)據(jù)庫系統(tǒng)一般基于分布式 k-v 存儲構(gòu)建,由存儲層保證事務(wù)特性,計(jì)算層做成純計(jì)算的無狀態(tài)節(jié)點(diǎn)。
- 難點(diǎn):要解決當(dāng)前數(shù)據(jù)庫生態(tài)兼容性問題 & 分布式 k-v 系統(tǒng)的 hotspot 問題比較麻煩。
最佳實(shí)踐:veDB 分布式存儲系統(tǒng)
基于上述字節(jié)數(shù)據(jù)庫團(tuán)隊(duì)的調(diào)研結(jié)果,我們設(shè)計(jì)了 veDB 分布式存儲系統(tǒng)以解決單機(jī)數(shù)據(jù)庫存儲系統(tǒng)面臨的問題與挑戰(zhàn),本節(jié)將主要介紹 veDB 分布式存儲系統(tǒng)的系統(tǒng)目標(biāo)與核心技術(shù)特點(diǎn)。
系統(tǒng)目標(biāo)
在設(shè)計(jì)理念上,我們期望存儲系統(tǒng)能夠?qū)崿F(xiàn)以下四個(gè)主要目標(biāo):
- 極致彈性:存儲節(jié)點(diǎn)與計(jì)算節(jié)點(diǎn)解耦,隨時(shí)彈性擴(kuò)縮容;
- 極致易用性:構(gòu)建 one-size-fits-all 的存儲系統(tǒng),而非專用存儲,要能兼容多個(gè)主流數(shù)據(jù)庫(MySQL & PostgreSQL & Mongo……);
- 極致性價(jià)比:低時(shí)延、低成本;
- 極致可靠性:具備高性能、高可靠的備份恢復(fù)能力。
基于以上系統(tǒng)目標(biāo),數(shù)據(jù)庫團(tuán)隊(duì)設(shè)計(jì)并開發(fā)了 veDB 分布式存儲系統(tǒng),如下圖所示:
從圖中可以看出,分布式存儲層基于 LogStore 和 PageStore 這兩個(gè)子系統(tǒng)構(gòu)建,其系統(tǒng)特點(diǎn)與我們的設(shè)計(jì)目標(biāo)相互呼應(yīng)。
- 高彈性:存儲層可獨(dú)立擴(kuò)縮容,計(jì)算層完全不感知;
- 高性價(jià)比:在 LogStore 實(shí)現(xiàn)了高性能 Log 存儲 & 在 PageStore 實(shí)現(xiàn)了低成本 Page 存儲;
- 兼容性好:LogStore 和 PageStore 都支持多 DB Engine 插件化;
- 高可靠:PageStore 側(cè)支持 Segment 級別的 PITR 功能。
核心技術(shù)
以下主要從研發(fā)背景(Problem)、解決思路(Solution)、解決成效(Outcome)三個(gè)方面來分別介紹 veDB 分布式存儲系統(tǒng)的五個(gè)核心技術(shù)。
Distributed Data Model
Problem:從單機(jī) FS 到分布式存儲,需要有高效的數(shù)據(jù)布局模型。基于單機(jī)的文件系統(tǒng)或塊存儲系統(tǒng)去實(shí)現(xiàn)數(shù)據(jù)持久化是比較簡單的,我們可以直接通過申請一批 LBA 或者一批文件來存儲數(shù)據(jù),然后控制并發(fā)即可,但是這對于分布式存儲并不容易。從上面的示意圖可以看到,最上層的 Tablespace 代表一張數(shù)據(jù)庫表,里面可能包含上百萬甚至上千萬的 Page data(數(shù)據(jù)庫的基礎(chǔ)管理單元)。然而存儲系統(tǒng)的管理單元,卻不可能是 Page —— Page 的粒度過小,往往只有 KB 級別,如果存儲層以過小的粒度去管理數(shù)據(jù),可能會造成元數(shù)據(jù)膨脹,增加管理成本。
Solution:Tablespace -> Segement 分布式映射?;谏鲜鰡栴},我們可以在存儲層利用相對大的管理單元 Segment 去進(jìn)行數(shù)據(jù)管理。此時(shí),數(shù)據(jù)庫的管理單元是 Page,存儲系統(tǒng)的管理單元是 Segement。Tablespace 和 Segement 之間必然要存在一層映射關(guān)系,該映射關(guān)系可以根據(jù)不同數(shù)據(jù)庫引擎的數(shù)據(jù)管理空間大小要求進(jìn)行設(shè)置,可能 MySQL 和 PostgreSQL 的映射規(guī)則就大不相同。上述示意圖展示了最簡單的模 2 規(guī)則,我們也可以發(fā)展出其他更加復(fù)雜的打散規(guī)則,此處不進(jìn)行贅述。當(dāng)我們將 Page 打散到對應(yīng)的 Segement 之后,數(shù)據(jù)庫就不需要管數(shù)據(jù) Replication 的邏輯,不管底層存儲是多副本還是 EC 策略,可以完全由存儲系統(tǒng)來做透明的 Replication ,數(shù)據(jù)庫就像在使用單機(jī)文件系統(tǒng)一樣簡單。
Outcome
- 天然負(fù)載均衡;
- 分布式打散,可最大程度實(shí)現(xiàn)并行計(jì)算;
- Scale in/out 簡單,僅需部分 Segement 數(shù)據(jù) rebalance,摒棄了將整個(gè)數(shù)據(jù)庫表的 TB 級數(shù)據(jù)在硬盤間搬遷的繁雜流程。
Log-Only Segement
Problem:數(shù)據(jù)冗余成本高,需要降低存儲成本。
Solution:開發(fā) Log-Only Segement,節(jié)省非必要的 Page 副本空間。什么是 Log-only segement? 關(guān)系型數(shù)據(jù)庫中往往都包含 Log 數(shù)據(jù)和 Page 數(shù)據(jù)。在存儲層中,存了多副本的 Log 數(shù)據(jù)后,我們可以選擇性地只回放一部分 Log 數(shù)據(jù)來生成 Page,讓另一部分 Log 數(shù)據(jù)保持不動(dòng),不要生成任何 Page 數(shù)據(jù)。以上面的示意圖為例,Rep_0 和 Rep_1 都是 Log 數(shù)據(jù)生成的各種版本 Page 數(shù)據(jù),然而 Rep_2 是一個(gè)空的 Page 數(shù)據(jù)副本,它里面只有 Redo log。我們都知道 Redo log 和 Page data 的數(shù)據(jù)大小比例是比較夸張的,Page data 的大小可能是 Redo log 的幾倍甚至十幾倍,因此通過以上方法能夠較大的節(jié)省單機(jī)的 Page 存儲空間。
Outcome:結(jié)合單機(jī)引擎的壓縮算法,能將存儲空間放大倍數(shù)從 3.x -> 1.x,較好緩解成本問題。
高性能 IO 引擎
Problem:存儲層寫性能容易成為系統(tǒng)性能瓶頸,如何解決?
Solution:全異步 IO + 無鎖結(jié)構(gòu) +并發(fā)打散。當(dāng)數(shù)據(jù)庫提交了一個(gè) Redo log 到 Log Storage 之后,Log Storage 中會有一個(gè)無鎖的 Ring buffer 去對 Redo log 進(jìn)行有序組織,然后我們將 Redo Log 的 Ring buffer 進(jìn)行線性的定長切割,并發(fā)打散到底層存儲的 Blob 單元。
Outcome:4KB + depth 8,write latency ~100+us,較好支撐了數(shù)據(jù)庫下發(fā)日志的性能剛需。
PITR
PITR(Point-in-time Recovery)是指我們都可以迅速地恢復(fù)在過去一段時(shí)間內(nèi)某個(gè)時(shí)間點(diǎn)的數(shù)據(jù)庫快照。
Problem:如何快速備份恢復(fù),且降低對前臺業(yè)務(wù)影響?
Solution:基于 Segement 的高并發(fā) PITR 機(jī)制,Segement 間互不影響。之前提到存儲層的管理單元是 Segement ,我們也可以基于 Segement 做備份恢復(fù)。這樣做有兩個(gè)好處:首先計(jì)算層是完全透明的,計(jì)算層完全不會感知,并且計(jì)算層的性能不會抖動(dòng)。其次基于 Segment 可以做到天然的并發(fā)打散,因此備份恢復(fù)也可以做到并發(fā)恢復(fù)。
Outcome
- 性能優(yōu)秀,恢復(fù) 1TB 數(shù)據(jù) ~15min;
- 擴(kuò)展性強(qiáng),不受數(shù)據(jù)大小影響,性能與數(shù)據(jù)大小呈近常數(shù)關(guān)系:因?yàn)榛?Segment 單元去做并發(fā)備份恢復(fù),每個(gè) Segment 都是獨(dú)立的,其性能能夠與數(shù)據(jù)大小解耦開來。因此不管數(shù)據(jù)大小是多少,只要備份恢復(fù)資源足夠,都能做到常數(shù)級的備份恢復(fù)性能。
多計(jì)算引擎插件化
Problem:數(shù)據(jù)庫團(tuán)隊(duì)希望統(tǒng)一的存儲層能夠支持不同的數(shù)據(jù)庫引擎,做到 100% 兼容和快速接入。
Solution:Write Ahead Log + Log Replay = 任意 Page Data?;诒镜卮鎯σ娴?k-v 結(jié)構(gòu),或者基于裸的塊設(shè)備抽象出一種相對通用的數(shù)據(jù)結(jié)構(gòu),從而高效地存儲 Page data。同時(shí),我們在 SDK 側(cè)和 Server 側(cè)都做了 Log parse 的插件化,要接入新的數(shù)據(jù)庫引擎只需要其提供適配存儲接口的日志插件,從而可以快速接入各式各樣的數(shù)據(jù)庫計(jì)算引擎。
Outcome:
- 基于統(tǒng)一接口,計(jì)算引擎僅需提供 Log parse + Replay lib 即可接入 veDB 存儲層。
- 統(tǒng)一存儲層已支持 MySQL、PostgreSQL、MongoDB 計(jì)算引擎,目前仍在持續(xù)拓展。
數(shù)據(jù)庫存儲系統(tǒng):What's Next
在談及數(shù)據(jù)庫存儲的未來演進(jìn)時(shí),首先我們可以思考一下哪些因素會觸發(fā)數(shù)據(jù)庫存儲架構(gòu)的變革和演進(jìn)?答案可能包含:存儲架構(gòu)自身的革命、數(shù)據(jù)庫理論的突破、或者新硬件沖擊引發(fā)存儲系統(tǒng)架構(gòu)迭代?;谶@三個(gè)方向的思考,我們總結(jié)了以下幾個(gè)數(shù)據(jù)庫存儲系統(tǒng)的演進(jìn)趨勢:
HTAP/HSAP
我們總結(jié)的第一個(gè)趨勢即 HTAP/HSAP 系統(tǒng)將會逐漸爆發(fā)。在 HTAP/HSAP 系統(tǒng)中,“實(shí)時(shí)”是第一關(guān)鍵詞。為了支持實(shí)時(shí),存儲系統(tǒng)可能會發(fā)生架構(gòu)演進(jìn)和變革,因此我們需要探索:
- 行列存 All-in-one:既要存儲行式的數(shù)據(jù),又要存儲列式的數(shù)據(jù)。
- 近實(shí)時(shí),寫時(shí)計(jì)算:我們需要在存儲層實(shí)現(xiàn)寫時(shí)計(jì)算的邏輯來支持實(shí)時(shí)性。
AI Enhancement
AI 技術(shù)運(yùn)用領(lǐng)域廣泛,具體在數(shù)據(jù)庫存儲領(lǐng)域,我們可以利用 AI 技術(shù)進(jìn)行以下工作:
- 存儲參數(shù)調(diào)優(yōu);
- 智能存儲格式:利用 AI 技術(shù)進(jìn)行智能的行存和列存格式轉(zhuǎn)換,AI 可以提醒我們什么時(shí)間進(jìn)行轉(zhuǎn)換,什么時(shí)候絕對不能轉(zhuǎn)換,從而避免格式轉(zhuǎn)換為前臺業(yè)務(wù)帶來的性能 overhead。
Hardware Revolution
在硬件變革趨勢上,我們總結(jié)了三個(gè)變革方向:
- 存儲介質(zhì)變革:前幾年,我們可能更多關(guān)注 SSD、HDD。目前我們處于 SSD 往 persistent memory 轉(zhuǎn)變的風(fēng)口,那么如何利用 persistent memory 去定制軟件架構(gòu)?目前看在文件系統(tǒng)側(cè)已經(jīng)有一些研究,但是在數(shù)據(jù)庫側(cè)并沒有太多公開實(shí)踐。
- 計(jì)算單元變革:CPU 產(chǎn)品已經(jīng)從 multi-core 變成了 many-core (從 96c 變成了 192c、384c)。要怎么利用多核的能力?對于非計(jì)算密集型的存儲系統(tǒng)而言,多余的算力能否用來加速數(shù)據(jù)庫算子?一些無鎖的數(shù)據(jù)結(jié)構(gòu)是不是需要要重新設(shè)計(jì)?以上都需要我們認(rèn)真考慮。
- 網(wǎng)絡(luò)設(shè)施變革:例如 RDMA ,以及可編程的 P4 交換機(jī)這類全新的一些網(wǎng)絡(luò)設(shè)施,可能會對我們的軟件架構(gòu)特別是分布式存儲架構(gòu)造成較大的沖擊。相應(yīng)地,我們需要在存儲側(cè)做出調(diào)整。