
大數據和雲原生時代,企業數據量迎來急劇增長、數據類型日益豐富多樣,各類新興業務對數據庫的各項能力不斷提出挑戰。伴隨雲原生理念的廣泛傳播和真正落地,如何進一步整合雲計算基礎設施和數據庫,推動數據庫實現高可擴展、高度自動化、快速部署,以滿足企業真實業務需要,已成為業界關注的重要方向。火山引擎veDB 是一款全託管的雲數據庫,簡單易用,兼容 MySQL、PG 和 MongoDB 等數據庫引擎,業務代碼幾乎無需修改即可接入使用。據介紹,veDB 的架構遵循的是「分離」哲學,包括計算和存儲的分離、日誌和數據的分離、讀寫分離,因此veDB擁有敏捷靈活、性能和容量大、低成本、高可靠、高可用等優勢。火山引擎是字節跳動旗下的雲服務平台,也是字節跳動最佳技術實踐的對外輸出窗口。本文將圍繞 veDB(for MySQL)在字節內部遇到的各類挑戰和解決方案,介紹企業級大規模數據庫的演進歷程。veDB 整體架構呈現計算與存儲分離的總體特徵。以 veDB(for MySQL) 為例,其技術架構具有以下特點:
完全兼容 MySQL :計算層基於 Percona Sever 8.0 版本打造,完全兼容字節線上的 MySQL 生態,字節的應用遷移到 veDB 上不需要任何改造;計算存儲分離:雲原生架構,計算引擎能力和存儲能力均可以獨立擴展;超大實例容量:數據庫容量不受單機磁盤容量限制,存儲層容量可以按需擴展;只讀線性擴展:添加只讀節點無需拷貝數據,並且只讀節點支持頁面級別 REDO 並行回放技術,提供低讀延遲(~10ms);閃速備份恢復:存儲層支持快照備份,通過 PITR 技術快速恢復至歷史任意時間點。
veDB(for MySQL) 具體的發展歷程如下圖所示:

