PostgreSQL服务器管理:备份和恢复

本文档为PostgreSQL 9.6.0文档,本转载已得到原译者彭煜玮授权。

1. SQL转储

SQL 转储方法的思想是创建一个由SQL命令组成的文件,当把这个文件回馈给服务器时,服务器将利用其中的SQL命令重建与转储时状态一样的数据库。 PostgreSQL为此提供了工具pg_dump。这个工具的基本用法是:


pg_dump dbname > outfile

正如你所见,pg_dump把结果输出到标准输出。我们后面将看到这样做有什么用处。 尽管上述命令会创建一个文本文件,pg_dump可以用其他格式创建文件以支持并行 和细粒度的对象恢复控制。

pg_dump是一个普通的PostgreSQL客户端应用(尽管是个 相当聪明的东西)。这就意味着你可以在任何可以访问该数据库的远端主机上进行备份工作。但是请记住 pg_dump不会以任何特殊权限运行。具体说来,就是它必须要有你想备份的表的读 权限,因此为了备份整个数据库你几乎总是必须以一个数据库超级用户来运行它(如果你没有足够的特权 来备份整个数据库,你仍然可以使用诸如-n schema 或-t table选项来备份该数据库中你能够 访问的部分)。

要声明pg_dump连接哪个数据库服务器,使用命令行选项-hhost和 -p port。 默认主机是本地主机或你的PGHOST环境变量指定的主机。 类似地,默认端口是环境变量PGPORT或(如果PGPORT不存在)内建的默认值。 (服务器通常有相同的默认值,所以还算方便。)

和任何其他PostgreSQL客户端应用一样, pg_dump默认使用与当前操作系统用户名同名的数据库用户名进行连接。 要使用其他名字,要么声明-U选项,要么设置环境变量PGUSER。请注意pg_dump的连接也要通过客户认证机制(在Chapter 20里描述)。

pg_dump对于其他备份方法的一个重要优势是,pg_dump的输出可以很容易地在新版本的PostgreSQL中载入,而文件级备份和连续归档都是极度的服务器版本限定的。pg_dump也是唯一可以将一个数据库传送到一个不同机器架构上的方法,例如从一个32位服务器到一个64位服务器。

由pg_dump创建的备份在内部是一致的, 也就是说,转储表现了pg_dump开始运行时刻的数据库快照,且在pg_dump运行过程中发生的更新将不会被转储。pg_dump工作的时候并不阻塞其他的对数据库的操作。 (但是会阻塞那些需要排它锁的操作,比如大部分形式的ALTER TABLE)

1.1. 从转储中恢复

pg_dump生成的文本文件可以由psql程序读取。 从转储中恢复的常用命令是:


psql dbname < infile

其中infile就是pg_dump命令的输出文件。这条命令不会创建数据库dbname,你必须在执行psql前自己从template0创建(例如,用命令createdb -T template0 dbname)。psql支持类似pg_dump的选项用以指定要连接的数据库服务器和要使用的用户名。 参阅psql的手册获取更多信息。 非文本文件转储可以使用pg_restore工具来恢复。

在开始恢复之前,转储库中对象的拥有者以及在其上被授予了权限的用户必须已经存在。如果它们不存在,那么恢复过程将无法将对象创建成具有原来的所属关系以及权限(有时候这就是你所需要的,但通常不是)。

默认情况下,psql脚本在遇到一个SQL错误后会继续执行。你也许希望在遇到一个SQL错误后让psql退出,那么可以设置ON_ERROR_STOP变量来运行psql,这将使psql在遇到SQL错误后退出并返回状态3:


psql --set ON_ERROR_STOP=on dbname < infile

不管怎样,你将只能得到一个部分恢复的数据库。作为另一种选择,你可以指定让整个恢复作为一个单独的事务运行,这样恢复要么完全完成要么完全回滚。这种模式可以通过向psql传递-1或--single-transaction命令行选项来指定。在使用这种模式时,注意即使是很小的一个错误也会导致运行了数小时的恢复被回滚。但是,这仍然比在一个部分恢复后手工清理复杂的数据库要更好。

pg_dump和psql读写管道的能力使得直接从一个服务器转储一个数据库到另一个服务器成为可能,例如:


pg_dump -h host1 dbname | psql -h host2 dbname

Important: pg_dump产生的转储是相对于template0。这意味着在template1中加入的任何语言、过程等都会被pg_dump转储。结果是,如果在恢复时使用的是一个自定义的template1,你必须从template0创建一个空的数据库,正如上面的例子所示。

一旦完成恢复,在每个数据库上运行ANALYZE是明智的举动,这样优化器就有有用的统计数据了。

1.2. 使用pg_dumpall

pg_dump每次只转储一个数据库,而且它不会转储关于角色或表空间(因为它们是集簇范围的)的信息。为了支持方便地转储一个数据库集簇的全部内容,提供了pg_dumpall程序。pg_dumpall备份一个给定集簇中的每一个数据库,并且也保留了集簇范围的数据,如角色和表空间定义。该命令的基本用法是:


pg_dumpall > outfile

转储的结果可以使用psql恢复:


psql -f infile postgres

