本文介紹了如何進行緩衝區管理。
背景資訊
- 未來頁資料頁中包含唯讀節點尚未回放到的資料。例如,唯讀節點回放LSN為200的WAL日誌,但資料頁中已經包含LSN為300的WAL日誌對應的改動。此類資料頁被稱為未來頁。
- 過去頁資料頁中未包含所有回放位點之前的改動。例如,唯讀節點將資料頁回放到LSN為200的WAL日誌,但該資料頁在從Buffer Pool淘汰之後,再次從共用儲存中讀取的資料頁中沒有包含LSN為200的WAL日誌的改動,此類資料頁被稱為過去頁。對於唯讀節點而言,只需要訪問與其回放位點相對應的資料頁。但如果讀取到如上所述的未來頁和過去頁時,可以按照以下步驟處理:
- 對於過去頁,唯讀節點需要回放資料頁上截止回放位點之前缺失的WAL日誌,對過去頁的回放由每個唯讀節點根據自己的回放位點完成,屬於唯讀節點回放功能,此處暫不贅述。
- 對於未來頁,唯讀節點無法將未來的資料頁轉換為所需的資料頁,因此需要在主節點將資料寫入共用儲存時考慮所有隻讀節點的回放情況,從而避免唯讀節點讀取到未來頁,這也是Buffer管理需要解決的主要問題。
術語
| 名詞 | 說明 |
| Buffer Pool | 緩衝池,一種用來儲存最常訪問的資料的記憶體結構,通常以頁為單位來快取資料。PolarDB中每個節點都有自己的Buffer Pool。 |
| LSN | Log Sequence Number,記錄序號,是WAL日誌的唯一標識。LSN在全域是遞增的。 |
| Apply LSN | 回放位點,表示唯讀節點回放日誌的位置,一般用LSN來標記。 |
| Oldest Apply LSN最老回放位點 | 最老回放位點,表示所有隻讀節點中LSN最小的回放位點。 |
刷髒控制
- Buffer最近一次修改對應的LSN,即Buffer Latest LSN。
- 最老回放位點,所有隻讀節點中最小的回放位點,即Oldest Apply LSN。
if buffer latest lsn <= oldest apply lsn
flush buffer
else
do not flush buffer一致性位點
一致性位點表示該位點之前的所有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落盤。
- 如果Buffer不滿足刷髒條件,判斷其最近修改次數以及距離當前日誌位點的距離,超過一定閾值,則將當前資料頁拷貝至Copy Buffer Pool中。
- 下次再刷該Buffer時,判斷其是否滿足刷髒條件。如果滿足,則將該Buffer寫入儲存並釋放其對應的Copy Buffer。
- 如果Buffer不滿足刷髒條件,則判斷其是否存在Copy Buffer。若存在且Copy Buffer滿足刷髒條件,則將Copy Buffer落盤。
- Buffer被拷貝到Copy Buffer Pool之後,如果對該Buffer進行修改,則會重建該Buffer的Oldest LSN,並將其追加到FlushList末尾。

Lazy Checkpoint
PolarDB引入的一致性位點概念,與checkpoint的概念類似。PolarDB中checkpoint位點表示該位點之前的所有資料都已經落盤,資料庫Crash Recovery時可以從checkpoint位點開始恢複,提升恢複效率。普通的checkpoint會將所有Buffer Pool中的髒頁以及其他記憶體資料落盤,這個過程可能耗時較長且在此期間I/O吞吐較大,可能會對正常的業務請求產生影響。
藉助一致性位點,PolarDB引入了一種特殊的checkpoint:Lazy Checkpoint。之所以稱之為Lazy,是與普通的checkpoint相比,Lazy Checkpoint不會把Buffer Pool中所有的髒頁落盤,而是直接使用當前的一致性位點作為checkpoint位點,極大地提升了checkpoint的執行效率。