天天看點

WiredTiger系列3:Checkpoint/Block Mgr

在evict一章中我們簡單描述了記憶體page在寫盤時如何是選取磁盤寫入位置的:

選擇目前btree檔案的空閑塊中大小最合适的塊,裁選出所需大小的塊(如果沒有這樣的空閑塊,那麼向檔案末尾寫入)。

這一工作是通過block manager(

__wt_block

)完成的,每個block manager代表了一個實際的table磁盤檔案,其中包括了磁盤塊配置設定、資料壓縮等内容,整個子產品解讀起來稍顯複雜。

但是幸運的是,對于磁盤塊配置設定而言,我們并不需要過多關注block manager本身的具體結構與建立流程等,隻需要掌握checkpoint與block manager的關系以及checkpoint的相關内容即可。

相信閱讀完本章,WiredTiger寫盤時的磁盤塊選取問題将得到解答。

checkpoint結構

WiredTiger支援為每個表建立多個checkpoint。每個checkpoint都是隻讀的,作為所有對象的靜态視圖。一個checkpoint本質上是追蹤了自上一個checkpoint以來完成的所有修改的log。

WiredTiger的checkpoint可以由任意字元串命名。未命名的checkpoint将使用“WiredTigerCheckpoint”的預設名稱。

建立一個新的checkpoint會删除以前所有相同名稱的checkpoints,以及所有名為“WiredTigerCheckpoint”的checkpoint,也就是說,任何名稱的checkpoint都不會超過一個,建立一個新的checkpoint意味着無需維護以前的記憶體checkpoint。

如果目前要删除的checkpoint在cursor中處于打開狀态,則建立新checkpoint的嘗試将失敗;如果checkpoints沒有在cursor中打開則可以被删除。checkpoints是有序的,并維護了序号order。

盡管checkpoint是用于加快重新開機恢複,是以持久化後才能真正稱作有效的checkpoint,但為了便于了解,我們将WiredTiger的checkpoint分為兩類,一類是已經持久化的盤上的checkpoints,另一類是目前處在記憶體,可能發生更改的live checkpoint。

具體來說,一個已持久化的checkpoint包括以下幾個部分:

root page

:root offset、root size以及root checksum,辨別了該checkpoint所對應時段的b+tree的root page在磁盤上的位置

allocated lists

:每個entry表示在該checkpoint時間内,被配置設定用于寫盤的extent

available lists

:每個entry表示在該checkpoint時間内,能夠用于寫盤的extent(空閑塊)

discarded lists

:每個entry表示在該checkpoint時間内,與allocated lists中任何extent沒有重疊部分的需要被回收的extent

file size

:table檔案的大小

file write generation

WiredTiger系列3:Checkpoint/Block Mgr

如上圖所示,checkpoint在磁盤上分兩部分存儲,一部分我們稱為checkpoint中繼資料,存儲在中繼資料檔案WiredTiger.wt中(圖中隻展示部分内容),用于指向root page,allocated lists,available lists以及discarded lists在資料檔案中的位置;另一部分存儲在table的資料檔案中,包括root page的extent,allocated lists、available lists以及discarded lists的實際内容extent。

由于每個lists裡維護的是多個extent的資訊(可以簡單了解為<offset+size>的形式),是以每個lists在資料檔案上的chunk data是該lists内所有<offset+size>序列化後的資料,我們将這種extent稱作extent lists extent。

由此不難看出,table資料檔案除了包括前文所述leaf extent與internal extent外,還會包括一些特殊的extent,比如上圖中的extent lists extent。

接下來我們通過分析checkpoint的記憶體結構進一步加深了解。

WiredTiger系列3:Checkpoint/Block Mgr

如果任何一個checkpoint需要從盤上讀到記憶體,那麼将首先讀取中繼資料檔案,通過一個

__wt_ckpt

結構維護checkpoint中繼資料;然後根據中繼資料内容,讀取資料檔案,獲得allocated/available/discarded lists的内容,并以skiplist形式維護在

__wt_block_ckpt

結構内。

實際上,對于處于記憶體的live checkpoint,wiredtiger隻需要使用

__wt_block_ckpt

結構體進行維護。在block manager(

__wt_block

)中維護了一個名為live的

__wt_block_ckpt

結構體,用于表示live checkpoint。下一次checkpoint操作發生時,目前的live checkpoint将寫盤持久化,并更新得到新的live checkpoint,而在此之前,B+Tree的page寫盤将完全依賴于目前live checkpoint。