(实际上,你可以指定恢复到任何已有数据库名,但是如果你正在将转储载入到一个空集簇中则通常要用(postgres)。在恢复一个pg_dumpall转储时常常需要具有数据库超级用户访问权限,因为它需要恢复角色和表空间信息。如果你在使用表空间,请确保转储中的表空间路径适合于新的安装。

pg_dumpall工作时会发出命令重新创建角色、表空间和空数据库,接着为每一个数据库pg_dump。这意味着每个数据库自身是一致的,但是不同数据库的快照并不同步。

集簇范围的数据可以使用pg_dumpall的--globals-only选项来单独转储。如果在单个数据库上运行pg_dump命令,上述做法对于完全备份整个集簇是必需的。

1.3. 处理大型数据库

在一些具有最大文件尺寸限制的操作系统上创建大型的pg_dump输出文件可能会出现问题。幸运地是,pg_dump可以写出到标准输出,因此你可以使用标准Unix工具来处理这种潜在的问题。有几种可能的方法:

使用压缩转储。. 你可以使用你喜欢的压缩程序,例如gzip:


pg_dump dbname | gzip > filename.gz

恢复:


gunzip -c filename.gz | psql dbname

或者:


cat filename.gz | gunzip | psql dbname

使用split。. split命令允许你将输出分割成较小的文件以便能够适应底层文件系统的尺寸要求。例如,让每一块的大小为1兆字节:


pg_dump dbname | split -b 1m - filename

恢复:


cat filename* | psql dbname

使用pg_dump的自定义转储格式。. 如果PostgreSQL所在的系统上安装了zlib压缩库,自定义转储格式将在写出数据到输出文件时对其压缩。这将产生和使用gzip时差不多大小的转储文件,但是这种方式的一个优势是其中的表可以被有选择地恢复。下面的命令使用自定义转储格式来转储一个数据库:


pg_dump -Fc dbname > filename

自定义格式的转储不是psql的脚本,只能通过pg_restore恢复,例如:


pg_restore -d dbname filename

对于非常大型的数据库,你可能需要将split配合其他两种方法之一进行使用。

使用pg_dump的并行转储特性。. 为了加快转储一个大型数据库的速度,你可以使用pg_dump的并行模式。它将同时转储多个表。你可以使用-j参数控制并行度。并行转储只支持“目录”归档格式。


pg_dump -j num -F d -f out.dir dbname

你可以使用pg_restore -j来以并行方式恢复一个转储。它只能适合于“自定义”归档或者“目录”归档,但不管归档是否由pg_dump -j创建。

2. 文件系统级别备份

另外一种备份策略是直接复制PostgreSQL用于存储数据库中数据的文件,Section 18.2解释了这些文件的位置。你可以采用任何你喜欢的方式进行文件系统备份,例如:


tar -cf backup.tar /usr/local/pgsql/data

但是这种方法有两个限制,使得这种方法不实用,或者说至少比pg_dump方法差:

1.为了得到一个可用的备份,数据库服务器必须被关闭。例如阻止所有连接的半路措施是不起作用的(部分原因是tar和类似工具无法得到文件系统状态的一个原子的快照,还有服务器内部缓冲的原因)。关于停止服务器的信息可以在Section 18.5中找到。不用说,在恢复数据之前你也需要关闭服务器。

2.如果你已经深入地了解了数据库的文件系统布局的细节,你可能会有兴趣尝试通过相应的文件或目录来备份或恢复特定的表或数据库。这种方法也不会起作用,因为包含在这些文件中的信息只有配合提交日志文件(pg_clog/*)才有用,提交日志文件包含了所有事务的提交状态。一个表文件只有和这些信息一起才有用。当然也不可能只恢复一个表及相关的pg_clog数据,因为这会导致数据库集簇中所有其他表变得无用。因此文件系统备份值适合于完整地备份或恢复整个数据库集簇。

另一种文件系统备份方法是创建一个数据目录的"一致快照",如果文件系统支持此功能(并且你相信它的实现正确)。典型的过程是创建一个包含数据库的卷的"冻结快照",然后从该快照复制整个数据目录(如上,不能是部分复制)到备份设备,最后释放冻结快照。即使在数据库服务器运行时,这种方式也有效。但是,以这种方式创建的备份保存的文件看起来就像数据库没有被正确关闭时的状态。因此,当你从备份数据上启动数据库服务器时,它会认为上一次的服务器实例崩溃了并尝试重放WAL日志。这不是问题,只是需要注意(当然WAL文件必须要包括在备份中)。你可以在拍摄快照之前执行一次CHECKPOINT以便节省恢复时间。

如果你的数据库跨越多个文件系统,可能没有任何方式可以对所有卷获得完全同步的冻结快照。例如,如果你的数据文件和WAL日志放置在不同的磁盘上,或者表空间在不同的文件系统中,可能没有办法使用快照备份,因为快照必须是同步的。在这些情况下,一定要仔细阅读你的文件系统文档以了解其对一致快照技术的支持。

如果没有可能获得同步快照,一种选择是将数据库服务器关闭足够长的时间以建立所有的冻结快照。另一种选择是执行一次连续归档基础备份(Section 25.3.2),因为这种备份对于备份期间发生的文件系统改变是免疫的。这要求在备份过程中允许连续归档,恢复时使用连续归档恢复。

还有一种选择是使用rsync来执行一次文件系统备份。其做法是先在数据库服务器运行时执行rsync,然后关闭数据库服务器足够长时间来做一次rsync --checksum (--checksum是必需的,因为rsync的文件修改 时间粒度只能精确到秒)。第二次rsync会比第一次快,因为它只需要传送相对很少的数据,由于服务器是停止的,所以最终结果将是一致的。这种方法允许在最小停机时间内执行一次文件系统备份。

注意一个文件系统备份通常会比一个SQL转储体积更大(例如pg_dump不需要转储索引的内容,而是转储用于重建索引的命令)。但是,做一次文件系统备份可能更快。

3. 连续归档和时间点恢复(PITR)

在任何时间,PostgreSQL在数据集簇目录的pg_xlog/子目录下都保持有一个预写式日志(WAL)。这个日志存在的目的是为了保证崩溃后的安全:如果系统崩溃,可以"重放"从最后一次检查点以来的日志项来恢复数据库的一致性。该日志的存在也使得第三种备份数据库的策略变得可能:我们可以把一个文件系统级别的备份和WAL文件的备份结合起来。当需要恢复时,我们先恢复文件系统备份,然后从备份的WAL文件中重放来把系统带到一个当前状态。这种方法比之前的方法管理起来要更复杂,但是有其显著的优点:

  • 我们不需要一个完美的一致的文件系统备份作为开始点。备份中的任何内部不一致性将通过日志重放(这和崩溃恢复期间发生的并无显著不同)来修正。因此我们不需要文件系统快照功能,只需要tar或一个类似的归档工具。
  • 由于我们可以结合一个无穷长的WAL文件序列用于重放,可以通过简单地归档WAL文件来达到连续备份。这对于大型数据库特别有用,因为在其中不方便频繁地进行完全备份。
  • 并不需要一直重放WAL项一直到最后。我们可以在任何点停止重放,并得到一个数据库在当时的一致快照。这样,该技术支持时间点恢复:在得到你的基础备份以后,可以将数据库恢复到它在其后任何时间的状态。
  • 如果我们连续地将一系列WAL文件输送给另一台已经载入了相同基础备份文件的机器,我们就得到了一个热备份系统:在任何时间点我们都能提出第二台机器,它差不多是数据库的当前副本。

Note:
pg_dump和pg_dumpall不会产生文件系统级别的备份,并且不能用于连续归档方案。这类转储是逻辑的并且不包含足够的信息用于WAL重放。

就简单的文件系统备份技术来说,这种方法只能支持整个数据库集簇的恢复,却无法支持其中一个子集的恢复。另外,它需要大量的归档存储:一个基础备份的体积可能很庞大,并且一个繁忙的系统将会产生大量需要被归档的WAL流量。尽管如此,在很多需要高可靠性的情况下,它是首选的备份技术。

要使用连续归档(也被很多数据库厂商称为"在线备份")成功地恢复,你需要一个从基础备份时间开始的连续的归档WAL文件序列。为了开始,在你建立第一个基础备份之前,你应该建立并测试用于归档WAL文件的过程。对应地,我们首先讨论归档WAL文件的机制。

3.1. 建立WAL归档

抽象地来说,一个运行中的PostgreSQL系统产生一个无穷长的WAL记录序列。系统从物理上将这个序列划分成WAL 段文件,通常是每个16MB(段尺寸在构建PostgreSQL时可修改)。段文件会被分配一个数字名称以便反映它在整个抽象WAL序列中的位置。在没有使用WAL归档时,系统通常只创建少量段文件,并且通过重命名不再使用的段文件为更高的段编号来"回收"它们。系统假设内容位于最后一个检查点之前的段文件是无用的且可以被回收。

在归档WAL数据时,我们需要在每一段被填充满时捕捉其内容,并且在段文件被回收重用之前保存该数据。依靠应用和可用的硬件,有很多不同的方法来"保存数据":我们可以将段文件拷贝到一个已挂载的位于另一台机器上的NFS目录,或者将它们写出到一个磁带驱动器(确保你有办法标识每个文件的原始文件名),或者将它们批量烧录到CD上,或者其他什么方法。为了向数据库管理员提供灵活性,PostgreSQL不对如何归档做任何假设。取而代之的是,PostgreSQL让管理员声明一个shell命令来拷贝一个完整的段文件到它需要去的地方。 该命令可以简单得就是一个cp,或者它可以调用一个复杂的 shell 脚本 — 所有都由你决定。

要启用WAL归档,需设置wal_level配置参数为replica或更高,设置archive_mode为on,并且使用archive_command配置参数指定一个shell命令。实际上,这些设置总是被放置在postgresql.conf文件中。在archive_command中,%p会被将要归档的文件路径所替代,而%f只会被文件名所替代(路径名是相对于当前工作目录而言的,即集簇的数据目录)。如果你需要在命令中嵌入一个真正的%字符,可以使用%%。最简单的命令类似于:


archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'  # Unix
archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"'  # Windows

它将把 WAL 段拷贝到目录/mnt/server/archivedir(这个只是一个例子,并非我们建议的方法,可能不能在所有系统上都正确运行)。在%p和%f参数被替换之后,实际被执行的命令看起来可能是:


test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_xlog/00000001000000A900000065 /mnt/server/archivedir/00000001000000A900000065

对每一个将要被归档的新文件都会生成一个类似的命令。

归档命令将在运行PostgreSQL服务器的同一个用户的权限下执行。因为被归档的一系列WAL 文件实际上包含你的数据库里的所有东西,所以你应该确保自己的归档数据不会被别人窥探; 比如,归档到一个没有组或者全局读权限的目录里。

有一点很重要:当且仅当归档命令成功时,它才返回零退出。在得到一个零值结果之后,PostgreSQL将假设该文件已经成功归档, 因此它稍后将被删除或者被新的数据覆盖。但是,一个非零值告诉PostgreSQL该文件没有被归档; 因此它会周期性的重试直到成功。

归档命令通常应该被设计成拒绝覆盖已经存在的归档文件。这是一个非常重要的安全特性, 可以在管理员操作失误(比如把两个不同的服务器的输出发送到同一个归档目录)的时候保持你的归档的完整性。

我们建议你首先要测试你准备使用到归档命令,以保证它实际上不会覆盖现有的文件, 并且在这种情况下它返回非零状态。以上Unix中的命令例子通过包含一个独立的test步骤来保证这一点。在某些Unix平台上,cp具有诸如-i的开关,可用来更简洁地完成这一切,但是在没有验证返回的退出状态正确之前你不能依赖它们(特别地,GNU的cp在使用-i时将对已存在的目标文件返回状态零,这并不是我们所期望的行为)。

在设计你的归档环境时,请考虑一下如果归档命令不停失败会发生什么情况, 因为有些情况要求操作者的干涉,或者是归档空间不够了。 例如,如果你往磁带机上写,但是没有自动换带机,那么就有可能发生这种情况; 如果磁带满了,除非换磁带,否则任何事也做不了。 你应该确保任何错误情况或者任何要求操作员干涉的情况都会被正确报告, 这样才能迅速解决这些问题。否则pg_xlog/目录会不停地被WAL段文件填充, 直到问题解决(如果包含pg_xlog/的文件系统被填满,PostgreSQL将会做一次致命关闭。不会有未提交事务被丢失,但是数据库将会保持离线直到你释放一部分空间)。

归档命令的速度并不要紧,只要它能跟上你的服务器生成 WAL 数据的平均速度即可。即使归档进程稍微落后,正常的操作也会继续进行。 如果归档进程慢很多,就会增加灾难发生的时候丢失的数据量。这同时也意味着pg_xlog/目录包含大量未归档的段文件, 并且可能最后超出了可用磁盘空间。我们建议你监控归档进程,确保它是按照你的期望运转的。

在写自己的归档命令的时候,你应该假设被归档的文件名最长为64个字符并且可以包含 ASCII 字母、数字以及点的任意组合。 我们不需要保持原始的相对路径(%p),但是有必要保持文件名(%f)。

请注意尽管 WAL 归档允许你恢复任何对你的PostgreSQL数据库中数据所做的修改, 但它不会恢复对配置文件的修改(即postgresql.conf、pg_hba.conf 和 pg_ident.conf),因为这些文件都是手工编辑的,而不是通过 SQL 操作来编辑的。 所以你可能会需要把你的配置文件放在一个日常文件系统备份过程可处理的位置。

归档命令只会为完成的WAL段调用。因此如果你的服务器产生了一点点WAL流量(或者在产生时有宽松的周期),从一个事务完成到它被安全地记录在归档存储中之间将会有较长的延迟。要为未归档数据设置一个年龄限制,你可以设置archive_timeout来强制要求服务器按照其设定的频度切换到一个新的WAL段。注意由于强制切换而被归档的文件还是具有和完全归档的文件相同的长度。因此设置一个很短的archive_timeout是很不明智的 — 它会膨胀你的归档存储。将archive_timeout设置为1分钟左右通常是合理的。

同样,如果你希望确保一个刚刚完成的事务能被尽快归档,可以使用pg_switch_xlog进行一次手动段切换。其他与WAL管理相关的使用函数在Table 9-78中列出。

当wal_level为minimal时,一些SQL命令被优化为避免记录WAL日志。在这些语句的其中之一的执行过程中如果打开了归档或流复制,WAL中将不会包含足够的信息用于归档恢(崩溃恢复不受影响)。出于这个原因,wal_level只能在服务器启动时修改。但是,archive_command可以通过重载配置文件来修改。如果你希望暂时停止归档,一种方式是将archive_command设置为空串('')。这将导致WAL文件积累在pg_xlog/中,直到一个可用的archive_command被重新建立。

3.2. 制作一个基础备份

执行一次基础备份最简单的方法是使用pg_basebackup工具。它将会以普通文件或一个tar归档的方式创建一个基础备份。如果需要比pg_basebackup更高的灵活性,你也可以使用低级API来制作一个基础备份。

没有必要关心创建一个基础备份所需的时间。但是,如果你正常地运行停用了full_page_writes的服务器,你可能会注意到备份运行时的性能下降,因为full_page_writes在备份模式期间会被实际强制实施。

要使用备份,你将需要保留所有在文件系统备份期间及之后生成的WAL段文件。为了便于你做这些,基础备份过程会创建一个备份历史文件,它将被立刻存储到WAL归档区域。该文件以文件系统备份中你需要的第一个WAL段文件命名。例如,如果开始的WAL文件是0000000100001234000055CD,则备份历史文件将被命名为0000000100001234000055CD.007C9330.backup.(文件名的第二部分表明WAL文件中的一个准确位置,一般可以被忽略)。一旦你已经安全地归档了文件系统备份和在备份过程中被使用的WAL段文件(如备份历史文件中所指定的) ,所有名字在数字上低于备份历史文件中记录值的已归档WAL段对于恢复文件系统备份就不再需要了,并且可以被删除。但是你应该考虑保持多个备份集以绝对保证你能恢复你的数据。

备份历史文件是一个很小的文本文件。它包含你指定给pg_basebackup的标签字符串,以及备份的起止时间以及起止WAL段。如果你使用该标签来标识相关转储文件,则已归档的历史文件足以说明需要哪个转储文件进行恢复。

由于你不得不保存最后一次基础备份之后的所有归档WAL文件,基础备份之间的间隔通常应该根据你希望在归档WAL文件上花费的存储空间来设定。你也应该考虑你准备花多长时间来进行恢复,如果需要恢复 — 系统将不得不重放所有那些WAL段,如果这些WAL段覆盖了最后一次基础备份以后的很长时间,重放过程将会花费一些时间。

3.3. 使用低级API制作一个基础备份

使用低级API制作一个基础备份的过程比pg_basebackup方法要包含更多的步骤,但相对要更简单。很重要的一点是,这些步骤要按照顺序执行,并且在执行下一步之前要验证上一步是否成功。

可以用非排他或者排他的方法来制作低级基础备份。我们推荐非排他方法,而排他 的方法已经被废弃并且最终将被去除。

3.3.1. 制作一个非排他低级备份

非排他低级备份允许其他并发备份运行(既包括那些使用同样的 备份 API 开始的备份,也包括那些使用 pg_basebackup开始的备份)。

1.确保WAL归档被启用且正在工作。

2.作为一个具有运行 pg_start_backup 权利的用户(超级用户,或者被授予在该 函数上 EXECUTE 的用户)连接到服务器(不在乎是哪个数据库)并且发出命令:


SELECT pg_start_backup('label', false, false);

其中label是用来唯一标识这次备份操作的任意字符串。调用 pg_start_backup的连接必须被保持到备份结束,否则备份 将被自动中止。

默认情况下,pg_start_backup可能需要较长的时间完成。 这是因为它会执行一个检查点,并且该检查点所需要的 I/O 将会分散到一段 显著的时间上,默认情况下是你的检查点间隔(见配置参数 checkpoint_completion_target)的一半。这通常 是你所想要的,因为它可以最小化对查询处理的影响。如果你想要尽可能快地 开始备份,请把第二个参数改成true。

第三个参数为false会告诉pg_start_backup 开始一次非排他基础备份。

3.使用任何趁手的文件系统备份工具(例如tar或者 cpio,不是pg_dump 或者pg_dumpall)执行备份。当你做这些 时,不需要也不值得停止正常的数据库操作。

4.在同一个连接中,发出命令:


SELECT * FROM pg_stop_backup(false);

这会终止备份模式并且执行一次自动切换到下一个 WAL 段。要做切换的 原因是让在备份期间写入的最后一个 WAL 段文件能准备好被归档。

pg_stop_backup将返回一个具有三个值的行。这些域的 第二个应该被写入到该备份根目录中名为backup_label的 文件。第三个域应该被写入到一个名为tablespace_map 的文件,除非该域为空。这些文件对该备份正常工作来说是至关重要的, 不能被随意修改。

5.一旦备份期间活动的 WAL 段文件被归档,备份就完成了。由 pg_stop_backup的第一个返回值标识的文件是构成一个 完整备份文件集合所需的最后一个段。如果archive_mode 被启用,直到最后一个段被归档前pg_stop_backup都不会 返回。从你已经配置好archive_command之后这些文件的 归档就会自动发生。在大部分情况下,这些归档会很快发生,但是建议你监 控你的归档系统确保没有延迟。如果归档进程由于归档命令的失败而落后, 它将会持续重试知道归档成功并且备份完成。如果你希望对 pg_stop_backup的执行给出一个时间限制,可以设置一个 合适的statement_timeout值,但要注意如果 pg_stop_backup因此而中止会致使备份可能失效。

3.3.2. 制作一个排他低级备份

一个排他备份的处理绝大部分都和非排他备份相同,但是在一些关键步骤上 不同。它不允许超过一个并发备份运行,并且如果服务器在备份期间崩溃,会 有一些问题。在 PostgreSQL 9.6 之前,这是唯一可用的低级方法,但是现在 推荐所有用户在可能的情况下升级他们的脚本来使用非排他备份。

1.确保 WAL 归档被启用且正常工作。

2.作为一个具有运行 pg_start_backup 权利的用户(超级用户,或者被授予在该 函数上 EXECUTE 的用户)连接到服务器(不在乎是哪个数据库)并且发出命令:


SELECT pg_start_backup('label');

这里label是任何你希望用来唯一标识这个备份操作的字符串。 pg_start_backup在集簇目录中创建一个关于备份信息的 备份标签文件,也被称为backup_label, 其中包括了开始时间和标签字符串。该函数也会在集簇目录中创建一个 名为tablespace_map的表空间映射文件, 如果在pg_tblspc/中有一个或者多个表空间符号链接存在, 该文件会包含它们的信息。如果你需要从备份中恢复,这两个文件对于备份的 完整性都至关重要。

默认情况下,pg_start_backup会花费很长时间来完成。这是因为它会执行一个检查点,而检查点所需要的I/O在相当一段时间内将会被传播,默认情况下这段时间是内部检查点间隔的一半(参见配置参数checkpoint_completion_target)。这通常是你所希望的,因为它能将对查询处理的影响最小化。如果你要尽快开始备份,可使用:


SELECT pg_start_backup('label', true);

这会使检查点尽可能快地被完成。

3.使用任何方便的文件系统备份工具执行备份,例如tar 或cpio(不是pg_dump 或pg_dumpall)。在此期间,不需要也 不值得停止正常的数据库操作。在备份期间要考虑的事情可见 Section 25.3.3.3小节。

4.再次以具有运行 pg_stop_backup 权利的用户(超级用户,或者已经被授予 该函数上 EXECUTE 的用户)连接到数据库并且发出命令:


SELECT pg_stop_backup();

这将终止备份模式,并且执行一个自动切换到下一个WAL段。进行切换的原因是将在备份期间生成的最新WAL段文件安排为可归档。

5.一旦备份期间活动的WAL段文件被归档,你的工作就完成了。 pg_stop_backup的结果所标识的文件是构成一个完整备份 文件组所需的最新段。如果archive_mode被启用,直到最 新段被归档pg_stop_backup都不会返回。由于你已经配置了 archive_command,这些文件的归档过程会自动发生。在 大部分情况下这会很快发生,但还是建议你监控你的归档系统来确保不会有 延迟。如果归档处理由于归档命令的错误而延迟,它会保持重试直到归档成 功和备份完成。如果你希望在pg_stop_backup的执行上 设置一个时间限制,可对statement_timeout设 置一个合适的值,但要注意如果pg_stop_backup因此而 中止会致使备份可能失效。

3.3.3. 备份数据目录

如果被拷贝的文件在拷贝过程中发生变化,某些文件系统备份工具会发出警告或错误。在建立一个活动数据库的基础备份时,这种情况是正常的,并非一个错误。然而,你需要确保你能够把它们和真正的错误区分开。例如,某些版本的rsync为"消失的源文件"返回一个独立的退出码,且你可以编写一个驱动脚本来将该退出码接受为一种非错误情况。同样,如果一个文件在被tar复制的过程中被截断,某些版本的GNU tar会返回一个与致命错误无法区分的错误代码。幸运的是,如果一个文件在备份期间被改变,版本为1.16及其后的GNU tar将会退出并返回1,而对于其他错误返回2。在版本1.23及其后的GNU tar中,你可以使用警告选项--warning=no-file-changed --warning=no-file-removed来隐藏相关的警告消息。

确认你的备份包含数据库集簇目录(例如/usr/local/pgsql/data)下的所有文件。如果你使用了不在此目录下的表空间,注意也把它们包括在内(并且确保你的备份将符号链接归档为链接,否则恢复过程将破坏你的表空间)。

不过,你应当从备份中忽略集簇的pg_xlog/子目录中的文件。这种微小的调整是值得的,因为它降低了恢复时的错误风险。如果pg_xlog/是一个指向位于集簇目录之外其他地方的符号链接就很容易安排了,这是一种出于性能原因的常见设置。你可能也希望排除postmaster.pid和postmaster.opts,它们记录了关于postmaster运行的信息,但与最终使用这个备份的postmaster无关(这些文件可能会使pg_ctl搞混淆)。

从备份中忽略集簇的pg_replslot/子目录中的文件通常也是个好主意,这样 主控机上存在的复制槽不会成为备份的一部分。否则,后续用该备份创建一个后备机可能会导致该 后备机上的WAL文件被无限期保留,并且在启用了热后备反馈的情况下可能导致主控机膨胀,因为使用 那些复制槽的客户端将继续连接到主控机(而不是后备机)并且继续更新其上的槽。即使该备份是要被 用来创建一个新的主控机,拷贝复制槽也不是特别有用,因为这些槽的内容在新主控机上线时很可能已 经过时。

备份标签文件包含你指定给 pg_start_backup的标签字符串,以及 pg_start_backup被运行的时刻和起始WAL文件的名字。 在发生混乱的情况下就可以在备份文件中查看并准确地决定该转储文件来 自于哪个备份会话。表空间映射文件包括存在于目录pg_tblspc/ 中的符号链接名称以及每一个符号链接的完整路径。这些文件不仅是为了供参考, 它们的存在和内容对于系统恢复过程的正确操作是至关重要。

在服务器停止时也可以创建一个备份。在这种情况下,你显然不能使用 pg_start_backup或pg_stop_backup, 并且因此你只能依靠你的自己的策略来跟踪哪个备份是哪个, 以及相关WAL文件应该走回到什么程度。通常最好遵循上面的连续归档过程。

3.4. 使用一个连续归档备份进行恢复

好,现在最坏的情况发生了,你需要从你的备份进行恢复。这里是其过程:

1.如果服务器仍在运行,停止它。

2.如果你具有足够的空间,将整个集簇数据目录和表空间复制到一个临时位置,稍后你将用到它们。注意这种预防措施将要求在你的系统上有足够的空闲空间来保留现有数据库的两个拷贝。如果你没有足够的空间,你至少要保存集簇的pg_xlog子目录的内容,因为它可能包含在系统垮掉之前还未被归档的日志。

3.移除所有位于集簇数据目录和正在使用的表空间根目录下的文件和子目录。

4.从你的文件系统备份中恢复数据库文件。注意它们要使用正确的所有权恢复(数据库系统用户,不是root!)并且使用正确的权限。如果你在使用表空间,你应该验证pg_tblspc/中的符号链接被正确地恢复。

5.移除pg_xlog/中的任何文件,这些是来自于文件系统备份而不是当前日志,因此可以被忽略。如果你根本没有归档pg_xlog/,那么以正确的权限重建它。注意如果以前它是一个符号链接,请确保你也以同样的方式重建它。

6.如果你有在第2步中保存的未归档WAL段文件,把它们拷贝到pg_xlog/(最好是拷贝而不是移动它们,这样如果在开始恢复后出现问题你任然有未修改的文件)。

7.在集簇数据目录中创建一个恢复命令文件recovery.conf(见Chapter 27)。你可能还想临时修改pg_hba.conf来阻止普通用户在成功恢复之前连接。

8.启动服务器。服务器将会进入到恢复模式并且进而根据需要读取归档WAL文件。恢复可能因为一个外部错误而被终止,可以简单地重新启动服务器,这样它将继续恢复。恢复过程结束后,服务器将把recovery.conf重命名为recovery.done(为了阻止以后意外地重新进入恢复模式),并且开始正常数据库操作。

9.检查数据库的内容来确保你已经恢复到了期望的状态。如果没有,返回到第1步。如果一切正常,通过恢复pg_hba.conf为正常来允许用户连接。

所有这些的关键部分是设置一个恢复配置文件,它描述你希望如何恢复以及恢复要运行到什么程度。你可以使用recovery.conf.sample(通常在安装的share/目录中)作为一个原型。你绝对必须在recovery.conf中指定的是restore_command,它告诉PostgreSQL如何获取归档WAL文件段。与archive_command相似,这也是一个shell命令字符串。它可以包含%f(将被期望的日志文件名替换)和%p(将被日志文件被拷贝的目标路径名替换)。(路径名是相对于当前工作目录的,即集簇的数据目录)。如果你需要在命令中嵌入一个真正的%字符,可以写成%%。最简单的命令类似于:


restore_command = 'cp /mnt/server/archivedir/%f %p'

它将从目录/mnt/server/archivedir中拷贝之前归档的WAL段。当然,你可以使用更复杂的,甚至是一个要求操作者装载合适磁带的shell脚本。

重要的是命令在失败时返回非零退出状态。该命令将被调用来请求不在归档中的文件, 在这种情况下它应该返回非零值。这不是一种错误情况。一种例外是该命令被一个信号(除了被用作数 据库服务器关闭动作一部分的SIGTERM)终止或者被shell的错误 (例如命令未找到)终止,那样恢复将中止并且服务器将不会启动。

并非所有被请求的文件都是WAL段文件,你也许还会请求一些具有.backup或 .history后缀的文件。还要注意的是,%p路径的基本名字将会和%f 不同,但不要期望它们可以互换。

归档中找不到的WAL段可以在pg_xlog/中看到,这使得可以使用最近未归档的段。但是,在归档中可用的段将会被优先于pg_xlog/中的文件被使用。

通常,恢复将会处理完所有可用的WAL段,从而将数据库恢复到当前时间点(或者尽可能接近给定的可 用WAL段)。因此,一个正常的恢复将会以一个"文件未找到"消息结束,错误消息的准确文 本取决于你选择的restore_command。你也可能在恢复的开始看到一个针对名称类 似于00000001.history文件的错误消息。这也是正常的并且不表示在简单恢复情 况中的问题,对此的讨论见Section 25.3.5。

如果你希望恢复到之前的某个时间点(例如,恢复到幼稚的DBA丢弃了你主要的交易表之前),只需要 在recovery.conf中指定要求的停止点。你可以使用日期/时间、命名恢复点或一个 指定事务ID的结束时间来定义停止点(也被称为"恢复目标")。在这种写法中,只有日期/时 间和命名恢复点选项非常有用,因为没有工具可以帮助你准确地确定要用哪个事务ID。

Note:
停止点必须位于基础备份的完成时间之后,即pg_stop_backup的完成时间。在备份过程中你不能使用基础备份来恢复(要恢复到这个时间,你必须回到你之前的基础备份并且从这里开始前滚)。

如果恢复找到被破坏的WAL数据,恢复将会停止于该点并且服务器不会启动。在这种情况下,恢复进程需要从开头重新开始运行,并指定一个在损坏点之前的"恢复目标"以便恢复能够正常完成。如果恢复由于一个外部原因失败,例如一个系统崩溃或者WAL归档变为不可访问,则该次恢复可以被简单地重启并且它将会从几乎是上次失败的地方继续。恢复重启工作起来很像普通操作时的检查点:服务器周期性地强制把它的所有状态写到磁盘中,然后更新pg_control文件来说明已经处理过的WAL数据,这样它们就不会被再次扫描。

3.5. 时间线

将数据库恢复到一个之前的时间点的能力带来了一些复杂性,这和有关时间旅行和平行宇宙的科幻小说有些相似。例如,在数据库的最初历史中,假设你在周二晚上5:15时丢弃了一个关键表,但是一直到周三中午才意识到你的错误。不用苦恼,你取出你的备份,恢复到周二晚上5:14的时间点,并上线运行。在数据库宇宙的这个历史中,你从没有丢弃该表。但是假设你后来意识到这并非一个好主意,并且想回到最初历史中周三早上的某个时间。你没法这样做,在你的数据库在线运行期间,它重写了某些WAL段文件,而这些文件本来可以将你引向你希望回到的时间。因此,为了避免出现这种状况,你需要将完成时间点恢复后生成的WAL记录序列与初始数据库历史中产生的WAL记录序列区分开来。

要解决这个问题,PostgreSQL有一个时间线概念。无论何时当一次归档恢复完成,一个新的时间线被创建来标识恢复之后生成的WAL记录序列。时间线ID号是WAL段文件名的一部分,因此一个新的时间线不会重写由之前的时间线生成的WAL数据。实际上可以归档很多不同的时间线。虽然这可能看起来是一个无用的特性,但是它常常扮演救命稻草的角色。考虑到你不太确定需要恢复到哪个时间点的情况,你可能不得不做多次时间点恢复尝试和错误,直到最终找到从旧历史中分支出去的最佳位置。如果没有时间线,该处理将会很快生成一堆不可管理的混乱。而有了时间线,你可以恢复到任何之前的状态,包括早先被你放弃的时间线分支中的状态。

每次当一个新的时间线被创建,PostgreSQL会创建一个"时间线历史"文件,它显示了新时间线是什么时候从哪个时间线分支出来的。系统在从一个包含多个时间线的归档中恢复时,这些历史文件对于允许系统选取正确的WAL段文件非常必要。因此,和WAL段文件相似,它们也要被归档到WAL归档区域。历史文件是很小的文本文件,因此将它们无限期地保存起来的代价很小,而且也是很合适的(而段文件都很大)。如果你喜欢,你可以在一个历史文件中增加注释来记录如何和为什么要创建该时间线。当你由于试验的结果拥有了一大堆错综复杂的不同时间线时,这种注释将会特别有价值。

恢复的默认行为是沿着相同的时间线进行恢复,该时间线是基础备份创建时的当前时间线。如果你希望恢复到某个子女时间线(即,你希望回到在一次恢复尝试后产生的某个状态),你需要在recovery.conf中指定目标时间线ID。你不能恢复到早于该基础备份之前分支出去的时间线。

3.6. 建议和例子

这里将给出一些配置连续归档的建议。

3.6.1. 单机热备份

可以使用PostgreSQL的备份功能来产生单机热备份。这些备份不能被用于时间点恢复,然而备份和恢复时要比使用pg_dump转储更快(它们也比pg_dump转储更大,所以在某些情况下速度优势可能会被否定)。

在基础备份的帮助下,产生一个单机热备份最简单的方式是使用pg_basebackup工具。如果你在调用它时使用了-X参数,使用该备份所需的所有事务日志将会被自动包含在该备份中,并且恢复该备份也不需要特殊的动作。

如果在复制备份文件时需要更多灵活性,也可以使用一个较低层的处理来创建单机热备份。要为低层 单机热备份做准备,将wal_level设置为replica或更高, archive_mode设置为on,并且设置一个archive_command,该命令只当一个开关文件存在时执行归档。例如:


archive_command = 'test ! -f /var/lib/pgsql/backup_in_progress || (test ! -f /var/lib/pgsql/archive/%f && cp %p /var/lib/pgsql/archive/%f)'

该命令在/var/lib/pgsql/backup_in_progress存在时执行归档,否则会安静地返回0值退出状态(让PostgreSQL能回收不需要的WAL文件)。

通过这样的准备,可以使用一个如下所示的脚本来建立备份:


touch /var/lib/pgsql/backup_in_progress
psql -c "select pg_start_backup('hot_backup');"
tar -cf /var/lib/pgsql/backup.tar /var/lib/pgsql/data/
psql -c "select pg_stop_backup();"
rm /var/lib/pgsql/backup_in_progress
tar -rf /var/lib/pgsql/backup.tar /var/lib/pgsql/archive/

开关文件/var/lib/pgsql/backup_in_progress首先被创建,这使对于未完成WAL文件的归档操作发生。备份完成之后开关文件会被删除。归档的WAL文件则被加入到备份中,这样基础备份和所有需要的WAL文件都是同一个tar文件的组成部分。请记住在你的备份脚本中加入错误处理。

3.6.2. 压缩的归档日志

如果担心归档存储的尺寸,你可以使用gzip来压缩归档文件:


archive_command = 'gzip < %p > /var/lib/pgsql/archive/%f'

那么在恢复时你将需要使用gunzip:


restore_command = 'gunzip < /mnt/server/archivedir/%f > %p'

3.6.3. archive_command脚本

很多人选择使用脚本来定义他们的archive_command,这样他们的postgresql.conf项看起来非常简单:


archive_command = 'local_backup_script.sh "%p" "%f"'

任何时候如果你希望在归档处理中使用多个命令,明智的方法是使用一个独立的脚本文件。这可以使脚本更为复杂,它可以使用一种流行的脚本语言来编写,例如bash 或perl。

需要在一个脚本内解决的需求例子包括:

  • 将数据拷贝到安全的场外数据存储
  • 批处理WAL文件,这样它们可以每三小时被传输一次,而不是一次一个
  • 与其他备份和恢复软件交互
  • 与监控软件交互以报告错误

Tip: 在使用一个archive_command脚本时,最好启用logging_collector。任何从该脚本被写到stderr的消息将出现在数据库服务器日志中,这允许在复杂配置失败后能更容易被诊断。

3.7. 警告

在编写此文档时,连续归档技术存在一些限制。这可能会在未来的发布中被修复:

  • 哈希索引上的操作目前不被WAL记录,因此重放不会更新这些索引。这将意味着任何新的插入都会被索引忽略,被更新的行显然会消失而被删除的行将仍然保留有指针。换句话说,如果你修改了一个具有哈希索引的表,那么你将在一个后备服务器上得到不正确的查询结果。当恢复结束时,推荐你手工REINDEX每一个这样的索引。
  • 如果一个CREATE DATABASE命令在基础备份时被执行,然后在基础备份进行时CREATE DATABASE所复制的模板数据库被修改,恢复中可能会导致这些修改也被传播到已创建的数据库中。这当然是我们不希望的。为了避免这种风险,最好不要在创建基础备份时修改任何模板数据库。
  • CREATE TABLESPACE命令会WAL以其字面绝对路径记录,并且因此将在重放时以相同的绝对路径来创建表空间。当日志在一台不同的机器上被重放时,这可能也不是我们希望的。即使日志在同一台机器上被重放也是危险的,就算是恢复到一个新的数据目录重放过程也会覆盖原来表空间的内容。为了避免这种潜在的陷阱,最佳做法是在创建或丢弃表空间后创建一个新的基础备份。

还需要注意的是,默认的WAL格式相当庞大,因为它包括了很多磁盘页快照。这些页快照被设计用于支持崩溃恢复,因为我们可能需要修复断裂的磁盘页。依靠你的系统硬件和软件,页断裂的风险可能会小到可以忽略,在此种情况下你可以通过使用full_page_writes参数关闭页快照来显著降低归档日志的总容量(在这样做之前阅读Chapter 30中的注解和警告)。关闭页快照并不会阻止使用日志进行PITR操作。一个未来的开发点是通过移除不需要的页拷贝来压缩归档的WAL数据,即使full_page_writes为on。同时,管理员可能希望通过尽可能增大检查点间隔参数来减少WAL中包含的页快照数量。

你可能感兴趣的:(PostgreSQL服务器管理:备份和恢复)