在任何时候,PostgreSQL 都在集群的数据目录的 pg_xlog/ 子目录里维护着一套预写日志(WAL)。这些日志记录着每一次对数据库的修改细节。这些日志存在是为了防止崩溃:如果系统崩溃,数据库可以通过"重放"上次检查点以来的日志记录以恢复数据库的完整性。但是,日志的存在让它还可以用于第三种备份数据库的策略:我们可以组合文件系统备份与 WAL 文件的备份。如果需要恢复,我们就恢复备份,然后重放备份了的 WAL 文件,把备份恢复到当前的时间。这个方法对管理员来说,明显比以前的方法更复杂,但是有非常明显的优势:
在开始的时候我们不需要一个非常完美的一致的备份。任何备份内部的不一致都会被日志重放动作修改正确(这个和崩溃恢复时发生的事情没什么区别)。因此我们不需要文件系统快照的功能,只需要 tar 或者类似的归档工具。
因为我们可以把无限长的 WAL 文件序列连接起来,所以连续的备份简化为连续地对 WAL 文件归档来实现。这个功能对大数据库特别有用,因为大数据库的全备份可能并不方便。
我们可没说重放 WAL 记录的时候我们必须重放到结尾。我们可以在任意点停止重放,这样就有一个在任意时间的数据库一致的快照。因此,这个技术支持即时恢复:我们可以把数据库恢复到你开始备份以来的任意时刻的状态。
如果我们持续把 WAL 文件序列填充给其它装载了同样的基础备份文件的机器,我们就有了一套热备份系统:在任何点我们都可以启动第二台机器,而它拥有近乎当前的数据库拷贝。
和简单的文件系统备份技术一样,这个方法只能支持整个数据库集群的恢复,而不是一个子集。同样,它还要求大量的归档存储:基础备份量可能很大,而且忙碌的系统将生成许多兆需要备份的的 WAL 流量。但是,它仍然时在需要高可靠性的场合下的最好的备份技术。
要想从在线备份中成功恢复,你需要一套连续的 WAL 归档文件,它们最远回朔到你开始备份的时刻。因此,要想开始备份,你应该在开始第一次基础备份之前根据我们讨论过的归档 WAL 文件机制设置并测试你的步骤。
抽像来看,一个运行着的 PostgreSQL 系统生成一个无限长的 WAL 日志序列。系统物理上把这个序列分隔成 WAL 段文件,通常每段 16M(在编译 PostgreSQL 的时候可以改变其大小)。这些段文件的名字是数值命名的,这些数值反映他们在抽取出来的 WAL 序列中的位置。在不适用 WAL 归档的时候,系统通常只是创建几个段文件然后"循环"使用它们,方法是把不再使用的段文件的名字重命名为更高的段编号。系统假设那些内容比前一次检查点更老的段文件已经没用了,然后就可以循环利用。
在归档 WAL 数据的时候,我们希望在每个段文件填充满之后捕获之,并且把这些数据在段文件被循环利用之前保存在某处。根据应用以及可用的硬件的不同,我们可以有许多不同的方法"把数据保存在某处":我们可以把段文件拷贝到一个 NFS 挂载的目录,把它们放到另外一台机器上,或者把它们写入磁带机里(需要保证你有办法把文件恢复为原名),或者把它们打成包,烧录到 CD 里,或者是其它的什么方法。为了给数据库管理员提供最大可能性的灵活性,PostgreSQL 试图不对如何归档做任何假设。取而代之的是,PostgreSQL 让管理员声明一个 shell 命令执行来拷贝一个完整的段文件到它需要去的地方。该命令可以简单得就是一个 cp ,或者它可以调用一个复杂的 shell 脚本,这些都由管理员决定。
所使用的 shell 命令由配置参数 archive_command 声明,它实际上总是放在 postgresql.conf 文件里的。在这个字符串里,任何 %p 都被要归档文件的绝对路径代替,而任何 %f 只是被文件名代替。如果你需要在命令里嵌入一个真正的 % 字符,那么必须双写(%%)。最简单的有用命令类似下面这样
archive_command = 'cp -i %p /mnt/server/archivedir/%f </dev/null'
它将把 WAL 段拷贝到 /mnt/server/archivedir 目录。这个只是一个例子,并非我们建议的方法,可能不能在所有系统上都正确运行。
归档命令将在运行 PostgreSQL 服务器的同一个用户的权限下执行。因此被归档的 WAL 文件实际上包含你的数据库里的所有东西,所以你应该确保自己的归档数据不会被别人窥探;比如,归档到一个没有组或者全局读权限的目录里。
有一点很重要:当且仅当归档命令成功时,它才返回零。在得到一个零值结果之后,PostgreSQL 将假设该 WAL 段文件已经成功归档,因此它稍后将被删除或者被新的数据覆盖。但是,一个非零值告诉 PostgreSQL 该文件没有被归档;因此它会周期性的重试直到成功。
归档命令通常应该设计成拒绝覆盖已经存在的归档文件。这是一个非常重要的安全特性,可以在管理员操作失误(比如把两个不同的服务器的输出发送到同一个归档目录)的时候保持你的归档的完整性。我们建议你首先要测试你准备使用到归档命令,以保证它实际上不会覆盖现有的文件,并且在这种情况下它返回非零状态。我们发现,在这方面,cp -i 在某些平台上是正确的,而在其它平台上是不正确的。如果选定的命令本身并不能正确处理这个问题,你应该增加一个命令预先探测归档文件是否存在。比如,类似下面的东西。
archive_command = 'test ! -f .../%f && cp %p .../%f'
在几乎所有的 Unix 变种上都工作正确。
在设计你的归档环境都时候,请考虑一下如果归档命令不停失败会发生什么情况,因为有些方面要求操作者的干涉,或者是归档空间不够了。比如,如果你往磁带机上写,但是没有自动换带机,那么就有可能发生这种情况;如果磁带满了,那就除非换磁带,否则啥事也做不了。你应该确保人和错误条件或者人和要求操作员干涉带错误都会正确报告,这样才能迅速解决这些问题。否则 pg_xlog/ 目录会不停地填充 WAL 段文件,直到问题解决。
归档命令的速度并不要紧,只要它能跟上你的服务器生成 WAL 数据的平均速度即可。即使归档进程落在了后面一点,正常的操作也会继续进行。如果归档进程慢很多,就会增加灾难发生时的数据丢失量。同时也意味着 pg_xlog/ 目录包含大量未归档的日志段文件,并且可能最后超出了磁盘空间。我们建议你监控归档进程,确保它是按照你的意识运转的。
在写自己的归档命令的时候,你应该假设被归档的文件最多 64 个字符长并且可以包含ASCII字母、数字、点的任意组合。我们不必要记住原始的全路径(%p),但是有必要记住文件名(%f)。
请注意尽管 WAL 归档允许你恢复任何对 PostgreSQL 数据库的修改,在最初的基础备份之后,它还是不会恢复对配置文件的修改(postgresql.conf, pg_hba.conf, pg_ident.conf),因为这些文件都是手工编辑的,而不是通过 SQL 操作来编辑的。所以你可能会需要把你的配置文件放在一个日常文件系统备份过程即可处理到的地方。参阅节17.2获取如何重定位配置文件的知识。
因为归档命令仅在已经完成的 WAL 段上调用,因此,如果你的服务器只产生很小的 WAL 流量或段之间的间隔很长,那么在事务完成之后与其被安全归档之间就会存在很长的延时。为了限制未归档数据的最长期限,可以设置 archive_timeout 强制服务器在切换 WAL 段之间的时间间隔。需要注意的是,由于强制切换而提早结束的已归档文件的大小与完整的归档文件相同。因此将 archive_timeout 设为一个很小的值是不明智的,它将很快耗尽归档空间。将 archive_timeout 设置为 60 秒左右通常是比较合理的。
同样,如果你想确保刚刚完成的事务被立即归档,那么也可以通过 pg_switch_xlog
手动强制切换段文件。其它与 WAL 管理相关的工具函数在表9-47中列出。
进行基础备份的过程相当简单:
确保 WAL 归档打开并且可以运转。
以数据库超级用户身份连接到数据库,发出命令
SELECT pg_start_backup('label');
这里的 label 是任意你想使用的这次备份操作的唯一标识(一个好习惯是使用备份转储文件的放置地全路径)。pg_start_backup
用备份信息在集群目录里创建一个备份标签文件 backup_label 。
至于你连接到集群中的那个数据库没什么关系。你可以忽略函数返回的结果;但是如果它报告错误,那么在继续之前先处理它。
执行备份,使用任何方便的文件系统工具,比如 tar 或 cpio 。这些操作过程中既不需要关闭数据库,也不需要关闭数据库的操作。
再次以数据库超级用户身份连接数据库,然后发出命令
SELECT pg_stop_backup();
这将中止备份模式并自动切换到下一个 WAL 段。自动切换是为了在备份间隔中写入的最后一个 WAL 段文件可以立即为下次备份作好准备。
只要在备份过程中使用的 WAL 段文件作为正常数据库活动的一部分备份完毕,你的备份工作就完成了。由 pg_stop_backup
的结果标识的文件是完成备份的最后一个段。因为你已经配置了 archive_command ,所以这些文件的归档将自动发生。在大多数情况下,这个动作非常迅速,但是我们仍然建议你监视归档系统以确保备份成功。
有些工具在拷贝文件的时候,如果其正拷贝的文件改变了,就会发出警告或者错误信息。在进行一个活跃数据库的基础备份的时候,这种情况是正常的,不是错误;因此你必须能够区别这种报怨和真正的错误。比如,有些版本的 rsync会给"vanished source files"(源文件消失)发出独立的退出码,你可以写一个驱动脚本接受这样的退出码,把它当作非错误类型。还有,有些版本的 GNU tar 改变正在处理的文件会导致错误。而且看来好像除了手工检查 tar 的消息之外,没有什么很方便的办法来区别这类错误。因此 GNU tar 并不是做基础备份的最好工具。
我们不需要太关心在 pg_start_backup
和开始实际备份之间的时间开销,也不需要太关心备份结束和 pg_stop_backup
之间的时间;几分钟的延迟没什么害处。然而,如果在关闭 full_page_writes 的情况下运行服务器,你必须注意在 pg_start_backup
和 pg_stop_backup
之间执行的 DROP 命令,因为 full_page_writes 在备份模式中仍然有效。你必须确保这些操作是按顺序执行的而不是重叠执行的,否则备份可能是无效的。
要保证你的备份转储包括所有数据库集群目录里的文件(比如 /usr/local/pgsql/data)。如果你在使用并未放置在这个目录里的表空间,也要小心地包含它们,并且要确保你的备份转储归档符号连接是符号连接,否则,恢复会把你的表空间搞乱。
不过,你可以在备份转储文件里省略集群目录下的 pg_xlog/ 子目录。这个略微复杂些的动作是值得的,因为它减少了恢复时候的错误。如果 pg_xlog/ 是一个指向集群目录之外的符号连接,那么这件事情很容易处理,出于性能考虑的时候经常这么做。
要使用这个备份,你需要保存所有备份开始以及之后的 WAL 段文件。为了帮助你实现这个任务,pg_stop_backup
函数创建一个备份历史文件,它马上存储到 WAL 归档区域。这个文件的名字是以你在使用备份的时候需要的第一个 WAL 段文件的名字命名的。比如,如果开始 WAL 文件是 0000000100001234000055CD ,那么备份历史文件将命名为类似 0000000100001234000055CD.007C9330.backup 这样的东西。这个文件名的第二部分表示在该 WAL 文件里面的准确位置,通常可以被忽略。一旦你安全地把这些日志段文件归了档,那么你就可以删除所有那些数值名字在这个文件前面的归档的 WAL 段。文件系统备份不再需要它们了。当然,你应当保留几套备份以绝对确保可以恢复先前的数据。
备份历史文件只是一个小的文本文件。它包含你给予 pg_start_backup
的标签字符串,以及备份的起始时间和终止时间。如果你使用这个标签来表示转储文件放在哪里,则在需要的时候,归档的历史文件就足够告诉你转储文件存放在哪里了。
因为你必须保留直到最后一次基础备份的所有归档的 WAL 文件,那么两次基础备份之间的间隔通常是根据你想在归档 WAL 文件上花多少存储空间来定的。你还应该考虑你准备在恢复上花多少时间。如果需要恢复的话,系统将需要重放所有那些段,而如果最后一次基础备份以来,时间已经很长了,那么那些动作可能会花掉好些时间。
还有一件事值得一提,那就是 pg_start_backup
函数在数据库集群目录里创建了一个叫 backup_label 的文件,它被 pg_stop_backup
删除。这个文件当然也会作为备份转储文件的一部分归档。这个备份标签文件包含你给予 pg_start_backup
的标签字符串,以及 pg_start_backup
运行的时刻,以及起始 WAL 文件的名字。如果有混淆,那么我们可以看看备份转储文件里面然后判断转储文件来自那个备份会话。
我们还可以在服务器停止的时候制作一个备份转储。在这种条件下,很明显你不能使用 pg_start_backup
或 pg_stop_backup
,并且因此你必须靠自己的手段来跟踪备份转储文件都是那些,以及相关的 WAL 文件最远走到哪里。通常使用上面的在线备份步骤更好些。
好,最糟糕的事情发生了,现在你需要从备份中恢复。下面是步骤:
停止服务器,如果它还在运行的话。
如果你还有足够的空间,把整个集群数据目录和所有表空间拷贝到一个临时位置,以防万一你之后还需要它们。请注意这个预防措施要求你在系统里有足够的剩余空间来现有库的保持两份拷贝。如果你没有足够的空间,那么你至少需要把集群数据目录的 pg_xlog 子目录的内容拷贝到安全的地方,因为它们可能包含系统宕掉的时候还没有归档的日志。
然后清理掉所有在该集群数据目录里的现存文件,以及所有你使用的表空间里根目录下的现存文件。
从你的备份转储中恢复数据库文件。要小心用正确的所有者(数据库系统用户,而不是 root)和权限恢复它们。如果你使用了表空间,你可能需要核实在 pg_tblspc/ 里的符号连接都得到正确恢复。
删除任何目前还在 pg_xlog/ 里的文件;这些文件来自备份转储,因此它们可能比目前的老。如果你就根本没有归档 pg_xlog/ ,那么重建之,要注意也要重建 pg_xlog/archive_status/ 子目录。
如果你有在步骤 2 里面保存的 WAL 段文件,那么把它们拷贝到 pg_xlog/ 。最好是拷贝它们,而不是把它们移动回来,这样即使发生了糟糕的事情,你需要重启的时候,你也依然拥有未修改的文件。
在集群数据目录里创建一个恢复命令文件 recovery.conf(参阅恢复设置)。你可能还需要临时修改 pg_hba.conf 以避免普通用户连接,直到你确信恢复已经正常了为止。
启动服务器。服务器将进入恢复模式并且继续读取它需要的 WAL 归档文件。在遇见外部错误的应当中止恢复过程,然后重新启动服务器,这样它会自动继续进行恢复工作。在恢复过程完成后,服务器将把 recovery.conf 改名为 recovery.done 以避免不小心因后面的崩溃再次进入恢复模式,然后开始正常的数据库操作。
检查数据库的内容以确保你已经恢复到你期望的位置。如果还没有,回到步骤 1 。如果全部正常,则恢复 pg_hba.conf 成正常状态以允许普通用户登录。
所有这些操作的关键是设置一个恢复命令文件,这个文件描述你希望如何恢复以及恢复应该走到哪里。你可以使用 recovery.conf.sample(通常安装在安装目录的 share/ 子目录里)作为原型。你必须在 recovery.conf 里面声明的一个东西是 restore_command ,它告诉系统如何拿回归档的 WAL 文件段。类似 archive_command ,这个是一个脚本命令字符串。它可以包含 %f ,这个变量会被需要的日志文件名替换,以及 %p ,它会被要拷贝去的日志文件的绝对路径代替。如果需要在命令里替换真正的 % 字符,那么就双写(%%)。最简单的有用命令是类似下面的东西
restore_command = 'cp /mnt/server/archivedir/%f %p'
这个命令将把以前归档的 WAL 段从 /mnt/server/archivedir 目录拷贝过来。你当然可以使用某些更复杂的东西,甚至是一个要求操作者挂载合适的磁带的 shell 脚本。
重要的一点是:该命令在失败的时候返回非零值。如果日志文件没有出现在规档中,那么该系统将询问该命令;在问到的时候,它必须返回非零。这个不是错误条件。还要注意 %p 路径的基础名将和 %f 不一样;不要认为它们是可以互换的。
在归档中找不到的 WAL 段将被认为在 pg_xlog/ 里;这样就允许使用最近没有归档的段。但是在归档中的段将比 pg_xlog/ 中的优先。在检索归档文件的时候,系统将不会覆盖现有的 pg_xlog/ 内容。
通常,恢复将处理所有可用的 WAL 段,因此将把数据库恢复到当前时间(或者是在所给出的可用 WAL 段数的情况下,我们能走到的最近的地方)。但是如果你想恢复到某些以前的时刻点(比如,在菜鸟 DBA 删除你的主要事务表之前),那么只需要在 recovery.conf 里声明要求的停止点。你可以通过日期/时间来声明,也可以通过特定事务 ID 的结束来声明这个停止点,我们叫做"恢复目标"。目前,只有日期/时间选项比较有用,因为我们没有工具来帮助你精确地标识应该使用哪个事务 ID 。
【注意】请注意停止点必须在备份的终止时间之后(也就是
pg_stop_backup
的时间)。你无法使用一个基础备份恢复到备份正在进行中的某个时刻。要想恢复到该时刻,你必须回到你以前的基础备份,然后从那个位置向前滚动。
如果在恢复过程中发现在 WAL 数据中存在错误,那么恢复将在错误的地方停止,并且不会启动服务器。在这种情况下,可以指定一个位于错误点之前的"恢复目标",然后从起始点开始重新运行恢复进程,这样恢复就可以正常完成。如果由于外部原因(系统崩溃、无法读取 WAL 归档)导致恢复失败,那么可以简单的重新启动恢复过程即可,它将从上次失败的地方继续。重新启动恢复过程与检查点的操作非常类似:服务器周期性的强制将其状态记录到磁盘上并更新 pg_control 文件以标识已经处理的 WAL 数据不需要被再次扫描。
这些设置只能在 recovery.conf 里面使用,并且只是在恢复的过程中起作用。在任何之后的恢复中,你必须重新设置他们。恢复过程开始后,它们的值无法改变。
执行检索归档 WAL 文件段序列的 shell 命令。这个参数是必须的。字符串中的任何 %f 都被从归档中检索出来的文件名替换,而任何 %p 都被替换为拷贝过去的服务器上的路径(相对于服务器工作目录,也就是集群的数据目录)。需要在命令里嵌入真正的 % 字符是必须双写(%%)。
有一点很重要,那就是这个命令只有在成功的时候才返回零。系统会向这条命令询问没有在归档里出现的文件名;在这种情况下,它必须返回非零。比如:
restore_command = 'cp /mnt/server/archivedir/%f "%p"' restore_command = 'copy /mnt/server/archivedir/%f "%p"' # Windows
这个参数声明恢复执行到达的时间戳。最多可以声明一个 recovery_target_time 和 recovery_target_xid 。缺省是恢复到 WAL 日志的结尾。精确的停止点也受 recovery_target_inclusive 影响。
这个参数声明恢复将到达的事务 ID 。要注意的是,尽管事务 ID 在事务开始的时候是认为顺序的,但是事务可以以不同的数值顺序完成。将要恢复的事务是那些在这个已声明事务之前(可以选择包括它提交的时候)提交的事务。最多可以声明 recovery_target_xid 和 recovery_target_time 之一。缺省是恢复到 WAL 日志的结尾。精确的停止点也受 recovery_target_inclusive 影响。
声明是在恢复目标之后(true)还是之前(false)停止。同时适用于 recovery_target_time 和 recovery_target_xid ,而不管实际声明的是哪个。它分别表示是否在恢复中包含具有准确提交时间或者事务 ID 的那些(个)事务。缺省是 true 。
声明恢复到一个特定的时间线。缺省是恢复到进行基础备份时的时间线上。只是在复杂的重新恢复的情况下,你才需要设置这个参数,也就是在你需要恢复到一个本身是在即时恢复之后到达的状态下,才需要这么做。参阅节23.3.4。
能够把数据库恢复到以前的某个时间点的能力导致了一些类似科幻小说里的时间跟踪和并行宇宙这样的复杂情况。在数据库最初的历史里,可能你在周二下午 5:15 删除掉了一个非常关键的表。然后有条不紊地拿出备份,恢复到周二晚上 5:14 的即时备份。在这个数据库宇宙的历史里,你从来没有删除过那个表。但是假如你后来认识到这么干是错误的,并且想回到最初的历史中的稍后的点。你没法这么干,因为在数据库运行的时候,它覆盖了一些 WAL 段文件的序列,这些序列就在你希望回去的区间里。因此你的确需要区分在你从那些原始数据库历史生成的 WAL 中完成即时恢复之后生成的 WAL 序列。
为了处理这些问题,PostgreSQL 有个叫时间线的概念。每次你即时恢复到一个比 WAL 序列结尾更早的时刻,那么就创建一个新的时间线,以表示在该次恢复之后生成的 WAL 记录。不过,如果恢复动作一直处理到 WAL 结尾,就不会开始一个新的时间线而只是扩展现有个那个。时间线 ID 号是 WAL 段文件名的一部分,因此新的时间线并不会覆盖以前的时间线生成的 WAL 数据。实际上我们可以归档许多不同的时间线。虽然这些看起来像没用的特性,但它却可能常常是救命稻草。考虑一下你并不很确信应该恢复到那个时刻的情况,这个时候你不得不做好几次试验性即时恢复然后从中找到旧历史中最好的分支。如果没有时间线,那么这个过程可能很快就会导致无法管理的混乱。有了时间线,你可以恢复到任意以前的状态,包括恢复到你后来放弃的时间线分支的状态。
每当创建一个新的时间线的时候,PostgreSQL 都创建一个"时间线历史"文件,它显示自己从哪个时间线分出来,以及何时分出来的。这些历史文件是在从包含多个时间线的归档中进行恢复时,允许系统选取正确 WAL 段文件的必要文件。因此,它们像 WAL 段文件一样归档到 WAL 归档里。历史文件只是很小的文本文件(不像段文件很大),所以独立地保存他们代价很小,也值得做。如果你喜欢,你可以在历史文件里加入注释,记录自己为什么设置这个时间线以及如何设置的等信息。这样的注释会在你有厚厚一堆不同的时间线需要选择和分析的时候特别有价值。
恢复的缺省的行为是沿着与基础备份的同一个时间线恢复。如果你想恢复到某些子时间线,也就是,你想回到某些本身就是在开始恢复之后发生的状态。你需要在 recovery.conf 里声明目标时间线 ID 。你无法恢复到比基础备份更早的时间线分支。
目前,在线备份技术还有几个局限。它们可能在将来的版本中修补:
在 Hash 索引上的操作目前没有使用 WAL 记录日志,所以重放就不会更新这些索引类型。我们建议的绕开方法是在完成恢复操作之后手工 REINDEX 每个这样的索引。
如果在进行数据库备份的时候发出一个 CREATE DATABASE 命令,然后在这个过程中 CREATE DATABASE 命令拷贝的模板数据库被修改了,那么用这个备份进行恢复的数据库很有可能导致这些修改也传播到新创建的数据库中去。这个行为当然是不愿意看到的。为了避免这个风险,最好在进行数据库备份的时候不要修改任何模板数据库。
CREATE TABLESPACE 命令是用文本的绝对路径记录 WAL 日志的,因此会以相同的绝对路径重新创建。如果日志是在另外一台机器上重放,那么这个行为可能不是我们想要的。即使在同一台机器,但是在一个新的数据目录里重放日志,都很可能是危险的:重放仍将会覆盖原来的表空间的内容。为了避免这类的潜在问题,最好的方法是在创建或者删除表空间之后进行一次新的基础备份。
还要注意,缺省的 WAL 格式体积相当大,因为它包含许多磁盘页快照。这些磁盘页快照是设计来支持崩溃恢复的,因为我们可能需要修补部分写入的磁盘页。根据你的系统硬件和软件的不同,这种部分写入的危险可能是微乎其微的。这种情况下,你可以通过使用 full_page_writes 关闭磁盘页面快照,从而大大减少归档日志的总尺寸(在你这么做之前,阅读章27里面的注意和警告)。关闭页面快照并不阻止日志使用 PITR 操作。一个将来需要开发的功能是在 full_page_writes 打开的时候,通过删除不需要的磁盘页拷贝来压缩归档的 WAL 数据。同时,管理员可以通过尽量合理地增加检查点的时间间隔来减少包含在 WAL 里的页面快照。