PostgreSQL数据库如何从崩溃中恢复(下)

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介:

背景

在上期月报PgSQL · 特性分析 · 数据库崩溃恢复(上),我们分析了PostgreSQL在数据库非正常退出后(包括通过recovery.conf用户主动恢复)的处理,概括起来分为以下几步:

  1. 如果满足以下条件之一,则进行非正常退出恢复

    • pg_control文件中的数据库状态不正常(非DB_SHUTDOWNED)
    • pg_control文件中记录的最新检查点读取不到XLOG日志文件
    • 用户指定recovery.conf文件主动恢复
  2. 根据pg_control、backup_label确定恢复的XLOG日志记录起点
  3. 不断读取每个XLOG record,根据所属的资源管理器去调用各自:

    • rm_startup()
    • rm_redo(EndRecPtr, record)
    • rm_cleanup();
  4. 不断恢复XLOG日志记录,直到满足以下条件之一,则停止恢复正常启动

    • 达到recovery.conf文件中规定的最终的目标恢复位点
    • 当前XLOG日志全部应用完成
  5. 清理环境,并启动需要的辅助进程

其中,PostgreSQL会根据每个XLOG record所属的资源管理器操作来执行对应的函数。PostgreSQL中有以下的资源管理器:

RM_XLOG_ID
RM_XACT_ID
RM_SMGR_ID
RM_CLOG_ID
RM_DBASE_ID
RM_TBLSPC_ID
RM_MULTIXACT_ID
RM_RELMAP_ID
RM_STANDBY_ID
RM_HEAP2_ID
RM_HEAP_ID
RM_BTREE_ID
RM_HASH_ID
RM_GIN_ID
RM_GIST_ID
RM_SEQ_ID
RM_SPGIST_ID

而每种资源管理器,都存在几种具体的操作,例如RM_HEAP_ID包括以下操作(后面的数字代表对应XLogRecord中xl_info字段高4位,详情参考上期月报):

#define XLOG_HEAP_INSERT        0x00
#define XLOG_HEAP_DELETE        0x10
#define XLOG_HEAP_UPDATE        0x20
/* 0x030 is free, was XLOG_HEAP_MOVE */
#define XLOG_HEAP_HOT_UPDATE    0x40
#define XLOG_HEAP_NEWPAGE        0x50
#define XLOG_HEAP_LOCK            0x60
#define XLOG_HEAP_INPLACE        0x70

下面我们将以堆表的INSERT操作为例,具体分析对应的XLOG record内容和资源管理器对应的处理函数。

XLOG record

XLOG record 结构

PgSQL · 特性分析 · 数据库崩溃恢复(上)中,分析了WAL日志页的基本结构。其中,一条日志记录的的组织形式如下所示(以下分析基于RDS for PostgreSQL,即9.4版本):

 *        Fixed-size header (XLogRecord struct) /*日志记录头*/
 *        rmgr-specific data /*资源管理器数据,对应操作的对象,譬如元组的id等内容*/
 *        BkpBlock/*备份块块头*/
 *        backup block data/*操作的一些数据,如更新元组时,要更新的新值存储在这个区域*/
 *        BkpBlock
 *        backup block data
 *        ...

为了更好地探究堆表INSERT操作对应XLOG record 的内容,我们创建一个简单的TABLE,并执行INSERT操作:

create table test(id int);
insert into test values(3);

XLogInsert函数

在执行INSERT操作的时候,PostgreSQL会调用heap_insert函数,其中会调用XLogInsert去插入对应的XLOG record:

recptr = XLogInsert(RM_HEAP_ID, info, rdata);
PageSetLSN(page, recptr);

注意:实际上是调用两次XLogInsert,除了HEAP INSERT操作的XLOG record,还有事务提交的XLOG record。

函数XLogInsert的返回值是XLogRecPtr结构类型,即LSN(log secquence number)。heap_insert函数在执行XLogInsert()后,把其返回值XLogRecPtr记录赋值给对应的page的PageHeaderData结构中,以实现WAL机制(参考PgSQL · 特性分析 · Write-Ahead Logging机制浅析)。

XLogInsert函数中会去包装一个XLOG record,并把它刷写到磁盘,我们接下来分析一下XLogInsert函数。

XLogInsert函数定义:

XLogRecPtr XLogInsert(RmgrId rmid, uint8 info, XLogRecData *rdata)

