PgSQL · 特性分析 · 备库激活过程分析

前言

PostgreSQL standby 可以通过两种方法来激活成为主库:

  1. trigger file,配置在recovery.conf中。
  2. pg_ctl promote发送SIGUSR1信号给postmaster进程。

同时,PostgreSQL支持快速激活(fast promote)和非快速激活(fallback promote):

  1. fast promote 开启数据库读写前,不需要做检查点。而是推到开启读写之后执行一个CHECKPOINT_FORCE检查点。
  2. fallback_promote 在开启数据库读写前,需要先做一个检查点,现在这个模式已经不对用户开放,需要修改代码,只是用作调试。

实现分析

激活数据库的代码分析如下:

激活过程,根据fast_promote变量判断是否需要先做检查点,再激活。

src/backend/access/transam/xlog.c
        if (InRecovery)
        {
                if (bgwriterLaunched)
                {
                        if (fast_promote)  // 如果是快速promote,在打开数据库读写前,不需要创建检查点。只需要创建一个recovery结束标记
                        {
                                checkPointLoc = ControlFile->prevCheckPoint;

                                record = ReadCheckpointRecord(xlogreader, checkPointLoc, 1, false);
                                if (record != NULL)
                                {
                                        fast_promoted = true;
                                        CreateEndOfRecoveryRecord();
                                }
                        }

                        if (!fast_promoted)  //  如果是fallback_promote模式,须先创建一个检查点,再开启读写模式。
                                RequestCheckpoint(CHECKPOINT_END_OF_RECOVERY |
                                                                  CHECKPOINT_IMMEDIATE |
                                                                  CHECKPOINT_WAIT);
                }
...

        InRecovery = false;    //  开启读写模式,允许接收用户写请求.

        LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
        ControlFile->state = DB_IN_PRODUCTION;   //  改写控制文件的数据库状态
        ControlFile->time = (pg_time_t) time(NULL);
        UpdateControlFile();   //  更新控制文件
        LWLockRelease(ControlFileLock);

...
        if (fast_promoted)   //  如果是快速promote,在允许用户写请求后,在这里执行一个检查点。所以提高了数据库的可用时间。
                RequestCheckpoint(CHECKPOINT_FORCE);
......

通过pg_ctl命令行工具,向postmaster发SIGUSR1信号,通知它激活数据库。
首先会写一个promote文件,告诉postmaster,是fast_promote。

src/bin/pg_ctl/pg_ctl.c
/*
 * promote
 */

static void
do_promote(void)
{
        FILE       *prmfile;
        pgpid_t         pid;
        struct stat statbuf;

        pid = get_pgpid(false);

......
        sig = SIGUSR1;
        if (kill((pid_t) pid, sig) != 0)  // 发送SIGUSR1信号,通知postmaster激活数据库。
        {
                write_stderr(_("%s: could not send promote signal (PID: %ld): %s\n"),
                                         progname, pid, strerror(errno));
                if (unlink(promote_file) != 0)
                        write_stderr(_("%s: could not remove promote signal file \"%s\": %s\n"),
                                                 progname, promote_file, strerror(errno));
                exit(1);
        }

        print_msg(_("server promoting\n"));
}

数据恢复时,检查standby是否收到promote请求或是否存在trigger文件。
如果是promote请求,则检查有没有promote文件,或者fallback_promote文件,如果有promote文件,则是fast_promote请求。如果有fallback_promote文件,则不是fast_promote请求(实际上根本不可能检测到fallback_promote文件,因为没有写这个文件的操作)。所以通过pg_ctl promote来激活,一定是fast promote的,即不需要先做检查点再激活。
如果检查到trigger文件,同样也是fast promote激活模式。

src/backend/access/transam/xlog.c
#define PROMOTE_SIGNAL_FILE             "promote"
#define FALLBACK_PROMOTE_SIGNAL_FILE "fallback_promote"

/*
 * Check to see whether the user-specified trigger file exists and whether a
 * promote request has arrived.  If either condition holds, return true.
 */
static bool
CheckForStandbyTrigger(void)
{
        struct stat stat_buf;
        static bool triggered = false;

        if (triggered)
                return true;

        if (IsPromoteTriggered())   // 检查是否收到pg_ctl promote信号
        {
......
                if (stat(PROMOTE_SIGNAL_FILE, &stat_buf) == 0)    // 先检查promote文件是否存在
                {
                        unlink(PROMOTE_SIGNAL_FILE);
                        unlink(FALLBACK_PROMOTE_SIGNAL_FILE);
                        fast_promote = true;    //  快速promote
                }
                else if (stat(FALLBACK_PROMOTE_SIGNAL_FILE, &stat_buf) == 0)  // 否则再检查fallback_promote文件是否存在
                {
                        unlink(FALLBACK_PROMOTE_SIGNAL_FILE);
                        fast_promote = false;    //  先执行checkpoint再promote
                }

                ereport(LOG, (errmsg("received promote request")));

                ResetPromoteTriggered();
                triggered = true;
                return true;
        }

        if (TriggerFile == NULL)   //  检查recovery.conf是否配置了trigger_file
                return false;

        if (stat(TriggerFile, &stat_buf) == 0)
        {
                ereport(LOG,
                                (errmsg("trigger file found: %s", TriggerFile)));
                unlink(TriggerFile);
                triggered = true;
                fast_promote = true;    //   快速promote
                return true;
        }
        else if (errno != ENOENT)
                ereport(ERROR,
                                (errcode_for_file_access(),
                                 errmsg("could not stat trigger file \"%s\": %m",
                                                TriggerFile)));

        return false;
}

src/backend/postmaster/startup.c
        pqsignal(SIGUSR2, StartupProcTriggerHandler);  // 注册SIGUSR2信号处理函数

/* SIGUSR2: set flag to finish recovery */
static void
StartupProcTriggerHandler(SIGNAL_ARGS)
{
        int                     save_errno = errno;

        promote_triggered = true;
        WakeupRecovery();

        errno = save_errno;
}

bool
IsPromoteTriggered(void)
{
        return promote_triggered;
}

postmaster收到SIGUSER1信号后,检查是否收到promote信号,判断当前的状态是否处于恢复中的任意状态,然后向startup进程发一个SIGUSR2的信号,触发promote。

src/backend/postmaster/postmaster.c

        pqsignal(SIGUSR1, sigusr1_handler); /* message from child process */  // 注册SIGUSR1信号处理函数

/*
 * sigusr1_handler - handle signal conditions from child processes
 */
static void
sigusr1_handler(SIGNAL_ARGS)
{
......
        if (CheckPromoteSignal() && StartupPID != 0 &&
                (pmState == PM_STARTUP || pmState == PM_RECOVERY ||
                 pmState == PM_HOT_STANDBY || pmState == PM_WAIT_READONLY))
        {
                /* Tell startup process to finish recovery */
                signal_child(StartupPID, SIGUSR2);  //  向startup进程发SIGUSR2信号,通知它处理promote
        }
......

src/backend/access/transam/xlog.c
/*
 * Check to see if a promote request has arrived. Should be
 * called by postmaster after receiving SIGUSR1.
 */
bool
CheckPromoteSignal(void)
{
        struct stat stat_buf;

        if (stat(PROMOTE_SIGNAL_FILE, &stat_buf) == 0 ||
                stat(FALLBACK_PROMOTE_SIGNAL_FILE, &stat_buf) == 0)
                return true;

        return false;
}

最后提一点, 9.3以前,曾经出现过pg_ctl promote -m 来指定是否需要fast promote或者fallback promote。

时间: 2024-12-30 04:12:58

PgSQL · 特性分析 · 备库激活过程分析的相关文章

PgSQL · 特性分析 · 数据库崩溃恢复(上)

背景 为了合并I/O提高性能,PostgreSQL数据库引入了共享缓冲区,当数据库非正常关闭,比如服务器断电时,共享缓冲区即内存中的数据就会丢失,这个时候数据库操作系统重启时就需要从非正常状态中恢复过来,继续提供服务.本文将具体分析在这种情况下,PostgreSQL数据库如何从崩溃状态中恢复. 上期月报PgSQL · 特性分析 · checkpoint机制浅析中介绍了PostgreSQL中的checkpoint机制.其中提到,当PostgreSQL数据库崩溃恢复时,会以最近的checkpoint

PgSQL · 特性分析 · 数据库崩溃恢复(下)

背景 在上期月报PgSQL · 特性分析 · 数据库崩溃恢复(上),我们分析了PostgreSQL在数据库非正常退出后(包括通过recovery.conf用户主动恢复)的处理,概括起来分为以下几步: 1.如果满足以下条件之一,则进行非正常退出恢复 pg_control文件中的数据库状态不正常(非DB_SHUTDOWNED) pg_control文件中记录的最新检查点读取不到XLOG日志文件 2.用户指定recovery.conf文件主动恢复3.根据pg_control.backup_label确

PgSQL · 特性分析 · checkpoint机制浅析

背景 上期月报PgSQL · 特性分析 · Write-Ahead Logging机制浅析中简单介绍了PostgreSQL中WAL机制,其中讲到如果是创建checkpoint会触发刷新xlog日志页到磁盘,本文主要分析下PostgreSQL中checkpoint机制. checkpoint又名检查点,一般checkpoint会将某个时间点之前的脏数据全部刷新到磁盘,以实现数据的一致性与完整性.目前各个流行的关系型数据库都具备checkpoint功能,其主要目的是为了缩短崩溃恢复时间,以Oracl

PostgreSQL源码分析 备库查询冲突 - User was holding shared buffer pin for too long

背景 PostgreSQL 的基于流复制的物理备库是基于redo的物理块复制备库,允许开放只读的功能,但是需要注意,由于主库可能不断的产生redo,这些redo可能会与备库的QUERY产生冲突. 什么情况下query会堵塞.或与恢复冲突? 当以下操作产生的REDO被复制到备库,并且备库准备拿这些REDO来恢复时. Access Exclusive locks taken on the primary server, including both explicit LOCK commands an

PgSQL · 特性分析 · 金融级同步多副本分级配置方法

背景 对于金融级的应用场景,2个副本通常是不够的,用户可能会需要多个副本. 例如,一主4从,要求除了主以外,还需要2个同步的副本,其他可以为异步的副本. 另一方面,我们在使用数据库时,为了扩展读的能力,读写分离是比较常见的用法. 9.6以前的版本,同步复制是确保XLOG已经复制到备库,而不是已经在备库apply,虽然APPLY通常都很快,可能也在毫秒级别完成,但是以前没有apply级别的同步机制. 例如,用户A往用户B的账户汇了一笔钱,同一时间用户B在异地马上要看到A汇过来的钱,这种异地.异库的

PgSQL · 特性分析 · 时间线解析

"时间线"(Timeline)是PG一个很有特色的概念,在备份恢复方面的文档里面时有出现.但针对这个概念的详细解释却很少,也让人不太好理解,我们在此仔细解析一下. 时间线的引入 为了理解引入时间线的背景,我们来分析一下,如果没有时间线,会有什么问题?先举个将数据库恢复到以前时间点的例子.假设在一个数据库的运行过程中,DBA在周三12:00AM删掉了一个关键的表,但是直到周五中午才发现这个问题.这个时候DBA拿出最初的数据库备份,加上存在归档目录的日志文件,将数据库恢复到周三11:00A

PgSQL · 特性分析· JIT 在数据仓库中的应用价值

背景 近几年,分析型数据库中有项技术得到了广泛的应用.它就是 JIT(Just-in-time compilation)动态编译.还有一些相关名词 LLVM codegen 和这项技术相关.本文把这项技术做一个简单的分析,和大家分享. 一.JIT 是什么 长久以来数据仓库都是以高效的处理量数据的能力著称.随着硬件的发展,他们使用大量相关技术充分挖掘硬件的能力提高数据的吞吐量和处理效率.例如 SMP MPP mapreduce 列存储 压缩 等等.这么多年开源生态的活跃,使得我们对这类技术的实现细

PgSQL · 特性分析 · PostgreSQL 9.6 如何把你的机器掏空

背景 PostgreSQL 在向和纵向的扩展能力在开源数据库中一直处于非常领先的地位,例如今年推出的9.6,内置了sharding的功能,同时在scale-up的能力也有非常明显的提升,特别是在多核与高并发处理这块. 社区有同学在128核的机器上测试tpc-b的select only模式可以达到几百万的qps,机器的CPU资源被吃光光. 天下大势,分久必合,合久必分.谈了这么多年的sharding,业务也妥协了这么多年(比如不允许跨shard JOIN,忍受分片不平衡的痛楚,必须要有分区键值,分

PgSQL · 特性分析 · MVCC机制浅析

背景 我们在使用PostgreSQL的时候,可能会碰到表膨胀的问题(关于表膨胀可以参考之前的月报),即表的数据量并不大,但是占用的磁盘空间比较大,查询比较慢.为什么PostgreSQL有可能发生表膨胀呢?这是因为PostgreSQL引入了MVCC机制来保证事务的隔离性,实现数据库的隔离级别. 在数据库中,并发的数据库操作会面临脏读(Dirty Read).不可重复读(Nonrepeatable Read).幻读(Phantom Read)和串行化异常等问题,为了解决这些问题,在标准的SQL规范中