前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(38)写操作如何获取buffer与页面扩展

Postgresql源码(38)写操作如何获取buffer与页面扩展

作者头像
mingjie
发布2022-07-14 13:46:39
5050
发布2022-07-14 13:46:39
举报

写操作如何获取buffer与页面扩展

PG的insert和update操作都会用到RelationGetBufferForTuple函数,例如insert:

代码语言:javascript
复制
heap_insert
  // [1] sets the tuple header fields, assigns an OID, and toasts the tuple if necessary
  heap_prepare_insert
  
  // [2] Returns pinned and exclusive-locked buffer of a page in given relation with free space >= given len
  RelationGetBufferForTuple
  
  // [3] place tuple at specified page
  RelationPutHeapTuple
  
  // [4] xlog stuff
  
  // [5] mark it for invalidation from the caches
  CacheInvalidateHeapTuple

获取buffer

RelationGetBufferForTuple

单insert常规流程,不考虑bulk场景和heap_update调入的场景,双页场景在heap_update系列在分析。

场景一无扩展:insert用单buffer、空间够

代码语言:javascript
复制
// 从SMGR拿当前使用的页面
targetBlock = RelationGetTargetBlock(relation) : (relation)->rd_smgr->smgr_targblockReadBufferBI
// 循环指导拿到一个页面
while (targetBlock != InvalidBlockNumber) 
  // 写锁
  LockBuffer(BUFFER_LOCK_EXCLUSIVE)
  // 拿页面指针
  BufferGetPage
  // 拿空闲空间
  PageGetHeapFreeSpace
  // 空闲空间够用
  if (len + saveFreeSpace <= pageFreeSpace)
    RelationSetTargetBlock(relation, targetBlock); 
    return buffer;

场景二单页扩展:insert用单buffer、空间不够、无并发扩展可以拿到写锁

代码语言:javascript
复制
// 从SMGR拿当前使用的页面,肯定能拿到,空间够不够不关注
targetBlock = RelationGetTargetBlock(relation) : (relation)->rd_smgr->smgr_targblockReadBufferBI
while (targetBlock != InvalidBlockNumber) 
  // 不管空间够不够先锁
  LockBuffer(BUFFER_LOCK_EXCLUSIVE)
  // 拿页面指针
  BufferGetPage
  // 拿空闲空间
  PageGetHeapFreeSpace
  // 空闲空间 不够!
  if (80  + 0             >  16)
  if (len + saveFreeSpace <= pageFreeSpace)
    // RelationSetTargetBlock(relation, targetBlock); 
    // return buffer;
  // 先放锁、unpin
  LockBuffer(BUFFER_LOCK_UNLOCK)
  ReleaseBuffer(buffer)
  // 更新fsm之后再查询一次,没找到返回InvalidBlockNumber
  targetBlock = RecordAndGetPageWithFreeSpace(relation, targetBlock, pageFreeSpace, len + saveFreeSpace)

// 【【表写锁】】
!ConditionalLockRelationForExtension(relation, ExclusiveLock)
// 用P_NEW读buffer
ReadBufferBI(relation, P_NEW, bistate)
// 【写锁】
LockBuffer(BUFFER_LOCK_EXCLUSIVE)
// 【【放表写锁】】
UnlockRelationForExtension(relation, ExclusiveLock)

BufferGetPage
PageInit
RelationSetTargetBlock(relation, BufferGetBlockNumber(buffer))
return buffer

场景三批量扩展:insert用单buffer、空间不够、拿不到写锁批量扩展

代码语言:javascript
复制
// 从SMGR拿当前使用的页面,肯定能拿到,空间够不够不关注
targetBlock = RelationGetTargetBlock(relation) : (relation)->rd_smgr->smgr_targblockReadBufferBI
while (targetBlock != InvalidBlockNumber) 
  // 不管空间够不够先锁
  LockBuffer(BUFFER_LOCK_EXCLUSIVE)
  // 拿页面指针
  BufferGetPage
  // 拿空闲空间
  PageGetHeapFreeSpace
  // 空闲空间 不够!
  if (80  + 0             >  16)
  if (len + saveFreeSpace <= pageFreeSpace)
    // RelationSetTargetBlock(relation, targetBlock); 
    // return buffer;
  // 先放锁、unpin
  LockBuffer(BUFFER_LOCK_UNLOCK)
  ReleaseBuffer(buffer)
  // 更新fsm之后再查询一次,没找到返回InvalidBlockNumber
  targetBlock = RecordAndGetPageWithFreeSpace(relation, targetBlock, pageFreeSpace, len + saveFreeSpace)