我們假設圖中所示__wt_block_ckpt結構表示live checkpoint,其中包含了alloction/discard/available lists,每個list在記憶體中以skiplist組織。

每個list中的元素是不連續的磁盤塊空間,以<offset+size>作為辨別。alloction lists與discard lists分别以每個磁盤塊空間的offset正序作為排列順序,available lists略微特殊,分别以每個磁盤塊空間的offset正序以及size正序作為排列順序,以兩個skiplist組織元素,需要注意的是,以size正序排列的skiplist中的元素并不是一份拷貝,而是以指針的形式儲存的。

對于任意一個skiplist,每當有元素插入時,如果跳表中已有元素能與新元素組成連續空間,将進行merge操作,多個元素合并成一個代表更大連續空間的元素;相反,取出元素的操作則可能發生元素的split。

實際上,介紹到這裡,block manager給寫盤操作配置設定磁盤塊的方法已經呼之欲出了。沒錯,就是利用了available lists。

對于每次記憶體page的寫盤操作而言,已知需要寫盤的實際空間大小,block manager将從available lists中取出所需大小的元素,即磁盤塊空間,如果沒有這樣的元素(可能是沒有足夠大小的磁盤塊空間或者available list為空),那麼block manager将選擇向檔案末尾寫入(block manager維護了檔案大小等資訊,是以直接獲得檔案大小并更新檔案大小即可)。

block manager從available lists中擷取元素有兩種政策供選擇,一是first-fit,在該政策下,擷取元素通過以offset正序排列的skiplist進行順序周遊,找到第一個大小相等或大于所需空間大小的元素(将進行split);二是best-fit,在該政策下,擷取元素通過以size正序排列的skiplist進行查找,找到具有最合适空間大小的元素。

WiredTiger預設采用best-fit的政策為每個記憶體page寫盤尋找磁盤空間,root page除外,其采用的是first-fit的政策。

每當block manager擷取到可用的一個磁盤塊空間并進行寫盤後,不論是以檔案末尾的方式還是available list内擷取的方式,都将建構或得到一個磁盤塊空間元素,該元素将被加入到live checkpoint的allocted lists中,表示在目前live checkpoint未寫盤前被配置設定已使用的磁盤塊空間。

類似的,當某個已經持久化過的記憶體page需要刷盤時,原extent的磁盤空間将被block manager回收,block manager将根據該磁盤塊空間的offset與size建構出一個磁盤塊空間元素,并在live checkpoint的allocted list中進行查找,如果找到某元素與該元素有重疊或相同,則直接split或取出allocted list中的對應元素,重新插入available list;否則将該元素插入discard list。

available/allocated/discard list三者之間的關系将在下一小節checkpoint的寫盤過程中進一步描述。

checkpoint流程

如果config設定了checkpoint的參數,WiredTiger将在open時建立一個背景checkpoint server線程(

__ckpt_server

),通過逾時或事件觸發checkpoint操作。

MongoDB有一個背景線程定期觸發checkpoint(預設周期為60s,可以修改syncPeriodSecs配置項來更改)

ps:MongoDB的checkpoint似乎不同于WiredTiger本身的checkpoint,WiredTiger自身可以配置根據生成的WAL的大小或周期觸發checkpoint,但MongoDB沒有采用

整個checkpoint流程可以分為兩個部分:一部分是對B+Tree所有髒節點(其中部分涉及到不滿足某些事務條件的page可能不寫盤,這裡省去了相關内容)的寫盤,我們稱作checkpoint tree;另一部分是checkpoint本身資料的寫盤,我們稱作block checkpoint。

下圖描述了一個普通場景下,在進入checkpoint tree流程前的checkpoint準備階段。

WiredTiger系列3:Checkpoint/Block Mgr

準備階段大緻包括三個步驟:

  1. 首先從WiredTiger.wt檔案中将同一個btree的checkpoint中繼資料依次讀到記憶體并建構

    __wt_ckpt

    結構,存放在數組結構内;
  2. 然後為本次需要寫盤的live checkpoint建構

    __wt_ckpt

    結構,設定其狀态為

    WT_CKPT_ADD

    ,同樣存放在數組内;
  3. 最後将數組中所有與本次寫盤的checkpoint同名的

    __wt_ckpt

    狀态設定為

    WT_CKPT_DELETE

通常情況下checkpoint操作不指定名稱,采用“WiredTigerCheckpoint”的預設名稱,是以一般每一次checkpoint都将标記上一次已持久化的checkpoint為WT_CKPT_DELETE,進而将其删除。

