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

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