// 【【表写锁】】try失败
if (!ConditionalLockRelationForExtension(relation, ExclusiveLock))
  // 等别人用完
  LockRelationForExtension
  // 如果别人扩展了,就不用再扩展了
  targetBlock = GetPageWithFreeSpace
  if (targetBlock != InvalidBlockNumber)
    goto loop
  
  // *** 批量扩展
  RelationAddExtraBlocks
    
// 用P_NEW读buffer
ReadBufferBI(relation, P_NEW, bistate)
// 【写锁】
LockBuffer(BUFFER_LOCK_EXCLUSIVE)
// 【【放表写锁】】
UnlockRelationForExtension(relation, ExclusiveLock)

BufferGetPage
PageInit
RelationSetTargetBlock(relation, BufferGetBlockNumber(buffer))
return buffer

批量扩展流程RelationAddExtraBlocks

代码语言:javascript
复制
static void
RelationAddExtraBlocks(Relation relation, BulkInsertState bistate)
{
	// 几个人在等锁?
	lockWaiters = RelationExtensionLockWaiterCount(relation);
	if (lockWaiters <= 0)
		return;

	// 扩展至少512个页面
	extraBlocks = Min(512, lockWaiters * 20);

	while (extraBlocks-- >= 0)
	{
		// 循环一次扩展一个
		buffer = ReadBufferBI(relation, P_NEW, bistate);

		// 初始化
		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
		page = BufferGetPage(buffer);
		PageInit(page, BufferGetPageSize(buffer), 0);
		MarkBufferDirty(buffer);
		blockNum = BufferGetBlockNumber(buffer);
		freespace = PageGetHeapFreeSpace(page);
		UnlockReleaseBuffer(buffer);

		// 记录第一个页面
		if (firstBlock == InvalidBlockNumber)
			firstBlock = blockNum;

		// 更新FSM
		RecordPageWithFreeSpace(relation, blockNum, freespace);
	}

	// 更新FSM
	UpdateFreeSpaceMap(relation, firstBlock, blockNum, freespace);
}

单页扩展

第一步:ReadBuffer_common(...,P_NEW,...)

整体流程总结:

1、使用smgrnblocks当做新的页面ID。

2、用新页ID查hash查不到,clocksweep新分配一个。found=false

3、memset清空内存页面,smgr extend打开文件新页面的问题,写入清空的页面。

4、配置bmvalid标志返回

代码语言:javascript
复制
static Buffer
ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
				  BlockNumber blockNum, ReadBufferMode mode,
				  BufferAccessStrategy strategy, bool *hit)
{
  ...
	isExtend = (blockNum == P_NEW);
  ...
	if (isExtend)
    // 拿一个新的页面,比如0、1已经使用,当前有两个页面,smgrnblocks=2
		blockNum = smgrnblocks(smgr, forkNum);
  ...
  // 1、上面拿一个新的blockNum哈希表肯定搜不到tag,clocksweep拿一个页面
  // 2、初始化desc:buf_state |= BM_TAG_VALID | BM_PERMANENT | BUF_USAGECOUNT_ONE
  // 3、返回found=false
  bufHdr = BufferAlloc(smgr, relpersistence, forkNum, blockNum, strategy, &found);
    
	if (found)
	{
		...
	}
  // 拿到8k指针
	bufBlock = BufHdrGetBlock(bufHdr);

	if (isExtend)
	{
		// 清空8k
		MemSet((char *) bufBlock, 0, BLCKSZ);
    // 文件扩展
		smgrextend(smgr, forkNum, blockNum, (char *) bufBlock, false);
      mdextend
        // 找到第几个1G的文件
        v = _mdfd_getseg(reln, forknum, blocknum, skipFsync, EXTENSION_CREATE);
        // 如果是第二个页面,seekpos=16384
        seekpos = (off_t) BLCKSZ * (blocknum % ((BlockNumber) RELSEG_SIZE));
        // 文件读
        FileSeek(v->mdfd_vfd, seekpos, SEEK_SET) != seekpos;
        // 写入
        FileWrite(v->mdfd_vfd, buffer, BLCKSZ, WAIT_EVENT_DATA_FILE_EXTEND);
	}
	else
	{
	  ...
	}

	TerminateBufferIO(bufHdr, false, BM_VALID);
	
  ...

	return BufferDescriptorGetBuffer(bufHdr);
}

