full-page-writes
  IE5LYMWlmdvL 2023年11月02日 29 0

全页写的概述

在数据库发生一次checkpoint检查点后,需要往数据库的一个数据块里面插入数据,数据库在修改前需要把这个数据块从磁盘读到内存中数据缓冲区(shared buffer pool)里,然后再内存中进行数据块的修改插入。当我们执行insert语句时,对数据块进行插入数据A,内存中的数据块里面就会新增一条数据A。在commit提交后,PG数据块就会将这整个块写到WAL buffer日志缓冲区,然后再写到WAL日志文件中。然后我们再次对数据块进行插入数据B,内存中的数据缓冲区同样会再次新增一条数据B。在commit提交后,这个时候PG数据库就会将新插入数据B的事务日志条目写到WAL buffer日志缓冲区,最后再将这条数据库B的事务日志条目写到WAL日志文件中。

全页写就是把整个数据库块的内容写到WAL buffer日志缓冲区和WAL日志文件中。一个WAL记录长度是8字节,每个WAL段文件默认为16MB。一个WAL段可以记录将近200万事务。而如果存储8KB大小的数据块,只能储存2048个。就会导致WAL的写入量是非常大的。

全页写的特点

  • 全页写的概念
    将整个数据块写入到WAL日志文件中。
  • 全页写的优点
    提高数据库的安全性,解决块不一致问题。
  • 全页写的缺点
    导致WAL日志膨胀;
    增加额外的磁盘I/O,影响数据库整体性能;
    导致主备延迟变大。
  • 全页写的控制
    full_page_writes(默认on)。

全页写的模式

非强制模式

对于修改操作,当启用全页写时,pg会在每个检查点之后、每个页面第一次发生变更时,将头数据和整个页面作为一条WAL记录写入WAL缓冲区。

  • 最近一次检查点之后,第一次修改的数据块会进行全页写,后续再修改时不会进行全页写,直到下一次检查点发生。

强制模式

对于备份操作,强制启用全页写,只要块发生变化,就会被整块写入WAL文件(不管是不是第一次,也不管有没有检查点)。因此,它写入的量是更大的。

  • 当用pg_basebackup对数据库进行备份时,会自动执行强制模式,在备份期间被修改的数据块会全部写入WAL当中。
  • pg_start_backup命令,对应函数do_pg_start_backup(xlog.c文件),其中开启强制全页写。

pg_stop_backup对应的函数do_pg_stop_backup,有一句关闭强制全页写。

因此手动执行pg_start_backup命令之后,备份完一定要执行pg_stop_backup,避免WAL暴增

建议数据库备份时间点选在业务空闲时间段进行。

Oracle full-page-writes

1、不提供full-page-writes开关控制。

2、以下备份发生时自动启动全页写。

alter tablespace xxx begin backup;	
		alter database begin backup;

块不一致的场景

对PostgreSQL来说,块不一致可以发生在两种场景:

  • PG异常宕机(或者出现磁盘错误)时,数据文件中的页只写入了一部分。
  • 使用操作系统命令备份正在运行的数据库,备份途中源数据库可能被修改,此时得到的备份数据状态就是不一致的

无论是崩溃恢复还是备份还原的恢复,都无法基于不一致的数据块进行。

块不一致的原因

  • 操作系统进行I/O操作时,总是以块为单位,比如512字节、1KB等等。
  • 数据库块一般是操作系统块的整数倍,比如2k、4k、8k等等。
  • 块是数据库最小的I/O单位,当数据库写一个数据块时,操作系统需要I/O多次,可能在I/O过程中系统断电、磁盘故障等等原因导致一个数据块没有完整的写入,导致块不一致。

块不一致恢复

崩溃恢复

  • 通过checksum发现“部分写”的数据页,并将wal中保存的这个完整数据页覆盖当前损坏的数据页,然后再继续redo恢复整个数据库。

备份恢复

  • restore阶段,会直接还原不一致的块;但在recover阶段,会直接用WAL中一致的块对其进行覆盖,然后开始应用日志。

heap_xlog_insert

可以参考xlog的恢复代码

