Buffer Pool(緩衝池)
Aim:保存那些需要重用的數據庫文件中的page。
被用於讀寫想要的pages的access methods,如sequential scan,indexed retrieval 和 hashing。它使用文件管理函數去獲取與table對應的data files。
Buffer pool的操作:
1 . request_page(pid) 2.release_page(pid)
一定程度上request_page()替代getBlock() or get_page(), release_page()替代putBlock() or put_page()。 release_page是指不再常用這個page了,如果buffer pool 滿了則被換掉。Buffer pool比較穩定,一般讀次數大於寫。
Buffer pool的數據結構:
- frames… NBUFS Pages(8K) buffers的數組
- directory… NBUFS FrameData items的數組,給出每個frame的信息。
Directory裏Info比較小 ,包括frame裏是哪些pages,它們被修改了嗎(dirty bit),哪些transactions在用它們(pin count),最近訪問的時間戳等等。
Frames裏包含page裏的data,或者是空的。
PageID = BufferTag = (mode,forkNum,blockNum)
PS:fork是指額外的data file。
Scan without Buffer Pool(不用Buffer Pool的Scan):
Buffer buf;
//how many pages of data contained in this relation
int N = numberOfBlocks(Rel);
//Scan each of those pages of data
for (i=0; i<N; i++){
//get constructed pageID
pageID = makePageID(db,Rel,i);
//get block of data in page and feed into buffer
getBlock(pageID, buf);
for (j=0; j<nTuples(buf); j++){
process(buf, j)
}
}
需要讀 N pages。如果再讀一次,還是需要讀N pages。
Scan with Buffer pool(使用 Buffer Pool的Scan):
Buffer buf;
int N = numberOfBlocks(Rel);
for (i = 0; i<N; i++){
pageID = makePageID(db,Rel,i);
//Get Index into buffer pool
bufID = request_page(pageID);
//Get the data of buffer in frame
buf = frame[bufID]
for (j=0; j< nTuples(buf); j++){
process(buf, j)
}
// When finishing the frame,release it
release_page(pageID);
}
第一次需要讀N Pages。再次讀時,0<=page<=N,因爲已經在buffer pool裏的無需讀。
Buffer pool 數據結構:
typedef char Page[PAGESIZE];
typedef ... PageID; //defined earlier
typedef struct_FrameData{
PageID pid; // which page is in frame
int pin_count; //how many processes using page
int dirty; //page modified since loaded
Time last_used; //when page was last accessed
}FrameData;
Page frame[NBUFS]; //actual buffers
FrameData directory[NBUFS];
Request_page()的實現:
int request_page(pageID pid)
{
bufID = findInPool(pid)
if (bufId == NOT_FOUND){
if (no free frames in Pool){
//kick out the old free frame
bufID = findFrameToReplace()
if (directory[bufID].dirty){
old = directory[bufID].page
put_page(old,frame[bufID])
}
}
//read data into frame and set relevant entry
bufID = index of freed frame
directory[bufID].page = pid
directory[bufID].pin_count = 0
directory[bufID].dirty = 0
get_page[pid, frame[bufID]]
}
directory[bufID].pin_count++
}
對比Virtual file descriptor pool 滿了會擴1倍,buffer pool在PostgreSQL啓動時創建,比較大,基本不會擴張。
**Release_page(pid):**對指定page減少pin count。
(如不需要替換,此操作對disk和buffer裏的內容無影響,buffer裏的頁面狀態也和disk無關)
make_page (pid): 修改page時,爲指定page 設置dirty bit。
(不是真正的寫入disk,只是指出page改變過了)
flush_page(pid): 用write()將一個page寫入disk。
(high-level的DBMS不用常用它)
Evicting a page:
儘量減少read() 和 write()。
找到frame滿足:
1. pin count = 0 (即沒有人用它)
2. dirty bit = 0 (不改變)
如果選中的page被改變,將frame flush進disk。
將directory entry標記爲“frame empty”
如果有多個frames可被釋放,則需要一個policy決定哪一個是best的。
Page Replacement Policies:
常用的policies:
- Least Recently Used(LRU)最近最少使用算法
- Most Recently Used (MRU)最近最常用算法
- First in First Out(FIFO)
- Random
但postgreSQL不用以上任何一種policy。
LRU/MRU 需要知道pages最後被訪問的時間。兩種方式:
6. 更新usage當你釋放這個page。(一般選擇這個去track usage time)。
7. 更新usage當你標記這個page。
n frame 的 Buffer pool 的Cost benefit由以下決定:
8. 可用的frame的數量(越多越好)
9. 替換策略(replacement strategy) vs 頁獲取模式(page access pattern)
eg.
Case a.連續掃描(sequential scan), 使用LRU或MRU,當n(buffer pool中的page數量)>=b(file(relation)中的page數量)。
第一次掃描的代價是b reads,但接下來無掃描代價。
Case b.連續掃描, 使用MRU, 當n<b。
第一次掃描的代價是b reads,接下來代價是 b-n reads(在池中的只需要request)。
Case c.連續掃描,使用LRU,當n<b。
所有的scans的代價都是b reads,也就是 sequential flooding(當n個page對應完所有的frame後,n+1 page會代替第一個page的位置,然後以此類推)。
Buffer management的影響
找到既是customer也是emplyees的query:
select c.name
from Customer c,Employee e
where c.ssn = e.ssn;
也可以通過nested loops在DBMS中實現:
for each tuple t1 in Customer{
for each tuple t2 in Employee{
if (t1.ssn == t2.ssn){
append(t1.name) to result set
}
}
}
在實際系統中,對於page-level的操作:
Rel rC = openRelation("Customer");
Rel rE = openRelation("Employee");
for (int i =0l i< nPages(rC);i++){
PageID pid1 = makePageID(db,rC,i);
Page p1 = request_page(pid1);
for (int j =0; j< nPages(rE); j++){
PageID pid2 = makePageID(db,rE,j);
Page p2 = request_page(pid2);
//compare all pairs of tuples from p1,p2, fast since loading in memory.
//construct solution set from matching pairs
release_page(pid2);
}
release_page(pid1);
}
Exercise 5:Buffer Cost Benefit
當沒有buffer pool時(最壞情況):
需要Customer表中讀每一個page到一個buffer,
對於每個Customer的page,還要讀Employee的每個page。
eg:
C0 then E0,E1,E2,E3
C1 then E0,E1,E2,E3
…
代價是:reads = 10(Outer)+10*4(inner)=50 reads
優化:
C0 then E0,E1,E2,E3
C1 then E3,E2,E1,E0
C2 then E0,E1,E2,E3
…
reads = 10(Emp) + 4(Cust) +9*3(Cust) = 41reads
當有20個buffer時(最好情況):
每次只讀一次Customer的每個的 page
每次只讀一次Emplyee的每個page
reads = 10+4 =14 reads
C0 E0 E1 E2 E3 C1 C2…
當有n>2個buffer且用MRU策略時:
eg:如果有3個buffer pool:
當出現C和E的pair時,process這個pair,然後釋放 pair中的E。當沒有E了就釋放C。當緩衝池(buffer pool)滿了,優先替換緩衝池最後一個位置(Most recent)。PS:對於已經在緩衝池中但被釋放的不需要再讀。
操作 Buffer pool Notes
初始 _ _ _
request C0 C0 _ _
request E0 C0 E0 _
process C0+E0
release E0 MRU: E0
request E1 C0 E0 E1
process C0+E1
release E1 MRU E1->E0
request E2 C0 E0 E2
process C0+E2
…
因爲以上太tedious(枯躁),可以寫C程序模擬buffer pool的usage,參數:argv[1] :給出“outer”table的pages的數量。
argv[2]: 給出“inner” table的pages的數量。
argv[3]: 給出buffer pool中槽的數量。
argv[4]: 給出替換策略 (LRU, MRU, FIFO-Q)。
PostgreSQL 的 Buffer Manager:
1.對於所有的後端都提供一個共享的內存緩衝池( a shared pool of memory buffers for all backends)。
2.通過buffer manager有所有的從disk獲取data的方式。
因爲同一時間很多進程都在獲取buffer pool的數據,所以Buffers 被放到於 一個大的shared memory的區域。
定義:src/include/storage/buf*.h
函數:src/backend/storage/buffer/*.c
註釋:backend/storage/buffer/README
PostgreSQL 的Buffer pool組成:
BufferDescriptors (即directory):shared fixed array of NBuffers X BufferDesc。Server啓動前的配置。
BufferBlocks (即frames): shared fixed array of NBuffers X buffers (each BLCKSZ bytes.) (cd postgresql-12.1/src–>grep -r ‘define BLCKSZ’ include–> 得到BLCKSZ的值是8192。)
Buffer = 以上數組們的索引:
indexes: global buffers 1…NBuffers; local buffers negatives.
PostgreSQL的shared buffer的索引是1到NBuffers-1,因爲索引0指向的是BufferDescriptors。
Buffer pool的大小在postgresql.conf中設置:
shared_buffer = 16KB //min 128KB, 16*8KB buffers
BufferTag:pageID, 指出buffer pool 的某個frame中是哪個page
typedef struct buftag{
RelFileNode rnode; /* physical relation identifier*/
ForkNumber forkNum;
BlockNumber blockNum; /* relatice to start of relation */
}BufferTag;
typedef struct BufferDesc{ //(simplified)
BufferTag tag; //ID of page contained in buffer
int buf_id; //buffer's index number (from 0)
Bits32 state; //dirty, refcount, usage
int freeNext; //link is freelist chain
... //others related to concurrency
}BufferDesc;
Buffer Pool Functions
Buffer ReadBuffer(Relation r, BlockNumber n)–>相當於request
確保relation r的文件中的blocknumber是n的page被加載
累加buffer的pin count 和 usage count
返回buffer pool中被加載page的索引(Buffer value)
**BufferDesc BufferAlloc( Relation r, ForkNumber f, BlockNumber n, bool found)
用於ReadBuffer去找到一個buffer(r,f,n)
如果buffers都不available,則選擇哪個buffer將被替代。
void ReleaseBuffer(Buffer buf)
遞減buffer的pin count
如果pin count爲0,確保buffer中所有activity返回前完成(沒有用戶使用page)。
void MarkBufferDirty(Buffer buf)
標記buffer被修改過
要求buffer被pinned且locked
真正的write操作在之後完成(比如當buffer的替換)
…
Reference
comp9315 week3 lectures