PostgreSQL进程和内存结构

PostgreSQL数据库启动时,会先启动一个叫做Postmaster的主进程,还会fork一些辅助子进程,这些辅助子进程各自负责一部分功能,辅助子进程分类如下:

$ ps -ef | grep postgres
postgres  2162     1  0 May23 ?        00:01:01 /usr/local/pgsql/bin/postmaster -D /usr/local/pgsql/data
postgres  2346  2162  0 May23 ?        00:00:00 postgres: logger process                                
postgres  2804  2162  0 May23 ?        00:00:10 postgres: checkpointer process                          
postgres  2805  2162  0 May23 ?        00:00:14 postgres: writer process                                
postgres  2806  2162  0 May23 ?        00:00:25 postgres: wal writer process                            
postgres  2807  2162  0 May23 ?        00:00:43 postgres: autovacuum launcher process                   
postgres  2808  2162  0 May23 ?        00:00:05 postgres: archiver process                              
postgres  2809  2162  0 May23 ?        00:01:15 postgres: stats collector process

 

1. Postmaster进程

主进程Postmaster是整个数据库实例的总控制进程,负责启动和关闭数据库实例,用户可以运行postmaster,postgres命令加上合适的参数启动数据库,实际上,postmaster命令是一个指向postgres的链接.

$ ls -lh /usr/local/pgsql/bin/post*
-rwxr-xr-x 1 postgres dba 6.4M Jun 24  2016 /usr/local/pgsql/bin/postgres
lrwxrwxrwx 1 postgres dba    8 Jun 24  2016 /usr/local/pgsql/bin/postmaster -> postgres

更多时候我们使用pg_ctl启动数据库,pg_ctl也是通过运行postgres来启动数据库,它只是做了一些包装,让我们更容易启动数据库,所以,主进程Postmaster实际是第一个postgres进程,此进程会fork一些与数据库实例相关的辅助子进程,并管理他们.

当用户与PostgreSQL数据库建立连接时,实际上是先与Postmaster进程建立连接,此时,客户端程序会发出身份证验证的消息给Postmaster进程,Postmaster主进程根据消息中的信息进行客户端身份验证,如果验证通过,它会fork一个子进程postgres为这个连接服务,fork出来的进程被称为服务进程,查询pg_stat_activity表可以看到的pid,就是这些服务进程的pid.

postgres=# select pid from pg_stat_activity;
pid
------ 3279 4449 3411 (3 rows) $ ps -ef | egrep "3279|3363|3411|4352" postgres 3279 1366 0 09:55 ? 00:00:00 postgres: contentreader TnGeo-Here-Data 172.16.60.57(64716) idle postgres 3411 1366 0 09:58 ? 00:00:00 postgres: aceuser TnGeo-Here-Data 172.16.40.14(64017) idle

 

当某个服务出现错误的时候,Postmaster主进程会自动完成系统的修复,修复过程中会停掉所有的服务进程,然后进行数据库数据的一致性恢复,等待恢复完成后,数据库又可以接受新的连接了.

2. SysLogger进程

日志信息是数据库管理员获取数据库系统运行状态的有效手段。在数据库出现故障时,日志信息是非常有用的。把数据库日志信息集中输出到一个位置将极大方便管理员维护数据库系统。然而,日志输出将产生大量数据(特别是在比较高的调试级别上),单文件保存时不利于日志文件的操作。因此,在SysLogger的配置选项中可以设置日志文件的大小,SysLogger会在日志文件达到指定的大小时关闭当前日志文件,产生新的日志文件。在postgresql.conf里可以配置日志操作的相关参数:

log_destination:配置日志输出目标,根据不同的运行平台会设置不同的值,Linux下默认为stderr。

logging_collector:是否开启日志收集器,当设置为on时启动日志功能;否则,系统将不产生系统日志辅助进程。

log_directory:配置日志输出文件夹。

log_filename:配置日志文件名称命名规则。

log_rotation_size:配置日志文件大小,当前日志文件达到这个大小时会被关闭,然后创建一个新的文件来作为当前日志文件。