第二步:PageInit

第一步拿到空8k,这里先初始化页面头。

代码语言:javascript
复制
PageInit(Page page, Size pageSize, Size specialSize)
{
	PageHeader	p = (PageHeader) page;

	specialSize = MAXALIGN(specialSize);

	Assert(pageSize == BLCKSZ);
	Assert(pageSize > specialSize + SizeOfPageHeaderData);

	/* Make sure all fields of page are zero, as well as unused space */
	MemSet(p, 0, pageSize);

	p->pd_flags = 0;
	p->pd_lower = SizeOfPageHeaderData;
	p->pd_upper = pageSize - specialSize;
	p->pd_special = pageSize - specialSize;
	PageSetPageSizeAndVersion(page, pageSize, PG_PAGE_LAYOUT_VERSION);
	/* p->pd_prune_xid = InvalidTransactionId;		done by above MemSet */
}

FSM中找一个能用的页面

GetPageWithFreeSpace

代码语言:javascript
复制
	if (targetBlock == InvalidBlockNumber && use_fsm)
	{
		targetBlock = GetPageWithFreeSpace(relation, len + saveFreeSpace);
    
		if (targetBlock == InvalidBlockNumber)
		{
			BlockNumber nblocks = RelationGetNumberOfBlocks(relation);

			if (nblocks > 0)
				targetBlock = nblocks - 1;
		}
	}

GetPageWithFreeSpace函数是fsm提供的函数用来找到一个空闲页面,至少能容纳len的内容。

  • FSM是一个多页面的树形大顶堆结构,固定3层,0-1层不存数据,2层存块号。
  • FSM爬树从fsmpage->fp_next_slot开始爬,这是上一次查询找到合适页面时配置的。
  • FSM爬树规则:
    • 1、爬ROOT判断最大空闲页面能否满足
    • 2、爬fp_next_slot,当前不满足就开始move right,然后move up。
    • 3、父节点如果不满足,继续move right,可能到最右节点,那么需要继续向右到同级最左节点,然后move up。
    • 4、如果发现0、1辅助层满足了,需要继续向下遍历到叶子节点,从左孩子开始,优先用左孩子,向下遍历。
  • FSM使用1个字节记录空闲空间大小(例如0表示空闲0-31个字节,1表示空闲32-63个字节),计算方法为:(31+N)/32,每一个数据页面对应一个FSM字节。所以进入fsm的第一步就是做一个转换,把size/32后进入后续判断。

这是一个爬树的实例:

代码语言:javascript
复制
	   7
   7	   6
 5	 7	 6	 5
4 5 5 7 2 6 5 2
			T

Assume that the target node is the node indicated by the letter T,
and we're searching for a node with value of 6 or higher. The search
begins at T. At the first iteration, we move to the right, then to the
parent, arriving at the rightmost 5. At the second iteration, we move
to the right, wrapping around, then climb up, arriving at the 7 on the
third level.  7 satisfies our search, so we descend down to the bottom,
following the path of sevens.  This is in fact the first suitable page
to the right of (allowing for wraparound) our start point.
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-03-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 获取buffer
    • 场景一无扩展:insert用单buffer、空间够
      • 场景二单页扩展:insert用单buffer、空间不够、无并发扩展可以拿到写锁
        • 场景三批量扩展:insert用单buffer、空间不够、拿不到写锁批量扩展
          • 批量扩展流程RelationAddExtraBlocks
      • 单页扩展
        • 第一步:ReadBuffer_common(...,P_NEW,...)
          • 第二步:PageInit
          • FSM中找一个能用的页面
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档