王聰聰, 胡卉芪
(華東師范大學 數據科學與工程學院, 上海 200062)
在過去的十年中, 云計算技術經歷了飛速的發展. 在這一趨勢的影響下, 數據庫市場的重心已經逐漸從企業內部向云端遷移. 眾多商業云數據庫, 如亞馬遜的Aurora[1]、阿里巴巴的PolarDB[2]等, 已被廣泛應用. 云數據庫是一種基于云計算平臺的數據庫系統, 以服務形式提供給用戶. 該系統利用云計算中的資源池技術, 將數據庫的存儲、計算和網絡資源進行統一管理和調度, 以提供靈活、可靠、易用且可擴展的數據庫服務.
云原生數據庫的崛起改變了人們對于數據庫設計前景和權衡的理解. 在分布式系統設計中, 對于無共享架構和共享存儲架構的選擇, 存在著深入的爭論. 許多早期的在線事務處理 (on-line transaction processing, OLTP) 系統, 例如Spanner[3]和CosmosDB[4], 都積極推動使用無共享架構以擴展OLTP 系統, 從而超越單一系統的處理能力. 然而, 在云數據庫的發展背景下, 這種立場可能已經不再那么明確. 云服務為數據庫帶來了全新的需求, 例如計算層和存儲層的彈性擴展, 以及靈活適應各種負載場景等. 在這些新需求的推動下, 共享存儲架構再次受到了重視.
許多云原生數據庫如Aurora[1]和Socrates[5], 都已經采用了共享存儲架構. 在這一架構中, 首先所有的寫入事務都在一個主節點上完成, 其次該主節點將其寫前日志 (write-ahead log) 發送到共享存儲, 以供次級節點訪問. 存儲節點利用這些寫前日志在后臺重建數據頁, 而這些重建的數據頁可以被次級節點按需讀取, 從而以較低的開銷可在任何時候生成. 雖然這種設計提供了如熱故障切換和彈性負載均衡等重要功能, 但其仍然受到主節點處理能力的限制. 這種限制在OLTP 常見的寫入密集型負載場景下尤為明顯.
在共享存儲架構中簡單地添加多個主節點并不能解決這一問題, 因為如果允許多個讀寫節點同時修改共享存儲層, 那么為了保證數據的一致性, 讀寫節點之間的競爭將使共享存儲層成為新的瓶頸.一個可能的解決方案是采用共享緩存架構, 即在共享存儲架構中增加一層共享緩存, 從而讓多個讀寫節點可以在共享緩存中修改數據項, ScaleStore[6]、Oracle RAC[7]和NAM-DB[8]都采用了這種設計. 然而, 這種設計需要解決緩存一致性的問題, 主要有兩種方式: 一種是節點始終在共享存儲中進行讀寫;另一種是節點間通過緩存一致性協議來保證數據的一致性. 雖然共享緩存架構可以有效滿足云計算場景對內存擴展的需求, 但在提高數據庫事務的可擴展性方面, 仍有許多待解決的問題, 例如時間戳的擴展瓶頸及事務持久化速度的緩慢等.
時間戳用于在事務中提供快照, 然而在共享緩存數據庫中經常使用的全局時間戳存在線程級別的擴展性限制, NAM-DB[8]中采用的向量時間戳利用每個機器的時鐘解決了線程級別的擴展性瓶頸,但仍然無法擴展到多臺節點上. 此外在共享緩存數據庫中, 新的頁面會被快速地添加到節點的緩存中,因此共享緩存數據庫需要快速地驅逐冷頁到存儲層, 然而由于頁面需要在共享存儲層完成持久化, 整個傳輸過程依然較慢, 如何實現快速持久化是一個重要挑戰.
針對上述問題, 本文在共享緩存架構的基礎上, 結合新型硬件—持久化內存, 實現了一個具有三層共享架構 (包括內存層、持久化內存層、存儲層) 的數據庫. 在此架構的基礎上, 重新設計了事務的執行流程, 并針對若干關鍵技術進行優化, 以進一步提升數據庫事務執行的性能. 本文的主要貢獻有以下兩點.
(1) 提出了一種解耦事務執行流程的共享架構. 通過利用持久化內存技術, 對共享緩存架構進行了重新設計, 引入了共享持久化內存層, 并對數據庫事務的執行流程進行了優化. 本文利用持久化內存層的重做日志回放功能來重新生成頁面, 從而消除了從共享緩存層驅逐臟頁到存儲層的過程. 這一設計有助于消除共享緩存的頁面驅逐過程, 并實現快速的持久化.
(2) 提出了分布式向量時間戳技術. 為了解決在共享緩存架構下, 事務時間戳可擴展性的瓶頸問題, 本文利用RDMA (remote direct memory access)的原子更新能力, 設計了分布式向量時間戳技術.這項技術將時間戳分布式地維護在各個節點上, 并提供了一個一致的時間戳快照, 從而在多節點環境下顯著提升了時間戳的可擴展性.
本章首先回顧了分布式數據庫中的共享存儲架構和無共享架構; 其次介紹了云數據庫中的共享緩存架構; 最后針對當前共享緩存架構的一些問題提出了本文的架構設計—共享持久化內存架構.
分布式數據庫的架構可分為共享存儲架構(圖1 (a)) 和無共享架構(圖1 (b)) 兩種. 在共享存儲架構中, 所有節點都可以訪問數據庫的存儲層; 然而, 在無共享架構中, 每個節點都有其私有存儲, 其他節點無法訪問.