此外,postgresql.conf中还提供了其他配置参数,可以根据需要进行设置。

3. BgWriter进程

BgWriter是PostgreSQL中在后台将脏页写出到磁盘的辅助进程,引入该进程主要为达到如下两个目的:首先,数据库在进行查询处理时若发现要读取的数据不在缓冲区中时要先从磁盘中读入要读取的数据所在的页面,此时如果缓冲区已满,则需要先选择部分缓冲区中的页面替换出去。如果被替换的页面没有被修改过,那么可以直接丢弃;但如果要被替换的页已被修改,则必需先将这页写出到磁盘中后才能替换,这样数据库的查询处理就会被阻塞。通过使用BgWriter定期写出缓冲区中的部分脏页到磁盘中,为缓冲区腾出空间,就可以降低查询处理被阻塞的可能性。其次,PostgreSQL在定期作检查点时需要把所有脏页写出到磁盘,通过BgWriter预先写出一些脏页,可以减少设置检查点(CheckPoint,数据库恢复技术的一种)时要进行的IO操作,使系统的IO负载趋向平稳。通过BgWriter对共享缓冲区写操作的统一管理,避免了其他服务进程在需要读入新的页面到共享缓冲区时,不得不将之前修改过的页面写出到磁盘的操作。不过,当BgWriter无法维护足够的干净共享缓冲区时,其他服务进程仍然可以自行完成将脏页写回磁盘的操作。BgWriter同时也负责处理所有的检查点,它也会定期地发出一个检查点请求,当然也可以由其他进程通过信号要求BgWriter执行一个检查点。

BgWriter是PostgreSQL 8.0以后新加的特性,但在8.2以前版本中,使用BgWriter需要管理员进行很复杂的配置。在PostgreSQL 8.4中,数据库配置文件postgresql.conf中与BgWriter相关的配置选项有3个:bgwriter_delay、bgwriter_lru_maxpages、bgwriter_lru_multiplier。系统每隔bgwriter_delay指定的时间启动BgWriter。BgWriter从后向前扫描缓冲区的LRU链表,写出至多bgwriter_lru_multiplier*N个脏页,并且不超过bgwriter_lru_maxpages值的限制。其中N是最近一段时间在两次BgWriter运行期间系统新申请的缓冲页数。在BgWriter参数的配置中,如果BgWriter过于频繁地将脏页写出,则经常被更新的数据页很可能会被一次又一次地写出到磁盘上,反而增加了数据库的IO次数,进而导致系统性能下降。另一方面,若BgWriter写周期过长,又不能起到优化数据库写IO操作的作用。因此确定BgWriter以什么速率将脏页写出才能达到最佳效果需要综合考虑系统的实际运行状态,默认将bgwriter_delay设置为200毫秒,bgwriter_lru_maxpages设置为100,bgwriter_lru_multiplier设置为2.0。

4. WalWriter进程

预写式日志WAL(Write Ahead Log,也称为Xlog)的中心思想是对数据文件的修改必须是只能发生在这些修改已经记录到日志之后,也就是先写日志后写数据。如果遵循这个过程,那么就不需要在每次事务提交的时候都把数据块刷回到磁盘,因为在出现崩溃的情况下可以用日志来恢复数据库。使用WAL主要的好处就是显著地减少了写磁盘的次数,因为在日志提交的时候只需要把日志文件刷新到磁盘,而不是事务修改的所有数据文件。在多用户环境里,许多事务的提交可以用日志文件的一次fsync来完成。而且日志文件是顺序写的,因此同步日志的开销远比同步数据块的开销要小。WalWriter是Postgres 8.3以后才新加入的新特性,它避免了其他服务进程在事务提交时需要同步地写入预写式日志到磁盘,也使得事务提交记录不是在提交时同步地写入磁盘,而是在一个已知的预先设置的时间异步地写入。同BgWriter一样,其他服务进程在WalWriter出错时也允许直接进行预写日志写操作。