XLogInsert的三个函数参数分别是:

  • rmid

    • RmgrId类型
    • 代表本条XLOG record所属的资源管理器类型,例如我们上面的例子中INSERT操作属于RM_HEAP_ID,即堆表资源管理器
  • info

    • uint8类型
    • 代表资源管理器对应的操作,例如堆表中INSERT操作为0x00
  • rdata

    • XLogRecData指针类型(链表)
    • 每个XLogRecData结构体存储对应的资源管理器数据rmgr-specific data

之所以要用XLogRecData链,是因为在所要处理的日志记录实体数据在内存空间可能不是连续存储的,而且数据可能分布在多个缓冲区内,需要用XlogRecData链表将它们组织起来。XlogRecData数据结构如下:

typedef struct XLogRecData
{
    char       *data;    /*包含实体数据的起始位置*/
    uint32        len;        /*包含实体数据大小*/
    Buffer        buffer;    /*如果有buffer指明第几个缓冲区*/
    bool        buffer_std;    /*是否含有标准的pd_lower/pd_upper结构*/
    struct XLogRecData *next;    /*指向下一个XLogRecData*/
} XLogRecData;

其中,buffer_std该值为true,则容许XLOG释放备份页的空闲空间,空闲空间由pd_lower和pd_upper限定:

  • pd_lower表示页面起始位置与未分配空间开头的字节偏移
  • pd_upper表示页面末尾位置与未分配空间末尾的字节偏移

通过分析三个XLogInsert函数参数,可以看出XLogInsert主要是将rdata封装成一个XLOG record。接下来我们将分析heap_insert函数内如何对rdata进行赋值。

heap_insert函数

heap_insert函数主要操作HeapTupleData结构体,对应在每个数据页中存储的每个tuple,结构如下图所示:
image.png

tuple分为头部信息和数据信息,这里不再展开,我们将在分析PostgreSQL的MVCC机制时,将其中的结构详细分析。

heap_insert函数的主要操作如下:

  • 调用RelationGetBufferForTuple方法找到shmem里缓存数据块buffer
  • 调用RelationPutHeapTuple方法,把组装好的元组tuple放到对应buffer中合适的位置
  • 赋值XLogRecData类型变量rdata,通过代码分析可以看出rdata实际上是对tuple的内容摘要

    • XLogRecData rdata[4]; 堆表的INSERT操作有4个XLogRecData结构体组成的链表
    • rdata[0].data 存储一个xl_heap_insert结构,用于标示一些基本信息:
  1. struct xl_heap_insert
    {
    xl_heaptid target; / inserted tuple id /
    uint8 flags;
    / xl_heap_header & TUPLE DATA FOLLOWS AT END OF STRUCT /
    } xl_heap_insert;

    
    - rdata[0].buffer = InvalidBuffer
    - rdata[1].data存储一个xl_heap_header结构,存储tuple头部的简化信息:
    
  2. struct xl_heap_header
    {
    uint16 t_infomask2;
    uint16 t_infomask;
    uint8 t_hoff;
    } xl_heap_header;

    - rdata[1].buffer = need_tuple_data ? InvalidBuffer : buffer;如果需要存储整个数据块,则把buffer赋值给rdata
    - rdata[2].data存储tuple头部后面的数据,比如INSERT操作的插入元组的每列的数值
    - rdata[2].buffer = need_tuple_data ? InvalidBuffer : buffer;同rdata[1]
  • 调用XLogInsert,将rdata封装成XLOG record写入WAL缓冲区,如果需要切换日志段文件,调用XLogWrite刷写到磁盘

经过以上分析,我们可以知道,XLOG record的核心部分是资源管理器数据(XLogRecData)和备份数据块(backup block data),这两个数据包含了我们恢复时候需要的数据。在各个资源管理器的具体操作调用XLogInsert之前,需要对这两个部分进行填充。

资源管理器对应处理函数

资源管理器结构

在恢复过程中,每个资源管理器对应的处理函数是不同的,为了更好的抽象资源管理器,在PostgreSQL中定义了RmgrData结构体和一个RmgrData类型数组RmgrTable。