圖1 分布式數據庫的4 種經典架構Fig. 1 Four classical architectures for distributed databases
許多分布式數據庫, 如IBM DB2[9]、AlloyDB[10], 采用了共享存儲架構. 這些數據庫允許多個節點訪問共享存儲的讀副本, 但通常只有一個讀寫節點執行更新. 在讀密集型負載下, 這種單一更新節點的架構能實現良好的擴展性. 計算與存儲的分離架構能滿足云計算場景下的快速故障切換和彈性需求, 同時系統的復雜度相對較低. 然而, 這種架構在處理寫密集型負載時的擴展性受到單一節點能力的限制.
在無共享架構中, 數據庫被分為多個分區, 每個分區僅由相應的讀寫節點更新. 該架構通過數據分區將數據分散在各個節點上, 各節點負責對其本地分區進行讀寫操作. 現有的數據庫系統如MongoDB[11]及Cassandra[12]等, 主要依靠兩階段提交來確保分布式事務的原子性. 與共享存儲架構的單一讀寫節點相比, 該架構的可擴展性更好, 其可以通過分區將工作分配到對應的節點上. 與之相反,在處理帶有熱點的傾斜訪問時, 由于對熱鍵的請求都集中在這些節點上, 被頻繁訪問的少數節點可能成為新的瓶頸. 從彈性擴展的角度看, 由于增加新的節點可能需要對數據庫進行重新分區, 該架構的彈性相對較有限.
在共享存儲架構中, 允許多個讀寫節點同時對存儲層進行更新的情況下, 主要問題在于存儲層可能會受到來自不同讀寫節點的大量請求, 這將成為一個瓶頸, 從而限制存儲層和計算層的可擴展性.此外, 每次數據訪問都需要進行網絡通信以訪問存儲層, 這會顯著增加訪問延遲. 針對這些問題, 圖1 (c)中的共享緩存架構得以被提出. 在共享緩存架構中, 頻繁訪問的數據項被存儲在共享緩存層, 從而顯著降低了訪問延遲. 同時, 共享緩存層允許多個讀寫節點更新數據, 并通過緩存一致性協議來保證數據的一致性. 雖然共享緩存架構在處理傾斜訪問時的可擴展性仍然有限, 但在云服務場景中, 它帶來了許多優點, 如良好的彈性 (新添加的計算節點的緩存可以在訪問過程中逐漸填充), 以及靈活性 (不依賴用戶分區, 可以根據不同負載場景自適應數據的分布).
然而, 在共享緩存架構中, 仍然存在許多需要解決的問題, 尤其是在OLTP 云數據庫的事務處理場景中. 本文主要列舉了以下幾點.
(1) 數據持久化速度慢. 首先, 因為共享緩存層中的頁面是易失性的, 因此數據庫可能需要將臟頁面或日志寫入到存儲層. 其次, 數據從共享緩存層到共享存儲層的傳輸速度較慢, 這降低了寫入速度.這引發了兩個問題: 一是在云場景中, 節點的本地緩存可能會很快被填滿, 如果不能快速驅逐冷頁面,將導致共享緩存的性能受限; 二是數據庫的事務處理流程通常需要先將持久化日志寫入到存儲層, 如果寫入速度慢, 那么可能重新出現事務的可擴展性瓶頸.
(2) 事務時間戳的可擴展性瓶頸. 共享緩存架構沒有解決事務時間戳的可擴展性瓶頸. 主要原因有兩點: 一是共享緩存層的時間戳管理線程不能隨事務數量的增加而增加; 二是在訪問時間戳時, 每個計算節點都會對同一個內存區域進行RDMA Fetch & Add 操作, 對同一內存位置的RDMA 原子操作會隨并發操作數的增加而增加, 而RDMA 原子操作的同步成本很高.
(3) 維護緩存一致性協議目錄的延遲高. 共享緩存通常需要基于目錄的緩存失效協議來保證頁面的一致性.頁面的目錄通常存儲在不同的存儲節點上, 這些目錄主要負責管理基于失效的緩存一致性協議的元數據. 盡管以頁面粒度組織數據可以減少修改目錄的次數, 但由于從共享緩存層訪問存儲層的延遲較大, 所以記錄目錄的成本依然較高.
新型存儲介質持久化內存 (persistent memory, PM) 成功地在共享緩存層和存儲層之間架設了橋梁. 2019 年, 英特爾推出了首款商品化的PM 產品—Optane PM. 一些基于CXL (compute express link) 的解決方案也試圖通過整合DRAM、閃存SSD 以及兼容內存的互連技術來靠近PM. PM 的優勢在于其字節尋址能力、數據持久性以及快速訪問速度. 此外, 結合了RDMA (remote direct memory access) 技術后, PM 可以實現低延遲的數據訪問. 鑒于以上特性, PM 成為了解決共享緩存架構中現存問題的一種理想選擇.
為了解決共享緩存架構存在的問題, 本文提出了TampoDB: 一個在線事務處理 (OLTP) 共享數據庫. 該數據庫整合了RDMA、DRAM、PM 和SSD 等多種技術. 如圖1 (d) 所示, TampoDB 的設計基于三層共享架構, 包括共享緩存層、共享持久化內存層及共享存儲層.
(1) 共享緩存層. 共享緩存層位于存儲架構的頂層, 有最低的訪問延遲, 但又是易失性的. 該層主要用于存儲頻繁訪問的頁面.
(2) 共享持久化內存層. 該層是非易失性的, 提供與DRAM 相近的低延遲訪問性能, 并通過RDMA 實現快速的網絡訪問. 在TampoDB 中, 該層用于存儲持久化日志、日志回放生成的頁面以及緩存目錄.
(3) 共享存儲層. 位于存儲架構的最底層, 其訪問速度相較于共享緩存層和共享持久化內存層較慢. 該層用于儲存所有的數據頁.
本章首先全面概述了TampoDB 三層架構的構成與功能, 并解析了三層之間如何協同工作以執行事務; 其次, 詳細闡述了TampoDB 是如何執行事務的, 以及如何將事務在共享緩存層和共享持久化內存層之間解耦, 從而加快事務的持久化過程; 最后, 介紹了如何將向量時間戳擴展到多個節點, 并提出分布式向量時間戳技術.
TampoDB 的整體架構如圖2 所示. 事務的執行主要在共享緩存層進行, 持久化日志和頁面的回放則在共享持久化內存層完成, 而共享存儲層主要用于存放冷頁面. TampoDB 在共享緩存層和共享持久化內存層中, 都會維護同一個邏輯頁面的兩個物理頁面. 圖中的箭頭展示了事務執行的過程. 對于事務執行過程中需要訪問的頁面, 首先需要判斷該邏輯頁面是否存在于共享緩存層. 如果邏輯頁面不在共享緩存層, 那么事務則需要訪問共享存儲層以獲取所需頁面, 同時, 該頁面的副本也會被添加到共享緩存層和共享持久化內存層. 如果邏輯頁面在共享緩存層, 但對應的物理頁面并未在共享緩存層, 那么就會觸發一次緩存未命中. 這時, 后臺線程會將該邏輯頁面存在于共享持久化內存層中的物理頁面換入共享緩存層, 以便事務能重新讀取. 在事務執行過程中產生的重做日志將會被持久化到PM 層中的日志區域, 并由后臺線程回放, 生成共享持久化內存層中的頁面. 當共享持久化內存的空間受到壓力時, 已完成回放的冷頁面將被驅逐到共享存儲層中.