WAL日志文件存放在数据集簇中的pg_xlog目录里。它是作为一个段文件的集合存储的,每个段16MB,并分割成若干页,每页8KB。日志记录头格式在xlog.h里描述。日志内容取决于它记录的事件的类型。一个段文件的名字由24个十六进制字符组成,分为三个部分,每个部分由8个十六进制字符组成。第一部分表示时间线,第二部分表示日志文件标号,第三部分表示日志文件的段标号。时间线由1开始,日志文件标号和日志文件的段标号由0开始,所以系统中的第一个事务日志文件是000000010000000000000000,第二个事务日志文件是000000010000000000000001。目前这些数字不能循环使用(不过要把所有可用的数字都用光也需要非常长的时间)。

WAL的缓冲区和控制结构在共享内存里,它们是用轻量的锁保护的,对共享内存的需求由缓冲区数量决定,默认的WAL缓冲区大小是8个8KB的缓冲区(即64KB)。出于安全考虑,可以将日志文件和数据文件分别存储在不同的磁盘上,可以通过把pg_xlog目录移动到另外一个位置,然后在数据集簇里原来的位置创建一个指向新位置的符号链接来实现。

在PostgreSQL数据库的系统配置文件postgresql.conf中有如下参数可以配置WAL的属性:

fsync:该参数直接控制日志是否先写入磁盘。默认值是ON(先写入),表示更新数据写入磁盘时系统必须等待WAL的写入完成。可以配置该参数为OFF,表示更新数据写入磁盘完全不用等待WAL的写入完成。

synchronous_commit:参数配置是否等待WAL完成后才返回给用户事务的状态信息。默认值是ON,表明必须等待WAL完成后才返回事务状态信息;配置成OFF能够更快地反馈回事务状态。

wal_sync_method:WAL写入磁盘的控制方式,默认值是fsync,可选用值包括open_datasync、fdatasync、fsync_writethrough、fsync、open_sync。open_datasync和open_sync分别表示在打开WAL文件时使用O_DSYNC和O_SYNC标志;fdatasync和fsync分别表示在每次提交时调用fdatasync和fsync函数进行数据写入,两个函数都是把操作系统的磁盘缓存写回磁盘,但前者只写入文件的数据部分,而后者还会同步更新文件的属性;fsync_writethrough表示在每次提交并写回磁盘会保证操作系统磁盘缓存和内存中的内容一致。

full_page_writes:表明是否将整个page写入WAL。

wal_buffers:用于存放WAL数据的内存空间大小,系统默认值是64K,该参数还受wal_writer_delay、commit_delay两个参数的影响。

wal_writer_delay:WalWriter进程的写间隔时间,默认值是200毫秒,如果时间过长可能造成WAL缓冲区的内存不足;时间过短将会引起WAL的不断写入,增加磁盘I/O负担。

commit_delay:表示一个已经提交的数据在WAL缓冲区中存放的时间,默认值是0毫秒,表示不用延迟;设置为非0值时事务执行commit后不会立即写入WAL中,而仍存放在WAL缓冲区中,等待WalWriter进程周期性地写入磁盘。

commit_siblings:表示当一个事务发出提交请求时,如果数据库中正在执行的事务数量大于commit_siblings值,则该事务将等待一段时间(commit_delay的值);否则该事务则直接写入WAL。系统默认值是5,该参数还决定了commit_delay的有效性。

结合WAL日志和数据文件可以实现PostgreSQL数据库的在线备份和恢复。使用这种备份恢复方法时,我们可能要经常性地把数据文件、WAL日志文件保存到另外一个存储设备上。数据库文件拷贝和日志归档文件可以用于灾难恢复,每次做归档以后,过时的日志文件就可以删除。PostgreSQL提供了一种dump方式备份数据库文件,完成此工作的pg_dump工具存放在安装目录的子目录bin下。

5. PgArch进程

ostgreSQL从8.x版本开始提出了PITR(Point-In-Time-Recovery)技术,支持将数据库恢复到其运行历史中任意一个有记录的时间点。除2.5.3节中所述的WalWriter外,PITR的另一个重要的基础就是对WAL文件的归档功能。PgArch辅助进程的目标就是对WAL日志在磁盘上的存储形式(Xlog文件)进行归档备份。

