天天看點

圖解PostgreSQL-buffer管理(二)

一、資料結構

圖解PostgreSQL-buffer管理(二)

1、Buffer由數組BufferDescriptor[]數組進行管理。該數組由函數InitBufferPool建立,大小為NBuffers個成員即BufferDesc。該數組建立後由StrategyControl進行管理,firstFreeBuffer為連結清單頭,指向連結清單第一個成員;lastFreeBuffer指向連結清單尾;所有free list中成員由freeNext串起來,該值為數組下标。

2、BufferDescriptor數組是共享記憶體中申請,所有程序共享。可以看到兩個程序的BufferDescriptors位址相同:

程序1:
(gdb) p BufferDescriptors
$1 = (BufferDescPadded *) 0xa615fb80
(gdb) p *BufferDescriptors
$2 = {bufferdesc = {tag = {rnode = {spcNode = 1664, dbNode = 0, 
        relNode = 1262}, forkNum = MAIN_FORKNUM, blockNum = 0}, buf_id = 0, 
    state = {value = 2199126016}, wait_backend_pid = 0, freeNext = -2, 
    content_lock = {tranche = 53, state = {value = 536870912}, waiters = {
        head = 2147483647, tail = 2147483647}}}, pad = "\200"}
程序2:
(gdb) p BufferDescriptors
$1 = (BufferDescPadded *) 0xa615fb80
(gdb) p *BufferDescriptors
$2 = {bufferdesc = {tag = {rnode = {spcNode = 1664, dbNode = 0, 
        relNode = 1262}, forkNum = MAIN_FORKNUM, blockNum = 0}, buf_id = 0, 
    state = {value = 2199126016}, wait_backend_pid = 0, freeNext = -2, 
    content_lock = {tranche = 53, state = {value = 536870912}, waiters = {
        head = 2147483647, tail = 2147483647}}}, pad = "\200"}      

3、同時還會通過一個環形區進行管理這些數組成員。當進行大表掃描時使用。由strategy->buffers[]數組管理,該數組存儲的是BufferDescriptors[]數組的下标+1後的值,而每次取buf描述符時,從strategy->current值開始進行選擇。選出的不可用後,依次向後進行周遊,周遊到頭後從頭再來進行選擇,即形成一個環。是否可用的标準後文詳述。

4、下面說下BufferDesc成員變量:

  • BufferTag tag為一個描述符對應磁盤實體頁的映射。即space ID+database ID+檔案ID -- forkNum(表檔案還是fsm檔案或者vm檔案)-- 頁号
  • buf_id為buffer數組BufferBlocks[]的下标
  • state為狀态标記,包括該buffer的refcount和usagecount以及是否合法valid等待
  • wait_backend_pid:若程序A需要删除的元組所在緩沖塊有其他程序通路,即refcount>0時,程序A不能實體上删除元組。系統将該程序的ID記錄在wait_backend_id上,然後對緩沖塊加pin,并阻塞自己。當refcount為1時最後一個使用該緩沖塊的程序釋放緩沖區時,會向wait_backend_id程序發送消息。
  • FreeNext為連結清單的下一個節點的下标
  • content_lock為buffer鎖,當程序通路緩沖塊時加鎖,讀加LW_SHARE鎖,寫加LW_EXCLUSIVE鎖

二、共享buffer配置設定機制

圖解PostgreSQL-buffer管理(二)

1、前期準備:

1)該buffer配置設定有4種情況:從hash表SharedBufHash中查找;從環形緩沖區查找;從free list查找以及驅逐政策進行配置設定。

2)hash表SharedBufHash同樣是共享記憶體全局的,所有程序公有。下面分别是兩個會話連接配接的server端程序列印出的hash表。

(gdb) p SharedBufHash
$1 = (HTAB *) 0x87f5b04
 
(gdb) p SharedBufHash
$1 = (HTAB *) 0x87f5b04      

該hash表同樣在InitBufferPool中進行建立:

StrategyInitialize->InitBufTable(NBuffers + NUM_BUFFER_PARTITIONS)->
SharedBufHash = ShmemInitHash      

4)該hash表中條目為:[BufferTag,id]即key值為實體磁盤頁的标志,id為對應buffer的ID

5)首先需要建立一個newTag,對應實體檔案的一個頁

6)通過newTag到函數BufTableHashCode中計算hash表的key值newHash

7)共有128個buffer partition鎖,通過hash的key值以輪詢的方式取鎖

8)此時對key值對應的buffer partition加LW_SHARED鎖

2、此時進入第一種擷取buffer描述符的方法:所有程序共享的SharedBufHash

1)根據newTag從hash表SharedBufHash中查找對應的buffer

2)buf_id>0則表示資料頁在hash表中找到,即對應資料頁以加載到記憶體

3)根據buf_id擷取buffer的描述符BufferDescriptors[buf_id)].bufferdesc