圖2 TampoDB 的架構概覽Fig. 2 Overview of TampoDB architecture
TampoDB 的事務執行可以分為3 個階段: 執行、持久化和回放.
(1) 執行. 在事務開始時, 會為其分配一個時間戳. 對于事務請求的每個頁面,頁面讀取器將其讀取到本節點的緩存中. 事務完全在本地執行, 由事務管理器將事務執行中產生的私有數據保存在本地.每個已提交的事務都會產生一個重做日志, 它將被臨時存儲在一個線程本地的日志緩沖區中, 以避免線程之間的爭用.
(2) 持久化. 已提交的事務將其在日志緩沖區中的重做日志寫入到本節點的持久化內存中, 此步驟由后臺線程完成. 每個節點都將其私有日志文件寫入, 從而確保事務的原子性和持久性.
(3) 回放. 系統對持久化日志進行回放, 并將其應用到共享持久化內存的頁面中. 由于計算節點可能訪問任何頁面, 因此單個頁面的更改會分散在多個本地日志中. 因此, 在回放過程中, 各節點的本地日志首先會發送到一個節點進行合并后再發送回各節點. 每個節點根據重做日志, 修改持久化內存中的頁面數據. 回放也在后臺進行.
TampoDB 能夠利用持久化的重做日志在后臺執行回放, 將更新同步到共享持久化內存中的頁面.因此, 在事務執行過程中, 一旦完成持久化步驟, 就可以認為事務已經持久化. PM 層和DRAM 層被映射到同一地址空間, 事務執行過程中, 所有對日志數據的訪問都會被重定向到共享緩存中的地址.事務在共享緩存層和共享持久化層之間是完全解耦的, 滿足事務執行中對頁面訪問的較低延遲需求的同時, 又提升了共享緩存層臟頁驅逐的效率, 因為事務持久化過程中不需要對共享緩存層進行臟頁驅逐.
數據庫通常使用一個全局計數器為事務提供全局時間戳 (global timestamp, GTS), 該時間戳代表了事務的執行順序. 然而, GTS 機制不僅增加了事務之間的通信量, 還導致事務并發控制的擴展性存在較大瓶頸.
向量時間戳 (vector timestamp, VTS) 為每個計算服務器或線程維護提供一個邏輯時鐘. 事務開始時需要獲取最新版本的VTS 作為讀取時間戳, 獲取提交時間戳則相對簡單, 只需將對應計算服務器的邏輯時鐘遞增1 (內存服務器也需要同步). 由于事務間的時間戳是獨立的, 長時間運行的事務不會阻止讀取時間戳的前進, 從而消除了線程級別的時間戳擴展性瓶頸. 然而, 目前的VTS 機制無法擴展到多臺服務器, 因為這會導致不同工作線程之間無法獲取一致的快照. VTS 機制受限于單個服務器帶來的顯著問題是VTS 的網絡通信壓力集中在單個節點上, 盡管使用RDMA 原子更新操作延遲較小, 但是性能下降和可擴展性瓶頸依然存在.
將VTS 擴展到多臺服務器引入了一個新問題. 在單臺時間戳節點上, 計算節點讀取到的VTS 滿足單調遞增的關系. 但在多臺服務器中讀取VTS 會使其不再滿足此特性, 因此需要添加一些約束, 以使不同計算節點讀取到的時間戳重新滿足遞增的關系. 一個自然的想法是讓所有計算節點按照相同的順序訪問每臺時間戳服務器. 如果訪問每臺時間戳服務器的訪問延遲是相同的, 這就可以保證多臺服務器之間讀取到的VTS 按照最初的訪問順序是滿足單調遞增的. 但由于每臺時間戳服務器的訪問延遲是不可控的, 必須添加額外的約束, 使多臺服務器之間讀取到的VTS 按照最初的訪問順序保持單調遞增. 因此, 針對VTS 存在的問題本文提出了分布式向量時間戳 (discribute vector timestamp,DVTS), DVTS 在VTS 的基礎上添加了兩個約束規則以支持多節點間的擴展.
規則一: 所有計算節點在獲取時間戳時, 必須按照固定的順序訪問時間戳服務器, 例如 (時間戳服務器1、時間戳服務器2、時間戳服務器3······) .
規則二: 每個計算節點在當前時間戳服務器讀取的VTS 產生的偏序關系要和前一個訪問的時間戳服務器讀取的VTS 產生的偏序關系一致, 即若對于時間服務器n有an≤bn, 則有ai≤bi,?i≤n.
相對于VTS 單個服務器的網絡通信瓶頸, DVTS 利用RDMA 讀取和原子更新操作將時間戳的更新和讀取過程中的網絡開銷分布在多臺服務器中, 避免單一服務器的通信瓶頸. 此外, 由于時間戳分布在不同節點的不同內存區域, RDMA 遠程更新時避免了對統一內存區域的爭議. 此外DVTS 可以實現在多臺服務器之間的擴展, 具有更好的可擴展性.
在具體實現中, TampoDB 為時間戳服務器的每個時間戳維護一個 (tmin,tmax) 可見性區間,tmin和tmax分別代表前一個時間戳服務器訪問當前時間戳服務器時間戳的最小和最大版本. 這個區間記錄了訪問前一個時間戳服務器所生成的時間戳的可見性范圍, 時間戳的可見性區間之間滿足單調遞增的關系. 每個服務器會從當前最新的時間戳開始向前遍歷, 直到找到符合可見性區間范圍的時間戳為止.
本章首先描述了TampoDB 架構由哪些主要組成部分構成(圖3); 其次詳細描述了主要組成部分的功能及實現.

