29.1. 可靠性

可靠性是任何严肃的数据库系统的重要属性,PostgreSQL尽一切可能来保证可靠的操作。 可靠性操作的一个方面是所有已提交的数据都应该存储在一个非易失的区域,这样就不会因为电力失效、 操作系统崩溃、硬件失效(除了非易失区域自身失效之外)等原因导致数据丢失。向计算机的永久存储 (磁盘驱动器或者等效的东西)成功写入数据通常可以满足这个要求。实际上,即使计算机完全失效, 只要磁盘驱动器生存下来,那么它们就可以移动到另外一台类似硬件的计算机上, 而所有已经提交的事务将保持原状。

周期性地强制数据写入磁盘盘片看上去像一件简单的操作,但实际上不是。 因为磁盘驱动器比内存和 CPU 要慢许多,在计算机的主存和磁盘盘片之间存在多层缓冲。 首先,有操作系统的缓冲区内存,它缓冲常用的磁盘块并且组合对磁盘写入的请求。幸运的是, 所有操作系统都给予应用一个强制从缓冲区写入磁盘的方法,PostgreSQL 使用了该特性(参阅wal_sync_method参数)。

然后,在磁盘驱动器的控制器上可能还有一个缓冲;特别是在RAID控制卡上更为常见。 这些缓冲区中,有些是透过式写入,意思是写入动作在到达的同时写入到磁盘上。 其它则是回写式写入,意思是数据将在稍后写入驱动器。这样的缓冲区是可靠性的危害, 因为磁盘控制器上的内存是易失的,在发生电力失效的情况下会丢失其中的内容。 好一些的控制器卡备有电池备份单元(BBUs), 可以在系统电力失效的情况下提供电力。在电力恢复之后,这些数据将会被写入磁盘驱动器。

最后,大多数磁盘驱动器自身也有缓冲区。有些是透过式的,有些是回写式的。 和磁盘控制器一样,回写式的磁盘缓冲区也存在数据丢失的问题。 消费级别的 IDE 和 SATA 驱动器特别容易包含回写式缓冲,在掉电的情况下很容易丢失数据。 很多固态磁盘(SSD)也有易失的回写式缓冲。

这些缓存通常可以禁用;然而,这样做的方法会因操作系统和驱动类型而不同。

  • 在Linux上,IDE和SATA驱动器可以使用hdparm -I查询; 如果在Write cache后面有一个*则写缓存是开启的。hdparm -W 0 可以用来关闭写缓存。SCSI驱动器可以使用sdparm 查询。使用sdparm --get=WCE来检查写缓存是否打开,使用sdparm --clear=WCE 来关闭写缓存。

  • 在FreeBSD上,IDE驱动器可以使用atacontrol查询, 写缓存在/boot/loader.conf里用hw.ata.wc=0来关闭; SCSI驱动器可以使用camcontrol identify查询,写缓存也同时查询, 并且当可用时使用 sdparm改变状态。

  • 在Solaris上,磁盘写缓存通过format -e来控制。 (Solaris ZFS文件系统对于打开磁盘写缓存是安全的,因为它发行自己的磁盘缓存刷新命令。)

  • 在Windows上,如果wal_sync_methodopen_datasync(默认值), 写缓存可以通过取消选中My Computer\Open\``_disk drive_\Properties\Hardware\Properties\Policies\Enable write caching on the disk 来禁用。或者,设置wal_sync_methodfsyncfsync_writethrough 来阻止写缓存。

  • 在Mac OS X上,写缓存可以通过设置wal_sync_methodfsync_writethrough来阻止。

最近的SATA驱动器(跟随ATAPI-6或以后的)提供一个驱动器缓存刷新命令(FLUSH CACHE EXT), 而SCSI驱动器长期以来支持一个类似的命令SYNCHRONIZE CACHE。这些命令不能直接访问PostgreSQL, 但是某些文件系统(例如,ZFS,ext4)可以使用它们来刷新数据到启用了回写的驱动器上的盘片上。 不幸的是,这样的文件系统只有在组合电池备份单元(BBU)磁盘驱动器时行为有效。在这样的设置中, 同步命令强制所有数据从驱动器缓存到磁盘,消除BBU的好处。你可以运行pg_test_fsync 程序来查看你是否受影响。如果你受到影响,BBU的性能优势可以通过在文件系统中关掉写屏障或重新配置磁盘控制器重新获得, 如果这是一个选项。如果关闭了写屏障,确保电池仍然运行;失效的电池可能导致数据丢失。 希望文件系统和磁盘控制器设计将最终解决这个次优的行为。

在操作系统向存储硬件发出一个写请求的时候,它没有什么好办法来保证数据真正到达非易失的存储区域。 实际上,确保所有存储部件都保证数据和文件系统元数据的完整性是管理员的责任。 应该避免使用没有电池供电的回写缓冲磁盘控制器。在驱动级别, 如果驱动器不能保证在关闭(掉电)之前写入数据,那么应该关闭回写缓冲。 如果你使用SSD,要知道这些默认不尊重缓存刷新命令。你可以使用 diskchecker.pl 测试可靠的I/O子系统的行为。

另外一个数据丢失的风险来自磁盘盘片写操作自身。磁盘盘片会被分割为段,通常每段 512 字节。 每次物理读写都对整个段进行操作。当一个写操作到达磁盘的时候,它可能是512字节的某些整数倍 (PostgreSQL通常一次写入8192字节,或者16段),而写入操作可能因为电力失效而随时失败, 意味着某些 512 字节的段写入了,而另一些则没有。为了避免这个问题,PostgreSQL 在修改磁盘上的实际页面之前周期性地把整个页面的影像写入永久WAL存储。这样, 在崩溃恢复的时候,PostgreSQL就可以从WAL中恢复部分写入的页面。 如果你有文件系统(比如ZFS)自身能够避免部分页面写入,你可以通过关闭full_page_writes 参数来关闭页面影像功能。电池备份单元(BBU)磁盘控制器不能阻止部分页面写入, 除非他们保证数据以完整页面(8kB)写入BBU。

PostgreSQL也能阻止某些因为硬件错误或介质失效可能发生在存储驱动器上的数据损坏, 比如读/写垃圾数据。

  • 在WAL文件中的每个单独的记录受CRC-32 (32-bit)校验保护,这样允许我们判断记录内容是否正确。 当我们在崩溃恢复时写入每个WAL记录和检查、归档记录和复制时设置CRC值。

  • 数据页不是当前校验和的,尽管在WAL记录中的整个页面图像记录将受到保护。 为未来使用数据页校验和特性,数据页有一个16位字段可用。

  • 内部数据结构例如pg_clog, pg_subtrans, pg_multixact, pg_serial, pg_notify, pg_stat, pg_snapshots 不是直接校验和的, 也不是通过全页面写入受到保护的页面。然而,这样的数据结构具有持久性, WAL记录书面允许最近的改变在崩溃恢复时精确重建并且这些WAL记录正如上述讨论的那样受到保护。

  • pg_twophase中的个人状态文件受到CRC-32保护。

  • 临时数据文件用于较大的SQL查询,具体化和中间结果不是当前校验和的, 也不会WAL记录被写入修改这些文件。

PostgreSQL不预防矫正内存错误,假设您将使用具有工业标准纠错编码(ECC) 的内存操作或更好的保护。