可靠性是任何严肃的数据库系统的重要属性,PostgreSQL尽一切可能来保证可靠的操作。可靠的操作的一个方面是,被一个提交事务记录的所有数据应该被存储在一个非易失的区域, 这样就不会因为失去电力、操作系统失败以及硬件失败(当然,除了非易失区域自身失效之外)等原因导致的数据丢失。 向计算机的永久存储(磁盘驱动器或者等效的设备)成功写入数据通常可以满足这个要求。 实际上,即使计算机受到致命损坏,只要磁盘驱动器幸存下来,那么它们就可以被移动到另外一台具有类似硬件的计算机上, 而所有已经提交的事务将保持原状。
周期地强制数据进入磁盘盘片看上去像一件简单的操作,但实际上并不是。 因为磁盘驱动器比内存和CPU要慢很多,在计算机的主存和磁盘盘片之间存在多层的高速缓存。 首先,有操作系统的高速缓存,它缓冲常用的磁盘块并且组合对磁盘的写入。 幸运的是,所有操作系统都给予应用一种强制从高速缓存写入磁盘的方法,PostgreSQL则使用了那个特性(参阅wal_sync_method参数调节如何完成之)。
然后,在磁盘驱动器的控制器上可能还有一个高速缓存;这在RAID控制卡上是特别常见的。有些高速缓存是直写式的,即写入动作在到达的时候就立刻写入到磁盘上。其它是回写式的, 即发送给驱动器的数据在稍后的某个时间写入驱动器。这样的高速缓存可能会称为可靠性灾难,因为磁盘控制器高速缓存的内存是易失性的,在发生电力失败的情况下会丢失其内容。 好一些的控制器卡有后备电池单元(BBU), 即这种卡上面有电池可以在系统电力失败的情况下提供电力。 在电力恢复之后,这些数据将会被写入磁盘驱动器。
最后,大多数磁盘驱动器都有高速缓存。有些是直写的,有些是回写的, 和磁盘控制器一样,回写的磁盘高速缓存也存在数据丢失的问题。 消费级别的IDE和SATA驱动器尤其可能包含回写式高速缓存,在掉电的情况下很容易丢失数据。很多固态驱动器(SSD)也具有易失性回写式高速缓存。
这些高速缓存通常可以被禁用,但是不同的操作系统和驱动器类型有不同的做法:
在Linux上,可以使用hdparm -I
查询IDE和SATA驱动器,如果在Write cache
之后有一个*
则表示写高速缓存被启用。可以用hdparm -W 0
来关闭写高速缓存。可以使用sdparm查询SCSI驱动器。使用sdparm --get=WCE
来检查写高速缓存是否被启用,而sdparm --clear=WCE
可以用来禁用它。
在FreeBSD上,可以使用camcontrol identify
查询IDE驱动器,
并在/boot/loader.conf
中使用hw.ata.wc=0
关闭写缓存;
可以使用camcontrol identify
查询SCSI驱动器,
并在可用时使用sdparm
查询和更改写缓存。
在Solaris上,磁盘的写高速缓存被format -e
控制(Solaris的ZFS文件系统对于开启的磁盘写高速缓存是安全的,因为它会发出它自己的磁盘高速缓存刷写命令)。
在Windows上,如果wal_sync_method
是open_datasync
(默认值),写高速缓存可以通过取消选中My Computer\Open\
禁用。另一种方法可以通过设置disk drive
\Properties\Hardware\Properties\Policies\Enable write caching on the diskwal_sync_method
为fsync
或fsync_writethrough
来阻止写高速缓存。
在macOS上,通过设置wal_sync_method
为fsync_writethrough
可以阻止写高速缓存。
最近的SATA驱动器(遵循ATAPI-6及更新标准)提供了一个驱动器高速缓存刷写命令(FLUSH CACHE EXT
),而SCSI驱动器有一个存在很长时间的类似命令SYNCHRONIZE CACHE
。这些命令对于PostgreSQL并不能直接访问,但某些文件系统(例如ZFS、ext4)可以使用它们将数据刷写到回写式驱动器的盘片上。不幸的是,这些文件系统在和后备电池单元(BBU)一起工作时的表现要略差。在这种设置下,同步命令强制所有来自控制器高速缓存的数据到磁盘,消除了BBU的很多好处。你可以运行pg_test_fsync程序来看你是否被影响。如果你被影响了,BBU带来的性能好处可以通过关闭文件系统的写障碍或者重新配置磁盘控制器来重新获得。如果写障碍被关闭,请确认电池是否保持有效,一个有问题的电池可能会导致数据丢失。但愿文件系统和磁盘控制器设计师们将最终解决这种次优行为。
在操作系统向存储硬件发出一个写请求的时候,它没有什么好办法来保证数据真正到达非易失的存储区域。 实际上,确保所有存储部件都保证数据和文件系统元数据的完整性是管理员的责任。 避免使用那些没有电池作为后备的写高速缓存的磁盘控制器。在驱动器级别,如果驱动器不能保证在关闭(掉电)之前写入数据, 那么关闭回写高速缓冲。如果你在使用SSD,注意很多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位)校验码所保护,这让我们可以判断记录内容是否正确。CRC值在我们写入每一个WAL记录时设置,并且在崩溃恢复、归档恢复和复制时检查。
目前数据页并没有默认地被校验,但是WAL记录中记录的整页映像将被保护。 关于启用数据校验的内容详见initdb。
诸如pg_xact
、pg_subtrans
、pg_multixact
、
pg_serial
、pg_notify
、pg_stat
、pg_snapshots
等内部数据结构既没有被直接校验,其页面也没有被整页写保护。但是,这些数据结构是持久的话,WAL记录被写入,它允许最近的修改能在崩溃恢复时被准确重建且这些WAL记录被按照以上讨论的方式保护着。
pg_twophase
中的单个状态文件被CRC-32保护。
用在大型SQL查询中排序的临时数据库文件、物化和中间结果目前没有被校验,对于这些文件的改变也不会导致写入WAL记录。
PostgreSQL无法避免可更正内存错误,它假定你会操作由工业标准纠错码(ECC)或更好方案保护的RAM。