目前,在字節跳動內部,veDB 已大規模接入線上業務,包括在國內預生產環境已完成了對 RDS MySQL 的全量替代,已接入國內生產環境 ~40% 的業務庫,基本覆蓋所有業務門類。預計到 2022 年底,veDB 將替代內部 RDS MySQL 約 80% 的國內業務,並使綜合成本下降約 30%。同時,veDB 也已亮相字節跳動雲服務平台火山引擎,對外提供 NewSQL 類 DBaaS 服務。
在構建 veDB(for MySQL)的過程中,團隊遇到了以下三方面的技術挑戰:
相比於本地的 NVME SSD 磁盤,計算存儲分離架構會帶來時延的增加;由於業務會有各式各樣的個性化訴求,需要對其個性化的數據進行優化。雖然計算存儲分離架構帶來了一定的靈活性,但是相對於傳統的單機主備架構,也帶來了寫日誌和讀頁面的時延增加。具體來說,比如傳統單機主備架構的讀寫時延大概是十微秒,但是計算存儲分離架構由於經過一層網絡 TCP/IP,時延大概是 1 毫秒左右,這會造成部分時延敏感性業務體驗受損。解決方案
共享內存寫緩存/NVME SSD 讀緩存:首先,Redo 日誌在寫入遠端 Log Store 之前,會寫入一個共享內存,然後再批量寫入遠端,這樣做雖然類似於原生 MySQL 異步提交,但效率上要比異步提交高很多;其次,團隊提供了二級讀緩存的特性,雖然本地盤比較小,但是也有一部分存儲空間,團隊利用這部分存儲空間來實施 Buffer Pool 的擴展,大大減少對遠端 Page Store 的讀取操作;頁面預取/計算下推:團隊面對部分業務場景可以針對性地進行頁面預取優化,提前從 Page Store 讀取頁面到緩衝區,可以避免在有需求時臨時讀取;RDMA/AEP Store:這個其實類似於 PolarDB 讀寫 Polar FX ,但是 RDMA 部署會受到一些條件限制,團隊目前是在有部署條件的場景下推行使用 RDMA 網絡,並且結合 AEP Store 優化讀寫時延。通過以上解決方案,總體而言,veDB(for MySQL)與本地 NVME SSD 磁盤相比,在延時敏感型業務體驗上變化不大,在部分場景有更優表現,因為veDB去除了許多操作,比如不用刷髒頁、沒有 Checkpoint。什麼是讀延遲?主備架構中的主機寫完數據之後,備機不能立刻讀取數據,這其中的延遲被稱為讀延遲。這其中面臨的問題是什麼呢?首先是傳統的主備架構,比如 MySQL 基於 Binlog 同步,只讀節點延時受業務負載影響比較大,雖然它在 v5.7 版本引入並行回放機制改善了讀延遲,但是並沒有完全克服問題;另外計算存儲分離之後,團隊並不是基於 Binglog 構造數據,而是基於 Redo 日誌構造數據,那麼 RO 節點如何同步數據呢?團隊提供了以下技術解決方案。頁面(Page)級別並行回放機制:首先只讀節點啟動後,veDB會基於最新快照,持續從共享存儲去拉取物理日誌並行解析回放,值得注意的是,veDB可以根據不同的規格選擇不同的並行機制;
Redo 日誌拉取(Pull)模式:這種模式下的計算節點讀寫,RW 節點和 RO 節點之間並沒有直接的網絡交互(如上圖所示),只讀節點從存儲池拉取 Redo 日誌;
Redo 日誌推送(Push)模式:Push 模式是指在 RW 節點完成日誌持久化之後,直接把 Redo 日誌推到只讀節點。
為什麼會有 Pull 和 Push 兩種模式呢?兩種模式效果並不相同,在 Pull 模式下,veDB能夠支持 30+ RO 節點,但此時讀延時較高,大約在 100 毫秒左右。在 Push 模式下,veDB能夠支持 15 個左右的 RO 節點,此時讀延時較低,大約在 10 毫秒左右。對於如何更快從 MySQL 遷移數據,傳統方式是 MySQL 本身支持 Dump、Restore, 或者第三方工具支持 Myloader Mydumper ,但由於存儲計算分離,這種邏輯轉換方式的性能表現並不佳。
首先,考慮到 InnoDB 存儲層物理頁格式是一致的,veDB引入 Fastloader 工具直接把頁面批量寫入到存儲層(Page store),其中,有些信息需要更新,比如 InnoDB 表的 Space ID 、索引的 ID 、 LSN等。同時,veDB目前支持 MySQL 5.6/5.7/8.0 去導入數據。優化效果如何呢?總體而言,對於24G(1億條)數據,用時從 1637s 降到 32s,性能提升了 51 倍;對於 69G(3 億條)數據,用時從 21585s 降到 95s,性能提升了 227 倍。
原生 MySQL V8.0 版本支持 Instant 的 DDL 特性,雖然比較快,但是它適合的場景有限。另外,MySQL 備機回放 DDL 的 Binlog 時,它會引入一個較大的主備時延,只有 DDL 執行完成,它才能執行其它進程,這會造成較大的問題。目前各大互聯網公司包主要運用兩個第三方工具 Ghost 和 Pity online schema change ,來解決主備延時的問題,不過執行效率較低。尤其對veDB來說,存儲計算分離之後,表變得更大,延時問題更加凸顯。解決方案
veDB引入了 FastDDL 來解決以上問題,具體而言:
主鍵索引(源表)頁面預取:DDL 分為兩個階段,第一個階段是對原表主鍵索引進行全表掃描,然後在這一階段進行精準的頁面預取;
並行構建(按索引,多機房):第二個階段就是並行構建,比如按照索引並行構建,團隊要重新構建表,其中包含 10 個索引,那麼會有 10 個並發;同時,如果機房之間通過 Binlog 去同步,團隊可以多機房一起執行;
存儲層 Write-Through:veDB 架構的特點是 Log is Database。為了優化 DDL,團隊直接寫入 Page store,Bypass 了 Log store,起到一定的加速作用。

