背景介紹

傳統資料庫的主備架構,主備有各自的儲存,備節點回放WAL日誌並讀寫自己的儲存,主備節點在儲存層沒有耦合。PolarDB的實現是基於共享儲存的一寫多讀架構,主備使用共享儲存中的一份資料。讀寫節點,也稱為主節點或Primary節點,可以讀寫共享儲存中的資料;只讀節點,也稱為備節點或Replica節點,僅能各自通過回放日誌,從共享儲存中讀取資料,而不能寫入。基本架構圖如下所示:

一寫多讀架構下,只讀節點可能從共享儲存中讀到兩類資料頁:

  • 未來頁:資料頁中包含只讀節點尚未回放到的資料,比如只讀節點回放到LSN為200的WAL日誌,但資料頁中已經包含LSN為300的WAL日誌對應的改動。此類資料頁被稱為“未來頁”。

  • 過去頁:資料頁中未包含所有回放位點之前的改動,比如只讀節點將資料頁回放到LSN為200的WAL日誌,但該資料頁在從Buffer Pool淘汰之後,再次從共享儲存中讀取的資料頁中沒有包含LSN為200的WAL日誌的改動,此類資料頁被稱為“過去頁”。

對於只讀節點而言,只需要訪問與其回放位點相對應的資料頁。如果讀取到如上所述的“未來頁”和“過去頁”應該如何處理呢?

  • 對於“過去頁”,只讀節點需要回放資料頁上截止回放位點之前缺失的WAL日誌,對“過去頁”的回放由每個只讀節點根據自己的回放位點完成,屬於只讀節點回放功能,本文暫不討論。
  • 對於“未來頁”,只讀節點無法將“未來”的資料頁轉換為所需的資料頁,因此需要在主節點將資料寫入共享儲存時考慮所有隻讀節點的回放情況,從而避免只讀節點讀取到“未來頁”,這也是Buffer管理要解決的主要問題。

除此之外,Buffer管理還需要維護一致性位點,對於某個資料頁,只讀節點僅需回放一致性位點和當前回放位點之間的WAL日誌即可,從而加速回放效率。

術語解釋

  • Buffer Pool:緩衝池,是一種記憶體結構用來儲存最常訪問的資料,通常以頁為單位來快取資料。PolarDB中每個節點都有自己的Buffer Pool。
  • LSN:Log Sequence Number,日誌序列號,是WAL日誌的唯一標識。LSN在全域性是遞增的。
  • 回放位點:Apply LSN,表示只讀節點回放日誌的位置,一般用LSN來標記。
  • 最老回放位點:Oldest Apply LSN,表示所有隻讀節點中LSN最小的回放位點。

刷髒控制

為避免只讀節點讀取到“未來頁”,PolarDB引入刷髒控制功能,即在主節點要將資料頁寫入共享儲存時,判斷所有隻讀節點是否均已回放到該資料頁最近一次修改對應的WAL日誌。

主節點Buffer Pool中的資料頁,根據是否包含“未來資料”(即只讀節點的回放位點之後新產生的資料),可以分為兩類:可以寫入儲存的和不能寫入儲存的。該判斷依賴兩個位點:

  • Buffer最近一次修改對應的LSN,我們稱之為Buffer Latest LSN。
  • 最老回放位點,即所有隻讀節點中最小的回放位點,我們稱之為Oldest Apply LSN。

刷髒控制判斷規則如下:

if buffer latest lsn <= oldest apply lsn
flush buffer
else
do not flush buffer

一致性位點

為將資料頁回放到指定的LSN位點,只讀節點會維護資料頁與該頁上的LSN的對映關係,這種對映關係儲存在LogIndex中。LogIndex可以理解為是一種可以持久化儲存的HashTable。訪問資料頁時,會從該對映關係中獲取資料頁需要回放的所有LSN,依次回放對應的WAL日誌,最終生成需要使用的資料頁。

可見,資料頁上的修改越多,其對應的LSN也越多,回放所需耗時也越長。為了儘量減少資料頁需要回放的LSN數量,PolarDB中引入了一致性位點的概念。
一致性位點表示該位點之前的所有WAL日誌修改的資料頁均已經持久化到儲存。主備之間,主節點向備節點發送當前WAL日誌的寫入位點和一致性位點,備節點向主節點發送當前回放的位點。由於一致性位點之前的WAL修改都已經寫入共享儲存,備節點無需再回放該位點之前的WAL日誌。因此,可以將LogIndex中所有小於一致性位點的LSN清理掉,既加速回放效率,同時還能減少LogIndex佔用的空間。

FlushList

為維護一致性位點,PolarDB為每個Buffer引入了一個記憶體狀態,即第一次修改該Buffer對應的LSN,稱之為oldest LSN,所有Buffer中最小的oldest LSN即為一致性位點。
一種獲取一致性位點的方法是遍歷Buffer Pool中所有Buffer,找到最小值,但遍歷代價較大,CPU開銷和耗時都不能接受。為高效獲取一致性位點,PolarDB引入FlushList機制,將Buffer Pool中所有髒頁按照oldest LSN從小到大排序。藉助FlushList,獲取一致性位點的時間複雜度可以達到 O(1)。