typedef struct RmgrData
{
    const char *rm_name;
    void        (*rm_redo) (XLogRecPtr lsn, struct XLogRecord *rptr);
    void        (*rm_desc) (StringInfo buf, uint8 xl_info, char *rec);
    void        (*rm_startup) (void);
    void        (*rm_cleanup) (void);
} RmgrData;
extern const RmgrData RmgrTable[];
PG_RMGR(RM_XLOG_ID, "XLOG", xlog_redo, xlog_desc, NULL, NULL)
PG_RMGR(RM_XACT_ID, "Transaction", xact_redo, xact_desc, NULL, NULL)
PG_RMGR(RM_SMGR_ID, "Storage", smgr_redo, smgr_desc, NULL, NULL)
PG_RMGR(RM_CLOG_ID, "CLOG", clog_redo, clog_desc, NULL, NULL)
PG_RMGR(RM_DBASE_ID, "Database", dbase_redo, dbase_desc, NULL, NULL)
PG_RMGR(RM_TBLSPC_ID, "Tablespace", tblspc_redo, tblspc_desc, NULL, NULL)
PG_RMGR(RM_MULTIXACT_ID, "MultiXact", multixact_redo, multixact_desc, NULL, NULL)
PG_RMGR(RM_RELMAP_ID, "RelMap", relmap_redo, relmap_desc, NULL, NULL)
PG_RMGR(RM_STANDBY_ID, "Standby", standby_redo, standby_desc, NULL, NULL)
PG_RMGR(RM_HEAP2_ID, "Heap2", heap2_redo, heap2_desc, NULL, NULL)
PG_RMGR(RM_HEAP_ID, "Heap", heap_redo, heap_desc, NULL, NULL)
PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, NULL, NULL)
PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, NULL, NULL)
PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_xlog_startup, gin_xlog_cleanup)
PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_xlog_startup, gist_xlog_cleanup)
PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, NULL, NULL)
PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_xlog_startup, spg_xlog_cleanup)

RmgrTable的下标对应上文提到的资源管理器ID,比如HEAP表对应的RmgrData是RmgrTable[RM_HEAP_ID]。

RmgrData中定义了rm_redo、rm_desc、rm_startup、rm_cleanup四个函数指针,分别对应每个资源管理器具体的redo、desc、startup、cleanup函数,我们主要分析下其中的rm_redo恢复函数

REDO函数

不同资源管理器的REDO函数参数都为(XLogRecPtr lsn, XLogRecord *record),这两个类型的参数我们在前面两期月报中均有涉及。其中:

  • XLogRecPtr类型代表着该log record在日志序列中的位置
  • XLogRecord类型代表着该log record的头部信息

REDO函数主要是根据该log record的位置取到对应的log record进行相应的恢复操作,下面我们以堆表INSERT操作为例,分析下对应的REDO函数heap_xlog_insert:

  • 调用XLogRecGetData(record)方法,取出上文中的xl_heap_insert结构xlrec
  • 如果该XLOG record存在full-page image,则恢复该数据块
  • 将上文提到的rdata恢复成tuple,写入到缓存数据块中
  • 标记缓存数据块为脏页,等待刷出

通过以上分析,我们可以大体知道XLOG record满足了以下几点才能实现崩溃恢复甚至是任意时间点恢复:

  • 可靠性

    • full-page image,每次checkpoint第一次更新时都需要将整页复制
    • CRC32校验码
    • 插入XLOG record时不接受其他信号
  • 可重复性

    • 多次重放一个log record,得到的结果相同
  • 可恢复性

    • 大多数资源管理器操作对应的log record中存储的都是对应数据在磁盘存储的一个摘要(例如INSERT操作的rdata是tuple的一个摘要)

XLOG日志在PostgreSQL运维中占据了非常重要的地位,从WAL机制到备份恢复以及主备复制,许多功能都离不开XLOG日志。推荐大家看下《PostgreSQL Replication》这本书,加深对PostgreSQL中XLOG以及Replication的理解。

