全部產品
Search
文件中心

PolarDB:緩衝區管理

更新時間:Jul 06, 2024

本文介紹了如何進行緩衝區管理。

背景資訊

一寫多讀架構下,唯讀節點可能從共用儲存中讀到兩類資料頁:
  • 未來頁
    資料頁中包含唯讀節點尚未回放到的資料。例如,唯讀節點回放LSN為200的WAL日誌,但資料頁中已經包含LSN為300的WAL日誌對應的改動。此類資料頁被稱為未來頁。
  • 過去頁
    資料頁中未包含所有回放位點之前的改動。例如,唯讀節點將資料頁回放到LSN為200的WAL日誌,但該資料頁在從Buffer Pool淘汰之後,再次從共用儲存中讀取的資料頁中沒有包含LSN為200的WAL日誌的改動,此類資料頁被稱為過去頁。
    對於唯讀節點而言,只需要訪問與其回放位點相對應的資料頁。但如果讀取到如上所述的未來頁和過去頁時,可以按照以下步驟處理:
    • 對於過去頁,唯讀節點需要回放資料頁上截止回放位點之前缺失的WAL日誌,對過去頁的回放由每個唯讀節點根據自己的回放位點完成,屬於唯讀節點回放功能,此處暫不贅述。
    • 對於未來頁,唯讀節點無法將未來的資料頁轉換為所需的資料頁,因此需要在主節點將資料寫入共用儲存時考慮所有隻讀節點的回放情況,從而避免唯讀節點讀取到未來頁,這也是Buffer管理需要解決的主要問題。
    除此之外,Buffer管理還需要維護一致性位點,對於某個資料頁,唯讀節點僅需回放一致性位點和當前回放位點之間的WAL日誌即可,從而加速回放效率。

術語

名詞說明
Buffer Pool緩衝池,一種用來儲存最常訪問的資料的記憶體結構,通常以頁為單位來快取資料。PolarDB中每個節點都有自己的Buffer Pool。
LSNLog 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寫入儲存。熱點頁
說明 引入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中的髒頁以及其他記憶體資料落盤,這個過程可能耗時較長且在此期間I/O吞吐較大,可能會對正常的業務請求產生影響。

藉助一致性位點,PolarDB引入了一種特殊的checkpoint:Lazy Checkpoint。之所以稱之為Lazy,是與普通的checkpoint相比,Lazy Checkpoint不會把Buffer Pool中所有的髒頁落盤,而是直接使用當前的一致性位點作為checkpoint位點,極大地提升了checkpoint的執行效率。

Lazy Checkpoint的整體思路是將普通checkpoint一次性刷大量髒頁落盤的邏輯轉換為後台刷髒進程持續不斷落盤並維護一致性位點的邏輯。
說明 Lazy Checkpoint與PolarDB中Full Page Write的功能有衝突,開啟Full Page Write之後會自動關閉該功能。