PostgreSQL在数据集簇的pg_xlog子目录中始终只使用一个WAL日志文件,这个日志文件记录数据库中数据文件的每个改变。从逻辑上来看,PostgreSQL数据库会产生一个无限长的顺序的WAL记录序列。PostgreSQL在物理上把这个WAL记录序列分割成多个WAL文件(每个WAL文件为一个WAL段),通常每个段的大小为16MB(在编译PostgreSQL时可以通过编译选项改变这个大小)。每个段文件的名字是一个数字,用来反映它们在WAL序列中的位置。即使不进行WAL归档,PostgreSQL也会创建一些WAL段文件。但是只会使用其中一个来记录WAL日志,如果当前使用的WAL段文件超过了大小限制,则会关闭当前段文件,然后把另外一个可重复使用的段文件作为当前段文件来使用。能够被重复使用的段文件必须保证其中的内容都在最后一次检查点之前产生并保证其中的内容都写入磁盘中。在这种情况下,被保存下来的将只有部分WAL日志,因此也不能实现任意历史时间点的恢复。为实现PITR,需要在WAL段文件被重用时进行归档备份操作,把将被重用的WAL段中的日志记录保存到其他位置。这样归档日志加上当前日志就可以形成连续的WAL日志记录。为了给数据库管理员提供最大的灵活性,PostgreSQL不对如何归档做任何假设,而是让管理员提供一个shell命令来拷贝一个完整的WAL段文件到备份存储位置。该命令可以就是一个cp命令,或者是一个复杂的shell脚本,所有的操作都由管理员决定。

在postgresql.conf中与预写式日志归档相关的属性有:

archive_mode:表示是否进行归档操作,默认值为off(关闭)。

archive_command:由管理员设置的用于归档WAL日志的命令。

archive_time:表示归档周期,在超过该参数设定的时间时强制切换WAL段,默认值为0(表示禁用该功能)。

为允许归档,需要把postgresql.conf配置文件中的wal_level参数设置为“archive”或“hot_standby”,archive_mode参数设置为“on”,并为archive_command命令指定一个shell命令。在用于归档的命令中,预定义变量“%p”用来指代需要归档的WAL全路径文件名,“%f”表示不带路径的文件名(这里的路径都是相对于当前工作目录的路径)。每个WAL段文件归档时将调用archive_command所指定的命令。当归档命令返回0时,PostgreSQL就会认为文件被成功归档,然后就会删除或循环使用该WAL段文件。否则,如果返回一个非零值,PostgreSQL会认为文件没有被成功归档,便会周期性地重试直到成功。

为了标识各个段文件的状态,PostgreSQL在数据集簇的pg_xlog/archiver_status目录下记录了每一个WAL段文件的状态文件,状态文件的前缀与段文件同名,以表示时间顺序的整数形式命名。状态文件后缀为.ready或者.done,代表段文件的归档状态,分别代表“就绪”和“已完成”两种模式。PgArch进程会找到所有状态为“就绪”的段文件,找到状态文件后,若用户设置了归档命令,PgArch进程将归档命令解析后交由系统的shell函数system(3)执行。文件归档成功后会把pg_xlog/arcive_status目录下相应的状态文件后缀修改为.done。

对WAL日志的归档给管理员提供了一种新的数据库备份策略,这种备份策略组合了文件系统备份与WAL文件的备份。在恢复时,首先恢复数据文件,然后重放WAL日志到指定的时间点。应用这种备份恢复策略,在开始的时候并不需要一个非常完美的一致性备份。任何备份内部的不一致都会被日志的重放动作修改正确。因此,我们不需要文件系统快照的功能,只需要tar或者类似的归档工具。另外,WAL文件的分段归档也把连续的备份简化为了分段的备份。这个功能对大数据库特别有用,因为大数据库的完全备份可能并不方便。如果持续把WAL文件重放给其他装载了同样的基础备份文件的机器,就有了一套“热备份”系统:在任何点我们都可以启动第二台机器,而它拥有近乎当前的数据库拷贝。和简单的文件系统备份技术一样,这个方法只能支持整个数据集簇的恢复。