圖3 TampoDB 的組成部分Fig. 3 Components of TampoDB
DRAM 層主要用于處理事務, 由事務管理器負責執行.頁面讀取器負責在事務執行過程中獲取所需的頁面, 可能需要從本節點或遠程節點進行獲取. 在后臺, 分布式向量時間戳會持續生成滿足遞增關系的向量時間戳, 并在事務管理器首次訪問時進行分配. 日志寫入器負責將事務執行過程中產生的重做日志首先寫入日志緩沖區, 以便事務執行過程中的訪問; 其次, 由專門的后臺線程將數據日志和元數據日志寫入PM 的日志區域.頁面的回放也在后臺進行, PM 中的持久化日志的更改會被同步到共享持久化內存中的頁面上.
TampoDB 采用了一種基于目錄的緩存一致性協議, 這種協議能在頁面粒度上提供一致性. 根據緩存一致性協議,頁面根據其所有權被分為3 種狀態: 獨占、共享、失效. 協議通過失效動作來確保頁面的一致性, 例如每當某個節點打算修改一個頁面時, 這個節點就會向跟蹤當前緩存此頁面的節點的目錄并發送失效請求, 從而使頁面由獨占或共享狀態轉換為失效狀態. 每個節點都充當一些頁面目錄的存放節點, 并負責管理存儲在其本地PM 中的目錄. TampoDB 使用頁面ID 定位到目錄,頁面ID 共有64 位, 前8 位記錄節點ID, 后56 位記錄該頁對應目錄的偏移量. 目錄主要包含緩存一致性協議中的元數據. 如上圖所示,頁面P2 被本節點以獨占模式持有, 而頁面P1 則被節點0 和1 以共享模式緩存.
頁面讀取器對頁面的訪問包括本地節點訪問和遠程節點訪問兩種方式. 在本地節點訪問時,頁面讀取器首先會查找位于DRAM 中的頁表, 以判斷頁面是否存在于本地緩存中.頁表中還額外記錄了頁面的所有權信息、頁鎖狀態以及驅逐信息.頁面的所有權分為節點獨占和節點共享兩種. 每個工作線程在訪問頁面之前, 必須先檢查所有權是否正確: 寫入頁面需要在獨占模式下進行, 而讀取頁面則可以在共享模式或獨占模式下進行. 在確認所有權正確后, 就可以對頁面進行加鎖并進行訪問. 而在進行遠程節點訪問時, 如果頁面不在本節點的緩存中, 就需要向該頁面的目錄節點發送請求. 目錄節點的消息處理程序在收到請求后, 也需要先檢查頁面的所有權是否正確. 如果所有權正確, 它會修改緩存目錄, 將該頁面的目錄和頁面數據發送給請求節點.
本章展示了TampoDB 在優化事務執行流程和時間戳后的性能表現. 通過與NAM-DB 進行對比,評估了 TampoDB 在不同負載場景下的吞吐表現. NAM-DB 是一個兩層的 (計算層和內存層) 共享內存數據庫, 其不依賴于緩存一致性協議而是通過RDMA 單邊讀和寫分別讀取和更新遠程數據, 以此來保證數據的一致性. 此外NAM-DB 在事務執行中采用向量時間戳并使用結合RDMA 的SI 協議以進一步提高事務可擴展性.
實驗在兩臺相同配置的服務器上進行, 操作系統為CentOS Linux release 7.9.2009, 服務器硬件配置為 Intel(R) Xeon(R) Silver 4 110 CPU @ 2.10 GHz 16 核心, 32 線程; 內存容量為512 GB; 持久化內存容量為2 TB; SSD 容量為8 TB.
為了對比TampoDB 在讀密集負載場景下的性能, 本文使用YCSB 1B 記錄 (300 GB)的負載, 設置90%讀取和10%寫入訪問分布在2 臺節點上, 圖4 顯示量均勻(uniform)和傾斜(zipf-1)訪問下的吞吐量表現, 在均勻訪問時二者性能接近, 然而在傾斜訪問時由于更多的數據會分布在TampoDB 本節點緩存, 因此性能表現顯著優于NAM-DB.