第一次修改Buffer並將其標記為髒時,將該Buffer插入到FlushList中,並設定其oldest LSN。Buffer被寫入儲存時,將該記憶體中的標記清除。
為高效推進一致性位點,PolarDB的後臺刷髒程序(bgwriter)採用“先被修改的Buffer先落盤”的刷髒策略,即bgwriter會從前往後遍歷FlushList,逐個刷髒,一旦有髒頁寫入儲存,一致性位點就可以向前推進。以上圖為例,如果oldest LSN為10的Buffer落盤,一致性位點就可以推進到30。

並行刷髒

為進一步提升一致性位點的推進效率,PolarDB實現了並行刷髒。每個後臺刷髒程序會從FlushList中獲取一批資料頁進行刷髒。

熱點頁

引入刷髒控制之後,僅滿足刷髒條件的Buffer才能寫入儲存,假如某個Buffer修改非常頻繁,可能導致Buffer Latest LSN總是大於Oldest Apply LSN,該Buffer始終無法滿足刷髒條件,此類Buffer我們稱之為熱點頁。熱點頁會導致一致性位點無法推進,為解決熱點頁的刷髒問題,PolarDB引入了Copy Buffer機制。
Copy Buffer機制會將特定的、不滿足刷髒條件的Buffer從Buffer Pool中拷貝至新增的Copy Buffer Pool中,Copy Buffer Pool中的Buffer不會再被修改,其對應的Latest LSN也不會更新,隨著Oldest Apply LSN的推進,Copy Buffer會逐步滿足刷髒條件,從而可以將Copy Buffer落盤。
引入Copy Buffer機制後,刷髒的流程如下:

  1. 如果Buffer不滿足刷髒條件,判斷其最近修改次數以及距離當前日誌位點的距離,超過一定閾值,則將當前資料頁拷貝一份至Copy Buffer Pool中。
  2. 下次再刷該Buffer時,判斷其是否滿足刷髒條件,如果滿足,則將該Buffer寫入儲存並釋放其對應的Copy Buffer。
  3. 如果Buffer不滿足刷髒條件,則判斷其是否存在Copy Buffer,若存在且Copy Buffer滿足刷髒條件,則將Copy Buffer落盤。
  4. Buffer被拷貝到Copy Buffer Pool之後,如果有對該Buffer的修改,則會重新生成該Buffer的Oldest LSN,並將其追加到FlushList末尾。

如下圖中,[oldest LSN, latest LSN] 為 [30, 500] 的Buffer被認為是熱點頁,將當前Buffer拷貝至Copy Buffer Pool中,隨後該資料頁再次被修改,假設修改對應的LSN為600,則設定其Oldest LSN為600,並將其從FlushList中刪除,然後追加至FlushList末尾。此時,Copy Buffer中資料頁不會再修改,其Latest LSN始終為500,若滿足刷髒條件,則可以將Copy Buffer寫入儲存。
![image.png](pic/44_Copy Buffer.png) 需要注意的是,引入Copy Buffer之後,一致性位點的計算方法有所改變。FlushList中的Oldest LSN不再是最小的Oldest LSN,Copy Buffer Pool中可能存在更小的oldest LSN,因此,除考慮FlushList中的Oldest LSN之外,還需要遍歷Copy Buffer Pool,找到Copy Buffer Pool中最小的Oldest LSN,取兩者的最小值即為一致性位點。

Lazy Checkpoint

PolarDB引入的一致性位點概念,與checkpoint的概念類似。PolarDB中checkpoint位點表示該位點之前的所有資料都已經落盤,資料庫Crash Recovery時可以從checkpoint位點開始恢復,提升恢復效率。普通的checkpoint會將所有Buffer Pool中的髒頁以及其他記憶體資料落盤,這個過程可能耗時較長且在此期間IO吞吐較大,可能會對正常的業務請求產生影響。
藉助一致性位點,PolarDB中引入了一種特殊的checkpoint,Lazy Checkpoint。之所以稱之為Lazy(懶惰的),是與普通的checkpoint相比,lazy checkpoint不會把Buffer Pool中所有的髒頁落盤,而是直接使用當前的一致性位點作為checkpoint位點,極大地提升了checkpoint的執行效率。
Lazy Checkpoint的整體思路是將普通checkpoint一次性刷大量髒頁落盤的邏輯轉換為後臺刷髒程序持續不斷落盤並維護一致性位點的邏輯。需要注意的是,Lazy Checkpoint與PolarDB中Full Page Write的功能有衝突,開啟Full Page Write之後會自動關閉該功能。

企業級分散式開源資料庫 PolarDB for PostgreSQL-阿里雲開發者社群