checkpoint tree作為淘汰髒節點的流程,與eviction流程相差無幾:

1.首先從最左邊的leaf page開始,後序周遊整棵btree,選出樹中每一個dirty page

2.調用與evict page相同的流程将每一個選取的page寫盤,直到root page

3.root page采用first-fit政策配置設定磁盤空間,并在寫盤後轉入block checkpoint流程

block checkpoint流程較長,是以在下圖中沒有将步驟标出,該圖僅用于輔助了解。

WiredTiger系列3:Checkpoint/Block Mgr

block checkpoint流程包括兩個部分:第一部分(

__ckpt_process

)将新的checkpoint寫入disk,其中包括所有修改過的checkpoints以及live checkpoint;第二部分(

__wt_block_checkpoint_resolve

)更新live checkpoint的available list。

也就是說,需要先将checkpoint資訊寫盤後才能使用新的available list。這裡可能不是很好了解,我們将在接下來的流程描述中給出解釋:

  1. 從頭周遊從盤上讀取的checkpoint中繼資料,對于每一個已經設定為

    WT_CKPT_DELETE

    __wt_ckpt

    ,為其建構對應的

    __wt_block_ckpt

    結構,并讀取checkpoint的實際資料填充alloction/discarded skiplist;
  2. 對于

    WT_CKPT_DELETE

    __wt_ckpt

    的下一個

    __wt_ckpt

    ,同樣執行步驟1,直到周遊到

    WT_CKPT_ADD

    __wt_ckpt

    ,将live checkpoint與其關聯起來;
  3. 再次從頭周遊,依次對設定為

    WT_CKPT_DELETE

    的ckpt a以及下一個ckpt b做如下操作:
    1. 首先将a的root page extent加入a的discarded list中,表示root page磁盤空間可回收;
    2. 然後将a的allocated/available/discarded list page extent加入live checkpoint的ckpt_avail list中,表示這三塊空間将在本次checkpoint結束後可配置設定;
    3. 然後将a的allocated/discarded skiplist合并到b的allocated/discard skiplist中
    4. 如果b是

      WT_CKPT_DELETE

      狀态,那麼重複以上操作;
    5. 否則将b的allocated skiplist與discarded skiplist重疊的空間摘取出來并插入到live checkpoint的ckpt_avail list中;
    6. 如果b是live checkpoint對應的

      __wt_ckpt

      ,那麼結束操作;
    7. 否則設定b的狀态為

      WT_CKPT_UPDATE

      ,表明該checkpoint修改過需要重新寫盤(即可能存在被修改的checkpoints)
  4. 将所有設定為

    WT_CKPT_UPDATE

    的checkpoint重新寫盤;
  5. 然後将狀态為

    WT_CKPT_ADD

    __wt_ckpt

    ,即live checkpoint對應的

    __wt_ckpt

    寫盤,包括allocated/available/discarded list于table檔案的寫盤,以及checkpoint中繼資料的寫盤。

需要注意兩點:

一是available list的chunk data除了包括live checkpoint的available skiplist内容外,還包括了步驟3中所更新得到的ckpt_avail skiplist内容;

二是allocated/available/discarded list寫盤時配置設定的磁盤塊不需要重新添加到allocated list中,因為可以在删除舊checkpoint的時候直接回收到ckpt_avail list中。

  1. 當live checkpoint成功寫盤後,live checkpoint的allocated/discarded skiplist将清空,ckpt_avail list将合并到available list,本次checkpoint結束(即必須寫盤後才能将本次回收的空間真正添加到available list供使用),并在下一個checkpoint之前将能夠使用本次checkpoint成功回收的可配置設定空間。

前文提到,通常情況下checkpoint操作不指定名稱,是以一般每一次checkpoint都将标記上一次已持久化的checkpoint為

WT_CKPT_DELETE

,進而将其删除。這一點展現在步驟3中,通常情況下磁盤上隻有一個ckpt,是以ckpt a将直接與live ckpt進行合并,即每次checkpoint将删除上一個已持久化的checkpoint。

并且容易注意到一點,盡管available list已寫盤,但在合并時并不需要讀取盤上的available list,這是因為live checkpoint是始終繼承上個live checkpoint的available list的。

每次checkpoint結束清空live checkpoint的allocated/discarded skiplist,表明了隻有在一個checkpoint周期内配置設定出去并回收的空間能夠重配置設定,否則隻能加入discarded list中作為待回收空間,等到checkpoint時嘗試真正回收。

參考文獻

wiredtiger checkpoint wiki