圖4 TampoDB 在讀密集負載場景下的吞吐Fig. 4 Throughput of TampoDB in read-intensive workload scenarios
為了對比TampoDB 在寫密集負載場景下的性能, 本文同樣使用YCSB 1B 記錄 (300 GB)的負載, 設置90%寫入和10%讀取訪問分布在2 臺節點上, 圖5 顯示了均勻(uniform)和傾斜(zipf-1)訪問下的吞吐量表現, 在兩種情況下TampoDB 表現均優于NAM-DB, 在均勻訪問的情況下寫入速度依然提升了25%. 由于NAM-DB 在寫入時使用RDMA 原子寫的方式, 在傾斜訪問時由于競爭嚴重導致性能下降. 在數據均勻分布時, 由于NAM-DB 需要在事務寫集更新前將日志寫入到內存服務器, 而TampoDB事務執行時對頁面更新和日志持久化是解耦的, 因此寫入性能依舊優于NAM-DB.

圖5 TampoDB 在寫密集負載場景下的吞吐Fig. 5 Throughput of TampoDB in write-intensive workload scenarios
為對比分布式向量時間戳在不同負載場景下的性能表現, 本文同樣使用YCSB 1B 記錄 (300 GB)的負載, 設置在2 臺節點上, 圖6 顯示了在TampoDB 中使用VTS 和DVTS 分別在讀密集和寫密集負載下并且數據均勻分布的吞吐量表現. DVTS 在讀密集訪問和寫密集訪問負載下相較于VTS 吞吐量分別提高了16%和13%. DVTS 將單臺時間戳服務器的讀寫請求分布到兩臺時間戳服務器上, 減少了單臺服務器的時間戳通信開銷.

圖6 分布式向量時間戳在讀寫密集負載下的吞吐Fig. 6 Throughput of DVTS in read-intensive and write-intensive workload scenarios
本文針對共享緩存架構中的數據持久化速度慢、事務時間戳的可擴展性瓶頸、維護緩存一致性協議目錄的高延遲等問題, 基于共享緩存架構設計了TampoDB. 為了解決持久化速度慢的問題,TampoDB 添加了一個共享持久化內存層, 并將事務的執行和持久化過程進行了解耦. 此外,TampoDB 還重新設計了分布式向量時間戳, 并利用PM 加速了緩存目錄的修改, 從而提高了事務的可擴展性. 實驗證明, TampoDB 能高效地執行事務. 當前的研究主要集中在持久化日志方面, 而在后續的研究中, 共享緩存架構下的數據遷移和驅逐策略也是值得探討的問題.