1. 程式人生 > 其它 >CMU15445 PROJECT #1 - BUFFER POOL

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類:

  1. free: 存放在free_list_中, 內部的頁都是初始狀態, 頁內資料(data_)為無效資料, 通過free_list_查詢.
  2. pinned: 存放著有執行緒訪問的頁, 通過page_table_查詢.
  3. 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_dirtytrue時, 將對應pageis_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, 該pagepin_count_為1(有一個執行緒正在使用).
  • DeletePageImpl(page_id_t page_id):
    • page_table_中找到對應page, 只有當pin_count_為0時才能刪除, 並從page_table_擦除, 在free_list_末尾插入.

一些坑:

  • Replacer需要單獨用鎖保護.
  • UnpinPageImpl中, 只有當is_dirtytrue時, 才修改對應pageis_dirty_標誌為true(不用管is_dirty_標誌的原先狀態).
  • page中的鎖暫時不需要.

todo: 跑完3s+, 距離第一個1s還有很大差距, 有空在優化下吧.