static void
heap_xlog_insert(XLogReaderState *record)
{
	XLogRecPtr	lsn = record->EndRecPtr;
	xl_heap_insert *xlrec = (xl_heap_insert *) XLogRecGetData(record);
	Buffer		buffer;
	Page		page;
	union
	{
		HeapTupleHeaderData hdr;
		char		data[MaxHeapTupleSize];
	}			tbuf;
	HeapTupleHeader htup;
	xl_heap_header xlhdr;
	uint32		newlen;
	Size		freespace = 0;
	RelFileNode target_node;
	BlockNumber blkno;
	ItemPointerData target_tid;
	XLogRedoAction action;

	XLogRecGetBlockTag(record, 0, &target_node, NULL, &blkno);
	ItemPointerSetBlockNumber(&target_tid, blkno);
	ItemPointerSetOffsetNumber(&target_tid, xlrec->offnum);

	/*
	 * The visibility map may need to be fixed even if the heap page is
	 * already up-to-date.
	 */
	if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
	{
		Relation	reln = CreateFakeRelcacheEntry(target_node);
		Buffer		vmbuffer = InvalidBuffer;

		visibilitymap_pin(reln, blkno, &vmbuffer);
		visibilitymap_clear(reln, blkno, vmbuffer, VISIBILITYMAP_VALID_BITS);
		ReleaseBuffer(vmbuffer);
		FreeFakeRelcacheEntry(reln);
	}

	/*
	 * If we inserted the first and only tuple on the page, re-initialize the
	 * page from scratch.
	 */
	if (XLogRecGetInfo(record) & XLOG_HEAP_INIT_PAGE)
	{
		buffer = XLogInitBufferForRedo(record, 0);
		page = BufferGetPage(buffer);
		PageInit(page, BufferGetPageSize(buffer), 0);
		action = BLK_NEEDS_REDO;
	}
	else
		action = XLogReadBufferForRedo(record, 0, &buffer);
	if (action == BLK_NEEDS_REDO)
	{
		Size		datalen;
		char	   *data;

		page = BufferGetPage(buffer);

		if (PageGetMaxOffsetNumber(page) + 1 < xlrec->offnum)
			elog(PANIC, "invalid max offset number");

		data = XLogRecGetBlockData(record, 0, &datalen);

		newlen = datalen - SizeOfHeapHeader;
		Assert(datalen > SizeOfHeapHeader && newlen <= MaxHeapTupleSize);
		memcpy((char *) &xlhdr, data, SizeOfHeapHeader);
		data += SizeOfHeapHeader;

		htup = &tbuf.hdr;
		MemSet((char *) htup, 0, SizeofHeapTupleHeader);
		/* PG73FORMAT: get bitmap [+ padding] [+ oid] + data */
		memcpy((char *) htup + SizeofHeapTupleHeader,
			   data,
			   newlen);
		newlen += SizeofHeapTupleHeader;
		htup->t_infomask2 = xlhdr.t_infomask2;
		htup->t_infomask = xlhdr.t_infomask;
		htup->t_hoff = xlhdr.t_hoff;
		HeapTupleHeaderSetXmin(htup, XLogRecGetXid(record));
		HeapTupleHeaderSetCmin(htup, FirstCommandId);
		htup->t_ctid = target_tid;

		if (PageAddItem(page, (Item) htup, newlen, xlrec->offnum,
						true, true) == InvalidOffsetNumber)
			elog(PANIC, "failed to add tuple");

		freespace = PageGetHeapFreeSpace(page); /* needed to update FSM below */

		PageSetLSN(page, lsn);

		if (xlrec->flags & XLH_INSERT_ALL_VISIBLE_CLEARED)
			PageClearAllVisible(page);

		/* XLH_INSERT_ALL_FROZEN_SET implies that all tuples are visible */
		if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET)
			PageSetAllVisible(page);

		MarkBufferDirty(buffer);
	}
	if (BufferIsValid(buffer))
		UnlockReleaseBuffer(buffer);

	/*
	 * If the page is running low on free space, update the FSM as well.
	 * Arbitrarily, our definition of "low" is less than 20%. We can't do much
	 * better than that without knowing the fill-factor for the table.
	 *
	 * XXX: Don't do this if the page was restored from full page image. We
	 * don't bother to update the FSM in that case, it doesn't need to be
	 * totally accurate anyway.
	 */
	if (action == BLK_NEEDS_REDO && freespace < BLCKSZ / 5)
		XLogRecordPageWithFreeSpace(target_node, blkno, freespace);
}

/*
 * Handles MULTI_INSERT record type.
 */
static void

参考

PostgreSQL技术内幕:事务处理深度探索

阿里云直播—pg-full-page机制与原理

PG技术大讲堂直播—PostgreSQL Full-Page Writes 全页写

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  nQkVcpdWfLDr   2023年11月13日   26   0   0 数据数据库SQL
IE5LYMWlmdvL