前言

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变量判断是否需要先做检查点,再激活。

  1. src/backend/access/transam/xlog.c
  2. if (InRecovery)
  3. {
  4. if (bgwriterLaunched)
  5. {
  6. if (fast_promote) // 如果是快速promote,在打开数据库读写前,不需要创建检查点。只需要创建一个recovery结束标记
  7. {
  8. checkPointLoc = ControlFile->prevCheckPoint;
  9. record = ReadCheckpointRecord(xlogreader, checkPointLoc, 1, false);
  10. if (record != NULL)
  11. {
  12. fast_promoted = true;
  13. CreateEndOfRecoveryRecord();
  14. }
  15. }
  16. if (!fast_promoted) // 如果是fallback_promote模式,须先创建一个检查点,再开启读写模式。
  17. RequestCheckpoint(CHECKPOINT_END_OF_RECOVERY |
  18. CHECKPOINT_IMMEDIATE |
  19. CHECKPOINT_WAIT);
  20. }
  21. ...
  22. InRecovery = false; // 开启读写模式,允许接收用户写请求.
  23. LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
  24. ControlFile->state = DB_IN_PRODUCTION; // 改写控制文件的数据库状态
  25. ControlFile->time = (pg_time_t) time(NULL);
  26. UpdateControlFile(); // 更新控制文件
  27. LWLockRelease(ControlFileLock);
  28. ...
  29. if (fast_promoted) // 如果是快速promote,在允许用户写请求后,在这里执行一个检查点。所以提高了数据库的可用时间。
  30. RequestCheckpoint(CHECKPOINT_FORCE);
  31. ......

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

  1. src/bin/pg_ctl/pg_ctl.c
  2. /*
  3. * promote
  4. */
  5. static void
  6. do_promote(void)
  7. {
  8. FILE *prmfile;
  9. pgpid_t pid;
  10. struct stat statbuf;
  11. pid = get_pgpid(false);
  12. ......
  13. sig = SIGUSR1;
  14. if (kill((pid_t) pid, sig) != 0) // 发送SIGUSR1信号,通知postmaster激活数据库。
  15. {
  16. write_stderr(_("%s: could not send promote signal (PID: %ld): %s\n"),
  17. progname, pid, strerror(errno));
  18. if (unlink(promote_file) != 0)
  19. write_stderr(_("%s: could not remove promote signal file \"%s\": %s\n"),
  20. progname, promote_file, strerror(errno));
  21. exit(1);
  22. }
  23. print_msg(_("server promoting\n"));
  24. }

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

  1. src/backend/access/transam/xlog.c
  2. #define PROMOTE_SIGNAL_FILE "promote"
  3. #define FALLBACK_PROMOTE_SIGNAL_FILE "fallback_promote"
  4. /*
  5. * Check to see whether the user-specified trigger file exists and whether a
  6. * promote request has arrived. If either condition holds, return true.
  7. */
  8. static bool
  9. CheckForStandbyTrigger(void)
  10. {
  11. struct stat stat_buf;
  12. static bool triggered = false;
  13. if (triggered)
  14. return true;
  15. if (IsPromoteTriggered()) // 检查是否收到pg_ctl promote信号
  16. {
  17. ......
  18. if (stat(PROMOTE_SIGNAL_FILE, &stat_buf) == 0) // 先检查promote文件是否存在
  19. {
  20. unlink(PROMOTE_SIGNAL_FILE);
  21. unlink(FALLBACK_PROMOTE_SIGNAL_FILE);
  22. fast_promote = true; // 快速promote
  23. }
  24. else if (stat(FALLBACK_PROMOTE_SIGNAL_FILE, &stat_buf) == 0) // 否则再检查fallback_promote文件是否存在
  25. {
  26. unlink(FALLBACK_PROMOTE_SIGNAL_FILE);
  27. fast_promote = false; // 先执行checkpoint再promote
  28. }
  29. ereport(LOG, (errmsg("received promote request")));
  30. ResetPromoteTriggered();
  31. triggered = true;
  32. return true;
  33. }
  34. if (TriggerFile == NULL) // 检查recovery.conf是否配置了trigger_file
  35. return false;
  36. if (stat(TriggerFile, &stat_buf) == 0)
  37. {
  38. ereport(LOG,
  39. (errmsg("trigger file found: %s", TriggerFile)));
  40. unlink(TriggerFile);
  41. triggered = true;
  42. fast_promote = true; // 快速promote
  43. return true;
  44. }
  45. else if (errno != ENOENT)
  46. ereport(ERROR,
  47. (errcode_for_file_access(),
  48. errmsg("could not stat trigger file \"%s\": %m",
  49. TriggerFile)));
  50. return false;
  51. }
  52. src/backend/postmaster/startup.c
  53. pqsignal(SIGUSR2, StartupProcTriggerHandler); // 注册SIGUSR2信号处理函数
  54. /* SIGUSR2: set flag to finish recovery */
  55. static void
  56. StartupProcTriggerHandler(SIGNAL_ARGS)
  57. {
  58. int save_errno = errno;
  59. promote_triggered = true;
  60. WakeupRecovery();
  61. errno = save_errno;
  62. }
  63. bool
  64. IsPromoteTriggered(void)
  65. {
  66. return promote_triggered;
  67. }

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

  1. src/backend/postmaster/postmaster.c
  2. pqsignal(SIGUSR1, sigusr1_handler); /* message from child process */ // 注册SIGUSR1信号处理函数
  3. /*
  4. * sigusr1_handler - handle signal conditions from child processes
  5. */
  6. static void
  7. sigusr1_handler(SIGNAL_ARGS)
  8. {
  9. ......
  10. if (CheckPromoteSignal() && StartupPID != 0 &&
  11. (pmState == PM_STARTUP || pmState == PM_RECOVERY ||
  12. pmState == PM_HOT_STANDBY || pmState == PM_WAIT_READONLY))
  13. {
  14. /* Tell startup process to finish recovery */
  15. signal_child(StartupPID, SIGUSR2); // 向startup进程发SIGUSR2信号,通知它处理promote
  16. }
  17. ......
  18. src/backend/access/transam/xlog.c
  19. /*
  20. * Check to see if a promote request has arrived. Should be
  21. * called by postmaster after receiving SIGUSR1.
  22. */
  23. bool
  24. CheckPromoteSignal(void)
  25. {
  26. struct stat stat_buf;
  27. if (stat(PROMOTE_SIGNAL_FILE, &stat_buf) == 0 ||
  28. stat(FALLBACK_PROMOTE_SIGNAL_FILE, &stat_buf) == 0)
  29. return true;
  30. return false;
  31. }

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