另外,PostgreSQL XLOG目前也存在一些问题,最明显的问题就是写放大,因为full-page image导致XLOG record体积太大,如果设置不合理,可能日志的体积是数据体积的20倍左右。但是经过参数调优,可以尽可能地避免这种情况,可参考文章如何遏制PostgreSQL WAL的疯狂增长

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
25天前
|
关系型数据库 分布式数据库 数据库
成都晨云信息技术完成阿里云PolarDB数据库产品生态集成认证
近日,成都晨云信息技术有限责任公司(以下简称晨云信息)与阿里云PolarDB PostgreSQL版数据库产品展开产品集成认证。测试结果表明,晨云信息旗下晨云-站群管理系统(V1.0)与阿里云以下产品:开源云原生数据库PolarDB PostgreSQL版(V11),完全满足产品兼容认证要求,兼容性良好,系统运行稳定。
|
1月前
|
关系型数据库 分布式数据库 数据库
PolarDB常见问题之数据库不能自己减少节点如何解决
PolarDB是阿里云推出的下一代关系型数据库,具有高性能、高可用性和弹性伸缩能力,适用于大规模数据处理场景。本汇总囊括了PolarDB使用中用户可能遭遇的一系列常见问题及解答,旨在为数据库管理员和开发者提供全面的问题指导,确保数据库平稳运行和优化使用体验。
|
1月前
|
缓存 关系型数据库 分布式数据库
PolarDB常见问题之数据库cpu突然飙高如何解决
PolarDB是阿里云推出的下一代关系型数据库,具有高性能、高可用性和弹性伸缩能力,适用于大规模数据处理场景。本汇总囊括了PolarDB使用中用户可能遭遇的一系列常见问题及解答,旨在为数据库管理员和开发者提供全面的问题指导,确保数据库平稳运行和优化使用体验。
|
2月前
|
关系型数据库 分布式数据库 数据库
阿里云PolarDB登顶2024中国数据库流行榜:技术实力与开发者影响力
近日,阿里云旗下的自研云原生数据库PolarDB在2024年中国数据库流行度排行榜中夺冠,并刷新了榜单总分纪录,这一成就引起了技术圈的广泛关注。这一成就源于PolarDB在数据库技术上的突破与创新,以及对开发者和用户的实际需求的深入了解体会。那么本文就来分享一下关于数据库流行度排行榜的影响力以及对数据库选型的影响,讨论PolarDB登顶的关键因素,以及PolarDB“三层分离”新版本对开发者使用数据库的影响。
74 3
阿里云PolarDB登顶2024中国数据库流行榜:技术实力与开发者影响力
|
1月前
|
关系型数据库 分布式数据库 数据库
PolarDB PostgreSQL版:Oracle兼容的高性能数据库
PolarDB PostgreSQL版是一款高性能的数据库,具有与Oracle兼容的特性。它采用了分布式架构,可以轻松处理大量的数据,同时还支持多种数据类型和函数,具有高可用性和可扩展性。它还提供了丰富的管理工具和性能优化功能,为企业提供了可靠的数据存储和处理解决方案。PolarDB PostgreSQL版在数据库领域具有很高的竞争力,可以满足各种企业的需求。
|
10天前
|
运维 关系型数据库 分布式数据库
「合肥 * 讯飞」4 月 19 日 PolarDB 开源数据库沙龙,报名中!
4月19日周五,PolarDB开源社区联合科大讯飞共同举办开源数据库技术沙龙,本次沙龙我们邀请了众多数据库领域的专家,期待大家的参与!
「合肥 * 讯飞」4 月 19 日 PolarDB 开源数据库沙龙,报名中!
|
1月前
|
存储 关系型数据库 分布式数据库
PolarDB常见问题之PolarDB突然有大量服务连不上数据库如何解决
PolarDB是阿里云推出的下一代关系型数据库,具有高性能、高可用性和弹性伸缩能力,适用于大规模数据处理场景。本汇总囊括了PolarDB使用中用户可能遭遇的一系列常见问题及解答,旨在为数据库管理员和开发者提供全面的问题指导,确保数据库平稳运行和优化使用体验。
|
1月前
|
存储 关系型数据库 MySQL
TiDB与MySQL、PostgreSQL等数据库的比较分析
【2月更文挑战第25天】本文将对TiDB、MySQL和PostgreSQL等数据库进行详细的比较分析,探讨它们各自的优势和劣势。TiDB作为一款分布式关系型数据库,在扩展性、并发性能等方面表现突出;MySQL以其易用性和成熟性受到广泛应用;PostgreSQL则在数据完整性、扩展性等方面具有优势。通过对比这些数据库的特点和适用场景,帮助企业更好地选择适合自己业务需求的数据库系统。
|
1月前
|
缓存 监控 安全
宝塔数据库崩溃解决方案详解
宝塔数据库崩溃解决方案详解
|
1月前
|
Cloud Native 关系型数据库 分布式数据库
**PolarDB IMCI:云原生时代的智能数据库新选择**
**PolarDB IMCI:云原生时代的智能数据库新选择**
26 4