COMP9315 課堂筆記(二)

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的數據結構:

  1. frames… NBUFS Pages(8K) buffers的數組
  2. 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:

  1. Least Recently Used(LRU)最近最少使用算法
  2. Most Recently Used (MRU)最近最常用算法
  3. First in First Out(FIFO)
  4. 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

Buffer manager

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章