在前面的章节中,我们已经理解了各种复制概念。这不仅仅是一个为了接下来将要介绍的东西而增强您的意识的理论概述,还将为您介绍大体的主题。

在本章,我们将更加接近实际的解决方案,并了解PostgreSQL内部是如何工作的,复制意味着什么。我们将看到所谓的事务日志(XLOG)做什么,以及它是如何运作的。XLOG在PostgreSQL复制机制中起着主要作用。理解这部分是如何工作的是必要的。

2.1 PostgreSQL如何写入数据

PostgreSQL的复制完全是关于写入数据的。因此,PostgreSQL内部写一个数据块是直接和复制,复制概念相联系及高度相关的。在本章中,我们将深入写入操作。在本章您将学习如下东西:

• PostgreSQL如何写入数据

•涉及到哪些内存和存储参数

•写入如何进行优化

•写入如何被复制

•如何保证数据一致性

一旦您读完本章,就为您理解下一章做好了准备,它将教会您如何安全地复制您的第一个数据库。

2.1.1 PostgreSQL的磁盘布局

本章我们要了解的第一个问题就是PostgreSQL的磁盘布局。了解磁盘布局对检查一个现有的安装是非常有帮助的,对设计一个高效率,高性能的安装也是很有帮助的。

与其他数据库系统相比,如Oracle,PostgreSQL依靠文件系统来存储数据。PostgreSQL不使用原始设备。这背后的哲学是如果一个文件系统的开发者很好地完成了他或她的工作,就没有必要一遍又一遍地重新实现文件系统的功能。

查看数据目录

为了理解PostgreSQL使用的文件系统层,我们可以看一下在数据目录(由initdb在安装时创建)下我们能看到什么:

[hs@paulapgdata]$ ls -l

total 92

-rw------- 1 hs staff 4 Feb 11 18:14 PG_VERSION

drwx------ 6 hs staff 4096 Feb 11 18:14 base

drwx------ 2 hs staff 4096 Feb 11 18:14 global

drwx------ 2 hs staff 4096 Feb 11 18:14 pg_clog

-rw------- 1 hs staff 4458 Feb 11 18:14 pg_hba.conf

-rw------- 1 hs staff 1636 Feb 11 18:14 pg_ident.conf

drwx------ 4 hs staff 4096 Feb 11 18:14 pg_multixact

drwx------ 2 hs staff 4096 Feb 11 18:14 pg_notify

drwx------ 2 hs staff 4096 Feb 11 18:14 pg_serial

drwx------ 2 hs staff 4096 Feb 11 18:14 pg_snapshots

drwx------ 2 hs staff 4096 Feb 11 18:19 pg_stat_tmp

drwx------ 2 hs staff 4096 Feb 11 18:14 pg_subtrans

drwx------ 2 hs staff 4096 Feb 11 18:14 pg_tblspc

drwx------ 2 hs staff 4096 Feb 11 18:14 pg_twophase

drwx------ 3 hs staff 4096 Feb 11 18:14 pg_XLOG

-rw------- 1 hs staff 19630 Feb 11 18:14 postgresql.conf

-rw------- 1 hs staff 47 Feb 11 18:14 postmaster.opts

-rw------- 1 hs staff 69 Feb 11 18:14 postmaster.pid

您会看到一系列的文件和目录,这都是运行一个数据库实例的所需要的。让我们看看其中的细节。

PG_VERSION-PostgreSQL的版本号

在启动时该文件会告诉文件系统,数据目录是否包含正确的版本号。请注意:只有主版本号在这个文件中。在同一个主版本下的两个不同的小版本之间进行复制是可能的,也是容易的。

[hs@paulapgdata]$ cat PG_VERSION

9.2

该文件是可读的纯文本。

base-实际数据目录

在数据目录中,base目录是最重要的目录之一。他实际上包含了真实数据(系统表,索引等)。在base目录内部,每个数据库有它自己的子目录:

[hs@paula base]$ ls -l

total 24

drwx------ 2 hs staff 12288 Feb 11 18:14 1

drwx------ 2 hs staff 4096 Feb 11 18:14 12865

drwx------ 2 hs staff 4096 Feb 11 18:14 12870

drwx------ 2 hs staff 4096 Feb 11 18:14 16384

我们可以很容易地把这些目录连接到我系统内的数据库,值得注意的是PostgreSQL使用数据库的对象ID。这和使用名称相比有许多优势,因为对象ID从来不会改变,并且为抽象各种问题提供一个好的方法,例如在服务器上使

用不同的字符集的问题等等。

test=# SELECT oid, datname FROM pg_database;

oid |datname

-------+-----------

1 | template1

12865 | template0

12870 | postgres

16384 | test

(4 rows)

现在,我们可以看到数据在这些特殊的数据库目录内是如何存储的。在PostgreSQL中,每个表关系到(至少)一个数据文件。让我创建一个表,看看会发生什么:
      test=# CREATE TABLE t_test (id
int4);

CREATE TABLE

我们可以检查系统表来获取所谓的relfilenode,这代表磁盘上的存储文件名称:

test=# SELECT relfilenode, relname

FROM pg_class

WHERE relname = 't_test';

relfilenode | relname

-------------+---------

16385 | t_test

(1 row)

一旦表被创建,PostgreSQL就会在磁盘上创建一个空文件:

[hs@paula base]$ ls -l 16384/16385*

-rw------- 1 hs staff 0
Feb 12 12:06 16384/16385

越来越多的数据文件

表有时候会相当大,因此,把一个表的所有相关的数据都放到单个文件或多或少是不可能的。为了解决这个问题,每添加1GB的数据PostgreSQL就会额外添加一个文件。

因此,如果一个叫16385的文件大小超过1GB,就会有一个叫16385.1的文件。一旦这个文件被填满,您就会看到一个名为16385.2的文件,等等。通过这种方式,在某些罕见的操作系统或嵌入式设备上,PostgreSQL中的表可以安全可靠地扩大规模,而不必担心低层文件系统限制。

在块中执行I/O

为了提高I/O性能,PostgreSQL总是以8k大小的块执行I/O.因此,您会看到您的数据文件总是以8k的步长增长。当谈到物理复制,您必须确保双方(master 和slave)都用相同的块大小进行编译。

[除非您明确地按您自己使用的不同的块大小来编译PostgreSQL,您可以一直接受数据块一致且准确的8k大小的事实。]

关系forks

除了在前面段落讨论的数据文件之外,PostgreSQL会实行相同的文件号创建额外的文件。截至目前,这些文件用来存储表(FreeSpaceMap)内部空闲空间信息,所谓的Visibility
Map等等。以后,将会有更多类型的关系forks可能被添加进来。

global-全局数据

global包含全局系统表。这个目录较小,您不应该期望会有过多的存储消耗。

处理独立的数据文件

用户经常忘记一件事情:单个PostgreSQL数据文件基本上没有多少价值。如果您只有一个数据文件,不可能可靠地存储数据;容易地试图提取单个文件中的数据是毫无希望的猜测。因此,为了读数据,您需要一个完整的实例。

pg_clog-提交日志

提交日志是一个工作数据库实例的一个重要组成部分。它存储系统上进行的事务的状态。一个事务有四种状态(TRANSACTION_STATUS_IN_PROGRESS,
TRANSACTION_STATUS_COMMITTED,

TRANSACTION_STATUS_ABORTED, and
TRANSACTION_STATUS_SUB_COMMITTED),如果一个事物的提交日志的状态是不可用,PostgreSQL将不知道是否应该被看到。

如果一个数据库实例的提交日志由于某种原因被破坏(也许是因为文件系统损坏),您可以提前一些有趣的时间。