6. AutoVacuum进程

/opt/postgresql-9.5.2/src/backend/postmaster/autovacuum.c
 
 * threshold = vac_base_thresh + vac_scale_factor * reltuples
 

数据库总是不断地在执行删除,更新等操作。良好的空间管理非常重要,能够对性能带来大幅提高。在postgresql中用于维护数据库磁盘空间的工具是VACUUM,其重要的作用是删除那些已经标示为删除的数据并释放空间。
VACUUM语法结构:

 

VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table ]
VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ]

 

postgresql中执行delete操作后,表中的记录只是被标示为删除状态,并没有释放空间,在以后的update或insert操作中该部分的空间是不能够被重用的。经过vacuum清理后,空间才能得到释放。可惜的是vacuum工具不能够对相应的索引进行清理,唯一的办法就是手动去重建相应索引(令人非常不爽,而高兴的是在9.0之后有所改进)。
Full Vacuum
full vacuum与单纯的vacuum还是有很大的区别的。vacuum只是将删除状态的空间释放掉,转换到能够重新使用的状态,但是对于系统来说该数据块的空闲空间并没有反应到系统的元数据中。类似oracle中高水位标记并没有下降。Full vacuum将会使空间释放的信息表现在系统级别,其实质是将当前删除记录后面的数据进行移动,使得整体的记录连贯起来,降低了“高水位标记”。
Vacuum analyze
analyze的功能是更新统计信息,使得优化器能够选择更好的方案执行sql。oracle中同样也有analyze,作用也相同,目前更多的使用的是dbms_stats包。统计信息收集和更新对于系统性能来说非常重要,与oracle维护类似,通常可以通过采用手动或者定制任务的方式。也有不同,oracle在进行imp后自动的对相应数据对象进行统计信息的收集和更新,而postgresql的恢复过程还没有集成到里面,需要手动去执行。
自动vacuum配置
自动vacuum的执行直接由autovacuum参数值决定,默认值是on。
log_autovacuum_min_duration:默认值为-1,关闭vacuum的日志记录,配置为0表示记录autovacuum的所有log。参数设置为正整数表示对于在此时间内完成的vacuum操作不进行log记录,如果没能完成,则记录超出时间内的log。该参数对于了解对象执行vacuum操作的时间非常有用。
autovacuum_max_workers:最大的autovacuum进程的数量,默认值为3。参数大小的配置主要依据系统当前负载和资源。对于系统负载较重的情况,建议开启少量的进程为好,反之,空闲时间可以采用较大值的方式。
autovacuum_naptime:检查数据库的时间间隔。默认为1分钟。
autovacuum_vacuum_threshold:参数表示执行autovacuum操作之前,对单个表中记录执行DML操作的最少行数。达到该行数时自动激活autovacuum操作。该参数针对数据库中的所有表,还可以通过对单个表配置不同的值来改变相应表的autovacuum操作。默认值是50。
autovacuum_analyze_threshold:激活自动analyze操作的最小行数。默认值50。机制与上面相同。
autovacuum_vacuum_scale_factor:该参数采用百分比的方式设定阀值。默认值为20%,当DML涉及的数据量大于某个表的20%时,自动触发autovacuum操作。同样可以通过对单个表进行阀值设定。
autovacuum_analyze_scale_factor:机制与上面相同,到达阀值是自动激活analyze操作。同样可以通过对单个表进行阀值设定。
autovacuum_freeze_max_age:为防止事务ID的重置,在启用vacuum操作之前,表的pg_class .relfrozenxid字段的最大值,默认为200万。
autovacuum_vacuum_cost_delay:autovacuum进程的时间延迟限制,默认值是20ms。对于单个表同样适用。
autovacuum_vacuum_cost_limit:autovacuum进程的开销延迟限制,默认值是-1,表示不进行开销限制,系统将会直接依据vacuum_cost_limit参数管理vacuum的开销。对于单个表同样适用。

