CMU15445 PROJECT #1 - BUFFER POOL
CMU15445 PROJECT #1 - BUFFER POOL
第一個程式設計專案是在儲存管理器中實現緩衝池. 緩衝池負責將物理頁面從主記憶體來回移動到磁碟. 它允許 DBMS 支援大於系統可用記憶體量的資料庫. 緩衝池的操作對系統的其他部分是透明的. 例如, 系統使用其唯一識別符號(page_id_t
)向緩衝池詢問頁面, 它不知道該頁面是否已處於記憶體狀態, 也不知道系統是否必須從磁碟中取回該頁面.
需要處理併發的情況, 需要用到latches也就是os中的lock.
本實驗分為兩個子任務:
TASK #1 - LRU REPLACEMENT POLICY
緩衝池中有一些頁正在被讀寫(pinned
), 而有一些雖然快取在緩衝池但沒有執行緒在使用它們, 這些頁(unpinned
)就存放在Replacer, 通過frame_id
索引. 當需要從磁碟取出頁到緩衝池, 但是緩衝池滿了, 就要從Replacer中移除一些頁.
使用unordered_map
和雙鏈表可以在\(O(1)\)內完成以下操作:
Victim
: 通過連結串列的尾節點找到要被換出的頁, 將該節點刪除並在unordered_map
中刪除.Pin
: 通過unordered_map
可以快速找到frame_id
對應的頁, 並將該節點刪除. 當找不到該節點時就不處理直接返回.Unpin
: 如果已經在Replacer中存在, 就不處理, 如果不存在就將其加入連結串列第一個, 並判斷是否溢位(capacity<size), 如果溢位了, 就將最後一個頁換出.
TASK #2 - BUFFER POOL MANAGER
幾個重要引數:
-
page_id
: 用於索引磁碟上的頁(page). 系統給BPM想要讀取的頁id(page_id
), BPM在page_table_
中可以將page_id
轉換為緩衝池中的frame, 從而減少讀盤. -
frame_id
: 用於索引從磁碟中獲取的頁, 儲存在緩衝池中的幀(frame). -
pages_
: 儲存page, 通過(pages_ + frame_id)
page
地址. -
free_list_
: 儲存所有free frame的索引(frame_id
). -
page_table_
: 儲存所有pinned frame和unpinned frame, 通過page_id
來查詢對應的frame_id
. -
page
內參數:data_
: 頁內資料.pin_count
: 多少個執行緒正在使用該頁, 如果pin_count_
為0, 說明該頁存放在Replacer中.is_dirty_
: 是否有執行緒寫過該頁.
以下這張圖可以很好解釋frame和page的區別, 以及frame_id
索引frame, page_id
索引page.
BPM從磁碟取出一些頁, 放入緩衝池中, 這些頁被稱為frame, 對於記憶體池中所有frame, BPM可以將其分為3類:
free
: 存放在free_list_
中, 內部的頁都是初始狀態, 頁內資料(data_
)為無效資料, 通過free_list_
查詢.pinned
: 存放著有執行緒訪問的頁, 通過page_table_
查詢.unpinned
: 存放在Replacer中的頁, 沒有執行緒訪問, 但保留有有效資料(data_
), 通過page_table_
查詢.
通過一個狀態機可以很好的觀察frame的三種狀態, 以及其中的轉換.
需要實現以下幾個介面:
FetchPageImpl(page_id_t page_id)
:- 尋找
page_table_
, 如果通過page_id
可以在page_table_
找到對應的frame_id
, 就通過pages_
找到對應的page
, 此時使用該page
的程序多了一個, 就將pin_count_++
, 並從Replacer中取出. - 如果在
page_table_
中找不到, 就在free_list_
中找, 通過free_list_
找到frame_id
並在連結串列中刪除, 得到相應的page
. 在page_table_
中對映該frame. 為該page
賦予page_id
並從磁碟讀取data_
, 將pin_count_++
後返回. - 如果在
free_list_
還是找不到, 就在Replacer中Victim一個頁. 當該頁dirty時, 寫入磁碟. 在page_table_
中擦去此頁並用新page_id
對映, 此時頁的pin_count_
為1, 同樣讀取磁碟後返回. - 如果上述都不成立就返回
nullptr
.
- 尋找
UnpinPageImpl(page_id_t page_id, bool is_dirty)
:- 如果在
page_table_
中找不到對應的頁就直接返回. - 當
is_dirty
為true
時, 將對應page
的is_dirty_
設定為true
. 此處是手動改, 也就是說和is_dirty_
的原先狀態無關. pin_count_
為0時將frame放入Replacer.
- 如果在
FlushPageImpl(page_id_t page_id)
:- 不管
pin_count_
, 但凡能在page_table_
找到就刷盤
- 不管
NewPageImpl(page_id_t *page_id)
:- 如果能從
free_page_
中找到, 就分配一個新page
, 通過AllocatePage
獲得page_id
, 更新一下page
狀態並對映到page_table_
. - 否則就從Replacer中找一個Victim頁, 剩餘操作和
FetchPageImpl
類似. - new完一個
page
, 該page
的pin_count_
為1(有一個執行緒正在使用).
- 如果能從
DeletePageImpl(page_id_t page_id)
:- 在
page_table_
中找到對應page
, 只有當pin_count_
為0時才能刪除, 並從page_table_
擦除, 在free_list_
末尾插入.
- 在
一些坑:
- Replacer需要單獨用鎖保護.
UnpinPageImpl
中, 只有當is_dirty
為true
時, 才修改對應page
的is_dirty_
標誌為true
(不用管is_dirty_
標誌的原先狀態).page
中的鎖暫時不需要.
todo: 跑完3s+, 距離第一個1s還有很大差距, 有空在優化下吧.