[如果提交日志被损坏,我们建议给和数据库实例(文件系统)做快照,伪造提交日志,它有时有助于从有问题的数据库实例获得一个合理数量的数据。]

pg_hba.conf-基于主机的网络配置

pg_hba.conf文件配置PostgreSQL的内部防火墙,代表了一个PostgreSQL集群中的两个最重要的配置文件之一。它允许用户定义任何基于请求来源的各种认证的类型。一个数据库管理员,了解pg_hba.conf文件是至关重要的,因为这个文件决定是否允许一个slave连接到master。如果您碰巧错过了这里的东西。您可能会在slave的日志中看到错误信息(例如:没有pg_hba.conf的许可…)

pg_ident.conf-身份认证

pg_ident.conf文件可和pg_hba.conf文件结合用于配置身份认证。

pg_multixact-多事务状态数据

多事物日志管理器可以有效地处理共享行锁。此目录中和复制相关的东西没有实际的影响。

pg_notify-监听/通知数据

在这个目录中,系统存储关于监听/通知的信息(异步后端接口)。和复制没有实际的影响。

pg_serial-关于提交序列化事务的信息

序列化事务的信息存储在这里。我们必须在磁盘上存储序列化事务提交信息,以确保长时间运行的事务不会膨胀内存。内部采用一个简单的SLRU结构来跟踪这些事务。

pg_snapshot-输出快照

这是一个由PostgreSQL快照管理需要的信息组成的文件。

在某些情况下,快照必须输出到磁盘,以避免进入内存。崩溃之后,那些快照将自动被清理。

pg_stat_tmp-临时统计数据

临时统计数据存储在这个文件中。该信息被大多数pg_stat_*系统视图所需要(因此,也为低层函数提供原始数据)。

pg_subtrans-子事务数据

在这个目录中,我们存储有关子事务的信息。pg_subtrans(和pg_clog)目录永久性的(在磁盘上)存储事务相关信息。有一定限制数目的内存叶保存在内存中,因此,在很多情况下,没有必要实际从磁盘读取。但是如果有一个长期运行的事务或一个打开的在后端闲置的事务,从磁盘读写该信息可能是必要的。它们也允许通过服务器重启确保信息永久性存储。

pg_tblspc-到表空间的符号链接

pg_tblspc是一个非常重要的目录。在PostgreSQL中,一个表空间是一个可以替代的存储位置,由一个保存数据的目录来表示。

这里最重要的是:如果一个数据库实例被完全复制,我们不能简单地依靠一个事实:集群中的所有服务器都使用同样的磁盘布局和相同的存储硬件。这里存储这样一个场景:一个master比一个slave需要更多的I/O能力,这个slave可能只是身边充当备份或备用的。为了让用户处理不同的磁盘布局,PostgreSQL将把符号链接到pg_tblspc关联目录。该数据库将盲目地追随那些符号链接找到这些表空间,无论它们在哪里。

这为最终用户提供了巨大的力量和灵活性。一般而言,控制存储对复制以及性能都是必要的。请记住,这些符号链接只能事后进行更改。应该仔细考虑。

(我们建议使用仅在本节所描述的策略当他真正需要的时候。对于大多数的设置,在master和slave上绝对推荐使用相同的文件系统布局。这样可以大大减少复杂性。)

pg_twophase – 有关预处理语句的信息

PostgreSQL必须存储有关两阶段提交的信息。尽管两阶段提交是一个重要的特征,该目录本身对普通的系统管理员来说不太重要。

pg_XLOG – PostgreSQL 的事务日志(WAL)

PostgreSQL事物日志是本章我们必须要讨论的基本目录。pg_XLOG包含所谓XLOG的所有文件。如果在过去的时间里您有使用PostgreSQL,您可能熟悉术语WAL(Write
Ahead Log)。XLOG和WAL是一个事物的两个名字。对事务日志也同样适用。这三个术语被广泛地应用,知道他们是一样的意思是非常重要的。

pg_XLOG目录一般是这样的:

[hs@paulapg_XLOG]$ ls -l

total 81924

-rw------- 1 hs staff 16777216 Feb 12 16:29

000000010000000000000001

-rw------- 1 hs staff 16777216 Feb 12 16:29

000000010000000000000002

-rw------- 1 hs staff 16777216 Feb 12 16:29

000000010000000000000003

-rw------- 1 hs staff 16777216 Feb 12 16:29

000000010000000000000004

-rw------- 1 hs staff 16777216 Feb 12 16:29

000000010000000000000005

drwx------ 2 hs staff 4096 Feb 11 18:14 archive_status

您看到的是一堆文件,它们总是16MB的大小(默认设置)一个XLOG文件的文件名通常是24个字节长。编号总是十六进制。因此,该系统会计算”9,A,B,C,D,E,F,10”等等。

一个重要的事情是pg_XLOG目录的大小不会疯狂地随着时间的推移而变大,它完全独立于您在您系统上运行的事务的类型。XLOG的大小由将在本章的后面进行讨论的postgresql.conf中的参数决定的,简而言之:无论您是运行小事务或者大事务,XLOG的大小都是一样的。您可以轻松地运行一个像1TB一样大的事务,只有极少数的XLOG文件。这可能不是太有效,明智的性能,但它在技术上完全可行。

postgresql.conf – PostgreSQL配置文件的核心文件

最后,还有一个主要的PostgreSQL配置文件。所有的配置参数都可以在postgresql.conf中被改变,我们将使用这个文件广泛设置复制和调整我们的数据库实例,以确保我们的复制设置为我们提供了卓越的性能。

[如果您碰巧使用预编译的二进制文件,可能无法在您的数据目录找到postgresql.conf。它可能位于/etc/(在Linux/Unix)下的一些子目录,或在您在Windows下选择的地方。精确的位置是高度依赖于您正在使用的操作系统。数据目录的典型的位置是/var/lib/pgsql/data。但是postgresql.conf往往在/etc/postgresql/9.X/main/postgresql.conf下(如在Ubuntu和类似系统)或直接在/etc/目录下面。]

2.1.2 写入一行数据

既然我们已经了解了磁盘布局,我们将进一步深入PostgreSQL和探究当PostgreSQL写一行数据的时候会发生什么。一旦您掌握了这一章,您会充分理解XLOG背后的概念。

需要注意的是,在关于写一行数据的这一章,我们已经简化了一些过程,以确保我们可以强调重点和PostgreSQL XLOG背后的思想。

一条简单的INSERT语句

让我们假设我们正在做类似下面的一个简单的INSERT语句:

INSERT INTO foo VALUES ('abcd'):

正如人们所想象的,一个INSERT操作的目标是给现有的表添加一行数据。在前面关于PostgreSQL磁盘布局的章节中我们已经看到了,每个表将与磁盘上的一个文件关联。

让我们进行一个心理实验,假设这里我们正在处理的表大小为10TB。PostgreSQL将看到INSERT操作并寻找这个表(使用现有的块或者添加一个新块)内的一些空余的地方。为了这个例子的目的,我们简单地把数据插入到表中的第二块。

只要服务器实际地保存了事务,一切都没有问题。如果有人在写了abc而不是整个数据之后,拔了塞子,会发生什么事情?当服务器重新启动以后,我们会发现我们处于一种状况,我们有一个不完整的记录块,更有趣的是,我们甚至可能不会有丝毫的想法,这个块包含的坏记录可能在哪里。

一般情况下,包含不完整的未知位置的行的表被认为是坏表。当然,系统表损坏不是什么事,PostgreSQL社区会容忍的,特别地,没有由于明显的设计失误引起的类似这样的问题。

WAL-writing期间崩溃

[我们必须确保PostgreSQL在任何给定的时间点的中断中幸存,并且没有数据丢失或者损坏。保护您的数据是好的,并且是绝对必须的。]