4)通過函數PinBuffer将對應buffer pin住,然後就可以将buffer的partition鎖釋放,即,将buf的state的refcount+1,usagecount根據情況+1,具體流程下文分析。

5)pin失敗,通過StartBufferIO判斷,傳回TRUE,緩沖區無效,此時foundPtr為false,并傳回對應buf;傳回false,表示别人正在使用,直接傳回對應buf。foundPtr表示是否在緩沖區命中

3、若hash表中不存在,則需要從磁盤讀取。首先釋放buf的partition鎖,進入循環。

1)StrategyGetBuffer取出一個buf描述符,具體原理見下文。

2)PinBuffer_Locked将buf的refcount+1

3)此時該buf為髒塊BM_DIRTY,則對buf->content_lock加LW_SHARED鎖,加鎖失敗釋放pin,傳回1)。加鎖成功根據strategy是否為空處理。

4)使用環形緩沖區,即strategy不為空:BM_LOCKED鎖内擷取buf髒頁的lsn,根據lsn判斷其日志是否已經刷寫到磁盤,若未則将該buf從環形緩沖區删除;釋放buf->content_lock鎖及pin,傳回1)重新循環進行選擇。

5)使用環形緩沖區且日志已刷或者未使用環形緩沖區,則調用FlushBuffer将髒資料刷寫磁盤,最後釋放buf->content_lock鎖。

6)接着進入4,當該頁不為髒時也進入4

4、替換為自己的tag

1)先擷取buf的oldTag,是誰用過。其oldPartitionLock和newTag的newPartitionLock按順序加鎖,若同一個則隻加一個鎖。LW_EXCUSIVE

2)将newTag對應的條目插入到hash表SharedBufHash

3)buf_id>=0,表示該條目已在hash表,那麼unpin、oldPartitionLock鎖釋放後,擷取老buf,pin後釋放newPartitionLock

4)pin失敗,通過StartBufferIO判斷,傳回TRUE,緩沖區無效,此時foundPtr為false,并傳回對應buf;傳回false,表示别人正在使用,直接傳回對應buf。foundPtr表示是否在緩沖區命中

5)buf_id<0,即未在hash表SharedBufHash:buf_state的refcount==1且不為BM_DIRTY,表示無人使用該buf,退出循環,将buf->tag=newTag,最後釋放相關鎖

6)否則,需要釋放相關鎖,并将newTag對應的條目從hash表删除後,重新回到3進行選擇。

三、幾個子函數

1、PinBuffer

圖解PostgreSQL-buffer管理(二)

1、若buffer的state已為BM_LOCKED即已加鎖,則需等待,該鎖是pin鎖

2、GetPrivateRefCountEntry擷取ref,若ref不為NULL,則表示别人在使用,然後TRUE。是這樣了解嗎?需要了解這個函數

3、原子操作讀取state值old_buf_state,并将之儲存為buf_state

4、buf_state的refcount+1

5、預設政策下,即從free list中選擇空閑描述符,buf_state的usagecount+1;環形緩沖區政策下,buf_state的usagecount保持為1

6、通過CAS操作将buf->state的值替換為buf_state的值

7、函數傳回TRUE表示該buffer的資料有效,即合法的資料已經加載到記憶體;傳回false表示資料無效,即資料未加載到記憶體

2、StartBufferIO:開啟IO,将buf狀态置為BM_IO_IN_PROGRESS

圖解PostgreSQL-buffer管理(二)

1、每個buffer都有一個IO鎖(BufferIOLWLockArray[(bdesc)->buf_id]).lock

2、擷取buf_state狀态,需要先将其置為BM_LOCKED

3、該buf此時已為BM_IO_IN_PROGRESS,即正在讀寫,需要将上面兩個鎖釋放後WaitIO等待狀态變化

4、forInput為TRUE:要向裡面寫,需要其為!BM_VALID,若是BM_VALID表示有人已經向裡寫了合法資料;FALSE:需要向外讀,若為!BM_DIRTY表示已有人刷寫了。釋放兩個鎖傳回

5、将buf_state置為BM_IO_IN_PROGRESS。

6、傳回TRUE,表示buf中資料無效,可以使用。False,表示别人正在使用

3、StrategyGetBuffer

圖解PostgreSQL-buffer管理(二)

1、如果使用strategy,則從環形緩沖區取一個空閑的描述符:bufnum=strategy->buffers[strategy->current];buf = GetBufferDescriptor(bufnum - 1);,若沒有可用的則GetBufferFromRing傳回NULL,否則直接傳回該buf。

2、環形緩沖區取buffer失敗,則去free list取

3、StrategyControl->firstFreeBuffer>0,此時list不為空,

4、則先申請spin鎖StrategyControl->buffer_strategy_lock,再次判斷連結清單情況,若StrategyControl->firstFreeBuffer<0連結清單空了,則釋放鎖後退出循環,進入第8步

5、擷取StrategyControl->firstFreeBuffer指向的buffer描述符,并将該節點從free list删除