通过了解vacuum,能够更清楚postgresql是如何进行系统维护的,其中最为重要的是索引的维护,因为8.3中还不能对索引同时进行维护,这在实际的使用中需要耗费较大的精力,还好在9.0版本中有所改进(具体情况没有测试)。同时大致了解postgresql进行自动vacuum操作的机制与过程。

7. PgStat进程

PgStat辅助进程是PostgreSQL数据库系统的统计信息收集器,它专门负责收集数据库系统运行中的统计信息,如在一个表和索引上进行了多少次插入与更新操作、磁盘块的数量和元组的数量、每个表上最近一次执行清理和分析操作的时间,以及统计每个用户自定义函数调用执行的时间等。由于统计数据收集给查询处理增加了一些负荷,所以可以把系统配置为收集信息,也可以配置为不收集信息。系统表pg_statistic中存储了PgStat收集的各类统计信息,另外在数据库集簇的目录下有与统计信息收集器相关的文件:global子文件夹下的pgstat.stat文件用于保存当前全局的统计信息;pg_stat_tmp文件则是PgStat进程和各个后台进程进行交互的临时文件所在地。

PgStat辅助进程收集的统计信息主要用于查询优化时的代价估算。在PostgreSQL的查询优化过程中,查询请求的不同执行方案是通过建立不同的路径(Path)来表达的。在生成了许多符合条件的路径之后,从中选择出代价最小的路径转化为一个计划,这个计划将被传递给执行器执行。因此优化器的核心工作就是建立许许多多的路径,然后从中找出最优的路径。造成同一个查询请求有不同路径的主要原因是:表不同的访问方式(如顺序访问(Sequential Access)、索引访问(Index Access),PostgreSQL中还有可能使用TID直接访问元组);表间不同的连接方式(嵌套循环连接(Nest-loop join)、归并连接(Merge Join)、Hash连接(Hash Join));表间不同的连接顺序(左连接(Left-join)、右连接(Right-join)、布希连接(Bushy-join))。而评价路径优劣的依据是用系统表pg_statistic中的系统统计信息估计出的不同路径的代价。

在PostgreSQL数据库系统配置文件postgresql.conf中与PgStat相关的配置选项有:

track_activities:表示是否对会话中当前执行的命令开启统计信息收集功能,该参数只对超级用户和会话所有者可见,默认值为on(开启)。

track_counts:表示是否对数据库活动开启统计信息收集功能,由于在AutoVacuum自动清理进程中选择清理的数据库时,需要数据库的统计信息,因此该参数默认值为on。

track_function:表示是否开启函数的调用次数和调用耗时统计。

track_activity_query_size:设置用于跟踪每一个活动会话的当前执行命令的字节数,默认值为1024,只能在数据库启动后设置。

对于PgStat收集的统计信息,系统提供了部分标准视图以供管理员查看;同时,还提供了统计信息相关的操作函数,供管理员来定义视图。需要注意的是,使用的统计信息并非实时的,每个活动的独立数据库服务仅仅在处理空闲时才发送信息到统计信息收集器,因此查询或者事务仍然在处理中并未更新当前的统计信息。在设置了track_activities参数时,当前查询信息在统计信息收集器中是实时的。

8. 共享内存

PostgreSQL启动后,会生成一块共享内存,共享内存主要做数据块的缓冲区,以便提高读写新能,WAL日志缓冲区和CLOG缓冲区也存在于共享内存中,除此之外,一些全局信息也保存在共享内存中,如进程信息,锁信息,全局统计信息,等.

9. 本地内存

后台服务进程除了访问共享内存外,还会申请分配一些本地内存,以便暂存一些不需要全局存储的数据,这些内存缓冲区主要有以下几类:

  • 临时缓冲区:用于访问临时表的本地缓冲区
  • work_mem:内存排序操作和Hash表在使用临时磁盘文件之前使用的内存缓冲区.
  • maintenance_work_mem:在维护性操作(如vacuum,create index,和alter table add foreign key等)中使用的内存缓冲区.