为了解决我们刚才讨论过的问题,PostgreSQL使用了所谓的WAL(Write Ahead Log)或者简单地XLOG。使用WAL意味着写日志在写数据之前。因此,在我们实际把数据写到表中之前,我们将按顺序排列日志条目,这表明我们将要计划对我们的表做什么。下图显示了事情是如果工作的:

PostgreSQL Replication之第二章 理解PostgreSQL的事务日志(1)-LMLPHP

正如我们可以从图中看到了,一旦我们将数据写入到日志(1),我们就可以把事务标记为完成(2)。之后,数据被写到表中(3)。

[我们已经排除了等式中的内存部分—这将在本节稍后讨论。]

让我们用两个例子说明这种方法的优点:

为了确保本章中介绍的概念坚如磐石的工作,我们必须确保我们能够在任何时间点崩溃,而不用担心我们的数据。让我们假设,我们在写XLOG时崩溃。这种情况下会发生什么呢?在这种情况下,终端用户将知道事务没有成功,那么,他或者她将不依赖事务的成功。

只要PostgreSQL启动,它可以通过XLOG并重放一切要确保PostgreSQL是处于一致状态的事情。所以,如果我们不通过WAL-writing来实现它,讨厌的事情就已经发生了,我们不能指望有一个写是成功的。

WAL条目将永远知道它是否完成了。每个WAL条目都有一个内部校验和,因此,一旦有人试图重放坏的WAL,PostgreSQL可以立即检测到。在崩溃时,当我们可能无法依靠写入到磁盘的最新数据时,这尤其重要。WAL将自动解决崩溃恢复中的这些问题。

[如果PostgreSQL配置正确,崩溃在任何时间点都是相当安全的。]

WAL-writing之后崩溃

现在让我们假设,我们已经成功通过了WAL-writing,之后我们马上崩溃了,也许在写底层表。如果我们只设法写了ab而不是整个数据将会怎么样呢?

那么,在这种情况下,我们将知道在重放期间丢失了什么。再次,我们去重放日志以确保在我们的数据表中的所有数据安全需要的是什么。

虽然崩溃之后在一个表中找数据可能很困难,我们总能依靠一个事实,我们可以在WAL中找到数据。WAL是连续的,如果我们只是跟踪数据写了多远,我们总能从那里继续进行;XLOG将直接引导我们到表中的数据,并且它总能知道哪里有一个变化或者应该在哪里做一个改变。PostgreSQL 没有必要在WAL中搜索数据;它只是从正确的角度将其重放。

[一旦一个事务完成了写WAL,它不会再很容易就丢失了。]

2.1.3 读一致性

既然我们已经看到了一个简单的写是如何执行的,我们必须看看写对读有什么影响。接下来的图片显示了PostgreSQL数据库系统的基本架构:

PostgreSQL Replication之第二章 理解PostgreSQL的事务日志(1)-LMLPHP

为了简单起见,我们可以把一个数据库实例看作一个由三个主要部分组成的东西:

1. PostgreSQL 数据文件

2. 事务日志

3. 共享缓存

在前面的章节中,我们已经讨论了数据文件。您也看到了一些关于事务日志本身的基本信息。现在我们要扩大我们的模型,并给这个架构图添加另一个部分:游戏的内存部分,所谓的共享缓冲区。

共享缓冲区的目的

共享缓冲区是PostgreSQL的I/O缓存。它有助于缓存8K的块,这都是从操作系统中读取的,它有助于保持写回到磁盘以优化效率(稍后将在本章讨论这是如何工作的)。

[共享缓冲区是很重的,因为它影响性能。]

但是,当说到共享缓存时,性能并不是我们应该关注的唯一的问题。让我们假设我们要发送一个查询。为了简单起见,我们也假设我们只需要一个块来处理这个读请求。如果我们做一个简单的查询将怎么样呢?也许我们正在寻找一些简单的东西,像电话号码,或者给定一个特定键的用户名。下面的列表显示,用大量简化的方式,在所做的假设的情况下,PostgreSQL将做什么,实例刚才已经重新启动了:

1. PostgreSQL 将在高速缓存(如前所述,这是共享缓冲区)中查找所需要的块。在一个刚刚启动的实例的缓存中,它是不会找到数据块的。

2. PostgreSQL 将向操作系统请求那个数据块。

3. 一旦数据被从操作系统加载了,PostgreSQL将把它放进第一个缓存队列中。

4. 已成功送达该查询。

让我们假设一个相同的块将被第二个查询再次使用。在这种情况下,事情将如下工作:

• PostgreSQL 将查找所需要的数据块并且缓存命中。

• PostgreSQL 会发现一个缓存块已经被重新使用了并把它从一个低级别的缓存(Q1)移动到一个较高级别的缓存(Q2)。在第二个队列中的块将在缓存中保留的时间更长,因为它们证明比那些只在Q1级别的块更重要。

[共享缓冲区应该有多大?在Linux下,8GB的是通常推荐的。在Windows下,低于1GB的值被证明是有用的(如PostgreSQL9.2)。从PostgreSQL9.3开始,在Windows下,更高的值可能是有益且可行的。在Linux下,疯狂的大的共享缓冲区实际上是逆优化的。当然,这只是一个经验法则,特殊的安装可能需要不同的设置。]

混合读写

请记住,在本节,全部都是关于理解写,以确保我们的最终目标,全面深入理解复制,可以实现。因此,我们必须看看读和写是如何走到一起的。让我们看看读和写如何走到一起的:

1. 一个写请求到来。

2. PostgreSQL 将写入事务日志以确保可以达到一致性。

3. PostgreSQL 将获取一个在PostgreSQL共享缓冲区内部的块,并在内存中做出改变。

4. 一个读请求到来。

5. PostgreSQL 将查询缓存并查找所需要的数据。

6. 一个缓存命中将着陆并且查询将被送达。

这个例子的重点是什么?好吧,正如您可能已经注意到的,我们从来没有讨论实际的表下面的写。我们讨论了到高速缓存的写,到XLOG的写,等等,但是没有关于真正的数据文件的写。

[在这个例子中,我们在表里所写的行在或者不在表里,这是完全不相关的。原因很简单:如果我们需要一个刚刚被修改的块,我们永远都不会从表下面获得。]

理解在写操作期间或者之后,数据通常不直接写到数据文件是很重要的。稍后把数据写出来对提高效率是有意义的。之所以这样的原因是,它对复制有微妙的影响。数据文件本身是毫无价值的,因为它既不一定完整又不一定正确。要运行一个PostgreSQL实例,您将总是需要数据文件伴随着事务日志。否则,就没有办法幸免于崩溃。

从一致性的角度来看,共享缓冲区是在这里完成用户的数据的视图。如果有什么东西没有在表中,逻辑上,它应该在内存中。

在崩溃的情况下,内存将丢失,所以XLOG被咨询并重放,以再次将数据文件转换为一个一致的数据储存。在任何情况下,数据文件都只是故事一半。

[在PostgreSQL9.2以及之前的版本中,共享缓冲区只在SysV/POSIX 共享内存或者在Windows的模拟SysV中。PostgreSQL9.3(在写作的时候,未发布)开始使用内存映射文件,这在Windows下更快,在性能方面与在Linux下没有区别,但是在BSDs下稍慢。BSD开发者们已经开始解决这个问题。使用mmap可以使配置更容易,因为mmap是不受操作系统的限制的,只要有足够的内存,它是没有限制的。SysVshmem 是有限制的,大量的SysVshmem通常只有操作系统做相应的调整才被分配。从Linux发行版到Linux发行版,共享内存变量是缺省配置的。Suse更趋于替代,而RedHat,Ubantu和一些其它的则更趋于保守。]

05-11 20:20