關於優化效果,主要有以下三點:
普適:基本上 MySQL 能夠支持任何 Online DDL的操作;快:它作為一個內部實現工具,是 gh-ost 速度的十分之一;靜:團隊不引入 Binlog 主備延時,比如機房分別執行 DDL,等待它們執行差不多,最後 relay 就能完成。在 MySQL 裡面,複雜查詢基本都是單線程執行,而單線程的任務一般比較重,包括解析、優化、數據讀取和執行,計算存儲分離會導致跨網絡讀取頁面的時延大大增加。如果複雜查詢出現 catch miss,即在緩衝區找不到數據,就需要從存儲區拉取數據。這種情況頻繁發生,性能會變差。另外,單庫容量也大大增加了,數據容量從原來的幾T 變成了現在的幾十T,也加大了複雜查詢的負擔。解決方案
計算操作(部分)直接下推存儲層:當執行任務時,團隊會去查詢 B+ 數的倒數第二層節點,得到需要分發的頁面 ID 情況,並且把任務並行分發給存儲層;並行任務分發:團隊引入了並行執行查詢算子, SQL 層完成分發之後,可以通過算子收集結果,匯聚並返回給計算層;回表查詢頁面精準預取:對於回表查詢,比如查完二級索引之後,需要去主鍵索引上查詢,團隊會做預取。基於以上方案,veDB也取得了不錯的效果。比如對於 count(*) 操作,在不增加計算層 CPU 負擔的情況下,veDB實現了 5 倍 到 100 倍左右的提升;同時,以 TPCH (100G) 的數據集測試為例,對於緩衝區命中率低和命中率高的兩種情況而言,從最壞到最好的情況,會有 2 倍到 10倍的提升。在類似於秒殺、限時搶購、搶紅包等場景下,大量用戶需要在極短時間內請求商品或者紅包,此時數據庫將面臨單行記錄大並發更新的老大難問題。通常,這會導致系統活躍線程增加、 TPS 降低、時延增加、系統吞吐降低。針對這種情況,目前有兩種解決方案,其一是不依靠數據庫,在應用層進行處理,但是這種方案較為複雜;其二就是依靠數據庫來解決問題。
解決方案
SQL 語句更新列熱點標識:對於帶有熱點更新標識的 SQL ,團隊會在數據庫內部維護一個哈希表,會將相同數據的 SQL 放在哈希表的一個隊列里;批量處理:經過一段時間之後,團隊會針對隊列里的 SQL 進行合併處理;語法糖:為了更好地滿足業務需求,veDB支持打開 Binlog ,以及語法糖,比如:Auto_Logic_Commit_Rollback hint,是指成功就自動提交,否則 Rollback;另外veDB擴展 Update..returning,支持語句返回。
基於以上操作,實施效果如何呢?以一個 20c 的虛擬機為例,單行更新性能能夠達到 9.3w QPS,多行更新性能能夠達到 14.7w QPS。同時,團隊目前已經上線了電商直播、抖音的本地生活,在業務直播帶貨過程中,即使熱點商品 QPS 突然猛增到 1.5 萬,veDB數據庫也能夠提供足夠的支持。
對於一寫多讀的架構,是如何做到多寫呢?團隊結合了字節內部的分庫分表的中間件與 veDB,提供 Multimaster 支持。但是分庫分表的 Multimaster 在寫流量較大的場景時(例如 618 或雙 11 活動)會面臨集群擴容的問題,活動結束後又會面臨集群縮容的問題。傳統基於中間件的分庫分表方案擴容需要複製數據,會面臨兩個問題:其一是耗時與數據量成正比,其二是還需要一倍的冗餘空間,因此需要擴容。另外,分庫分表目前沒有一個有效的在線縮容方案,會導致在大型網絡活動結束後,沒有辦法及時縮容。
由於計算存儲架構分離,數據都在存儲層。veDB引入共享表的概念,共享表可以在不同的數據庫實例之間進行共享,而不同的實例在 MySQL 都有相同的 space ID。同時,共享表的所有權(包括讀和寫)在任何時候都屬於一個實例,它可以在實例之間動態切換,團隊僅需要變更元數據的信息,無需移動數據。
其實施效果如何呢?首先是擴容效果:假如分庫分表有 1000 partitions,並且 1000 partitions 都在一個實例上,而要擴展成兩個實例,此時擴容只需要在 1min 內就能完成 。這樣做的好處是拆分過程與數據量無關,只需要提前準備好計算節點資源(虛機 或 容器),即可迅速完成拆分。其次是縮容效果:縮容的方式與擴容的方式是一致的,它也是變更元數據,因此縮容與擴容的效率基本相同。首先來了解一下 veDB(for MySQL) 在字節的部署情況,主要包含高可用和高可靠兩種部署方案。
目前 veDB (for MySQL)是三機房部署,對於高可用部署方案,團隊分別在每個機房部署了存儲池,如下圖所示。第一個存儲池用於存儲 Redo 日誌,第二個存儲池用於存儲 Binlog,第三個存儲池是 Pagestore。為什麼要將 Redo 和 Binlog 分開存儲?因為 Redo 的存儲實現與 Binlog 不同,Redo 只需要存儲兩個小時,而 Binlog 需要存儲一周左右,因此它們所需的容量是完全不同的。如果將二者都放入 SSD,會對 SSD 造成極大的容量負擔,因此需要兩個存儲池。其次團隊會在主機房(DC1)部署三個計算節點,在副機房(DC2、DC3)部署兩個計算節點。DC1、 DC2 和 DC3 通過 Binlog 同步,但是在機房內是通過 Redo log 同步。該部署方案的優點:一是保證最大服務可用性(RTO ~= 0),通過 Binlog 方式同步,無論哪一個機房出現問題,DC1 依舊可以提供服務;二是性能好/時延低,因為事務提交沒有跨機房延遲,技術團隊利用 RDMA + AEP Store 能夠提供進一步加速。該部署方案的缺點:比如跨機房不能保證 RPO=0、副機房只讀節點的讀延遲會較高。如上圖所示,在 DC2 中,第一個只讀節點的延遲是 Binlog 延遲,第二個只讀節點的延遲是 Binlog 的延遲 + Redo log 的延遲。在主機房 DC1 中只有 Redo log 的延遲。因此,該部署方案適用於對可用性和性能要求較高的業務場景,同時在極端情況下,比如機房整體故障,可能會損失失一點數據。
該部署方案也可以被稱為為三機房六副本,它是存儲層跨機房部署,只包含兩個存儲池:LogStore SSD for redo&binlog,和 PageStore。由於它不需要依靠 Binlog 在機房間同步數據,所以可以不需要 Binlog 或者保留時間可以比較短,因此無需專門的 HDD 存儲池來存儲 Binlog。該部署方案的優點:一是保證最大數據可靠性:它依靠存儲層通過 column 協議保證數據可靠,因此如果機房出現問題,其中的數據也不會消失;二是只讀節點都延遲低:該方案中只有 Redo 日誌延遲,比如 DC1 延遲是10毫秒左右,DC2 的延遲大概是 20毫秒左右。該部署方案的缺點:一是性能相對較差:如果要保證 RPO = 0,事務性能相對會較差;二是時延相對較高:因為事務提交時需要寫三副本或者兩副本,這個過程中必然會存在跨機房的 RPC 延遲。該部署方案適用於對數據可靠性要求比較高的業務,比如支付業務場景;同時它也適用於 QPS 不高,但是對備機讀延遲敏感的業務。veDB(for MySQL) 主要被應用於以下四類業務實踐:小微庫
該庫的特點是數據量較小,QPS 較低。100% 預生產環境實例和部分生產環境實例都屬於這類情況,使用 veDB 可以顯著地提升資源利用效率。該庫的特點是數據量超大,QPS 較低,有複雜查詢。錢包歷史數據,財經歷史數據都屬於這類。以往的歷史數據都是導出到 Hive 或者其它的分析系統,需要經過一個 ETL 過程。另外基於 MySQL 開發的一些應用,在 Hive 上需要重新開發。如果通過 veDB 來做歷史庫,就不會有以上問題,上層應用無需二次開發適應異構類型的歷史庫。單體庫無法對分庫分表進行拆分,該庫的特點是數據量大,QPS 較高,時延較低。廣告庫和財經庫都屬於這類情況。此時,團隊會通過 基於 RDMA 的 AEP Store 或者本地的二級讀緩存,來應對該庫的問題與挑戰。該庫的特點是數據量超大,QPS 較高,時延較低,同時它能夠分庫。目前,團隊上線了激勵中台和 Ad-Hoc 節假日活動。veDB目前往以下方向發展:
Multi Master 2.0 的架構如下圖所示,一共包含三層,分別是接入層、計算層、存儲層。veDB主要提供了以下三方面的支持:
DDL :團隊會提供一套完整的 DDL 語法(包括表、索引),之前採用分庫分表的方式時,DDL 通常是通過DBA提交工單的方式去執行的,對外肯定是不行的;同時,也會提供 Online 操作,允許並發 DML;增強分布式事務/查詢:支持分布式一致性讀/寫,以及支持全局二級索引;其它:還會進行一致性備份/恢復,融入 Quick Resharding。MySQL 的查詢能力是比較弱的,通常會把 MySQL 的數據導入到其它系統中去做查詢,這會產生額外的部署成本。因此團隊希望增強 MySQL 的查詢能力,具體來說,會引入列存儲索引、向量化引擎和一些其它的優化支持,以此來解決部分的 OLAP 場景需要的複雜查詢能力。
在這個方向上,veDB主要提供了以下三方面的支持:
MM 事務引擎:MemTable 本質上是一個單獨的存儲引擎,獨立於 InnoDB 之外,與 InnoDB 實現方式完全不同,但是能夠達到 10 倍的 InnoDB 性能;另外,veDB提供了完整的 ACID 能力;MM 存儲引擎:深度融合了 RDMA/AEP,另外,日誌和數據一體化;編譯執行:在 SQL 語句上執行了預編譯優化,大幅減少了 CPU 執行指令數。除了上述外,團隊下一步還會在新硬件、無服務器方面對veDB進行迭代。新硬件方面,veDB會融合RDMA/AEP/DPU;無服務器方面,veDB會開展自主擴縮容、「自動駕駛」等。
目前, veDB 正對外開放邀測。點擊「閱讀原文」可訪問火山引擎官網,企業註冊賬號即可聯繫工作人員,參與 veDB 邀測。