PG-存储结构之物理存储结构2

1. 数据库物理存储结构

​ 数据库文件默认保存在initdb创建的数据目录中,它包含数据文件、参数文件、控制文件、数据库运行日志及WAL日志文件。PG不支持裸设备和块设备。

1.1 软件目录结构

$ ls -l /ups/app/pgsql-11/
total 16
drwxr-xr-x 2 root root 4096 Oct  8 15:06 bin     # 二进制可执行文件,是PG数据库的所有相关命令工具
drwxr-xr-x 6 root root 4096 Oct  8 15:06 include # 头文件目录
drwxr-xr-x 4 root root 4096 Oct  8 15:06 lib     # 动态库目录,PostgreSQL运行所需要的动态库
drwxr-xr-x 8 root root 4096 Oct  8 15:06 share   # 文档和配置模板文件,扩展插件的SQL文件在此目录下的extension中
$

pg软件目录结构

1.2 文件布局

数据库集簇所使用的配置和数据文件都被一起存储在集簇的数据目录里, 通常用PGDATA来引用。

PGDATA目录包含几个子目录以及一些控制文件

image-20200608104333698

PGDATA的内容

描述
postgresql.conf 数据库实例主要配置文件
pg_hba.conf 认证配置文件
pg_ident.conf 认证方式的用户映射文件
PG_VERSION 一个包含PostgreSQL主版本号的文件
base 包含每个数据库对应的子目录的子目录
current_logfiles 记录当前被日志收集器写入的日志文件的文件
global 包含集簇范围的表的子目录,比如pg_database
pg_commit_ts 包含事务提交时间戳数据的子目录
pg_dynshmem 包含被动态共享内存子系统所使用的文件的子目录
pg_logical 包含用于逻辑复制的状态数据的子目录
pg_multixact 包含多事务(multi-transaction)状态数据的子目录(用于共享的行锁)
pg_notify 包含LISTEN/NOTIFY状态数据的子目录
pg_replslot 包含复制槽数据的子目录
pg_serial 包含已提交的可序列化事务信息的子目录
pg_snapshots 包含导出的快照的子目录
pg_stat 包含用于统计子系统的永久文件的子目录
pg_stat_tmp 包含用于统计信息子系统的临时文件的子目录
pg_subtrans 包含子事务状态数据的子目录
pg_tblspc 包含指向表空间的符号链接的子目录
pg_twophase 包含用于2阶段提交分布式事务状态文件的子目录
pg_wal 包含 WAL (预写日志)文件的子目录
pg_xact 包含事务提交状态数据的子目录(pg9以前为pg_clog目录)-Commit log
postgresql.auto.conf 一个用于存储由ALTER SYSTEM 设置的配置参数的文件
postmaster.opts 一个记录服务器最后一次启动时使用的命令行参数的文件
postmaster.pid 一个锁文件,记录着当前的 postmaster 进程ID(PID)、集簇数据目录路径、postmaster启动时间戳、端口号、Unix域套接字目录路径(Windows上为空)、第一个可用的listen_address(IP地址或者*,或者为空表示不在TCP上监听)以及共享内存段ID(服务器关闭后该文件不存在)

对于集簇里的每个数据库,在PGDATA/base里都有一个子目录对应, 子目录的名字为该数据库在pg_database里的 OID。

1.3 数据文件

PG中,每个表和索引单独一个文件存储。对于大小超出1GB表数据文件,默认情况下,自动切分多个文件存储。用OID.<顺序号>来命名(pg_class.relfilenode.1)。

在PG中,保存在磁盘中的数据块称为page,而内存中的数据块称为Buffer,表和索引称为 Relation ,行称为 Tuple。数据的读写是以 Page 为最小单位, 每个Page 默认大小为 8kB ,在编译 PostgreSQL 时指定--with-blocksize=BLOCKSIZE选项决定 Page 的大小。每个表文件由多个 BLCKSZ 字节大小的 Page 组成, 每个 Page 包含若干 Tuple 。

1.3.1 表和索引(Page)

每个表和索引都存储在独立的文件里。对于普通关系,这些文件以表或索引的filenode号命名,它可以在pg_class.relfilenode中找到。但是对于临时关系,文件名的形式为tBBB_FFF,其中BBB是创建该文件的后台的后台ID,FFF是文件节点号。在每种情况下,在主文件(a/k/a 主分支)之外,每个表和索引有一个空闲空间映射,它存储关系中可用空闲空间的信息。空闲空间映射存储在一个文件中,该文件以节点号加上后缀_fsm命名。表还有一个可见性映射,存储在一个后缀为_vm的分支中,它用于跟踪哪些页面已知含有非死亡元组。可见性映射将在中进一步描述。不被日志记录的表和索引还有第三个分支,即初始化分支,它存储在后缀为_init的分支中。

在表或者索引超过 1GB 之后,它就被划分成1G大小的段。 第一个段的文件名和文件节点相同;随后的段被命名为 filenode.1、filenode.2等等。这样的安排避免了在某些有文件大小限制的平台上的问题(实际上,1GB只是默认的段尺寸。段尺寸可以在编译PostgreSQL时使用配置选项--with-blocksize=BLOCKSIZE进行调整)。

page的物理位置在$PGDATA/BASE/DATABASE_OID/PG_CLASS.RELFILENODE

-- 返回对象oid
SELECT relfilenode FROM pg_class WHERE relname='test_t1';
-- 返回文件路径
SELECT pg_relation_filepath('test_t1');
-- 数据根目录 $PGDATA
show data_directory;
表物理文件

每个表对应三个文件

  • 一个是存储数据的文件,文件名是该表的OID
  • 一个是管理表空闲空间的文件,文件名为OID_fsm
  • 一个是管理表块可见性的文件。文件名是OID_vm

img

  • 13881是数据库对象的OID
  • 16387是表对象的OID
数据块结构

image-20200618134852330

块头信息,它记录块中各个数据行指针,实际的数据行内容从块尾向前填充,行数据指针和行数据之间的区间时空闲空间。

块头信息包含

  • 块的checksum值
  • 空闲空间的起始地址和结束位置
  • 特殊数据的起始位置

行指针是一个32位的数字

  • 行内容的偏移量,占用15bit
  • 指针的标识,占用2bit
  • 行内容的长度,占用15bit

行指针长度15bit,其最大偏移量2^15=32768,那么块的最大大小32768.

1.3.2 Tuple布局

假设一个字节8位,item(项)指的是存储在一个页面里的独立数据值。如:在表中,一个项是一行记录;在索引中,一个项是一条索引记录。

image-20200618141857794

页面组成
描述
PageHeaderData 24字节长。包含关于页面的一般信息,包括空闲空间指针。
ItemIdData 一个记录(偏移量,长度)对的数组,指向实际项。每个项 4 字节。
Free space 未分配的空间(空闲空间)。新项指针从这个区域的开头开始分配,新项从其结尾开始分配。
Items 实际的项本身。
Special space 索引访问模式相关的数据。不同的索引访问方式存放不同的数据。在普通表中为空。

每个页面的头24个字节组成页头(PageHeaderData),在页头后面是项标识符(ItemIdData),每个占用四个字节。

PageHeaderData布局

类型 长度 描述
pd_lsn PageXLogRecPtr 8 bytes LSN: 最后修改这个页面的WAL记录最后一个字节后面的第一个字节
pd_checksum uint16 2 bytes 页面校验码
pd_flags uint16 2 bytes 标志位
pd_lower LocationIndex 2 bytes 到空闲空间开头的偏移量
pd_upper LocationIndex 2 bytes 到空闲空间结尾的偏移量
pd_special LocationIndex 2 bytes 到特殊空间开头的偏移量
pd_pagesize_version uint16 2 bytes 页面大小和布局版本号信息
pd_prune_xid TransactionId 4 bytes 页面上最老未删除XMAX,如果没有则为0

所有细节都可以在src/include/storage/bufpage.h中找到。

表和序列都使用一种叫做 HeapTupleHeaderData的结构。

表行组成

行有一个定长的头部(在大多数机器上占据 23 个字节), 后面跟着一个可选的空值位图、一个可选的对象 ID 域以及用户数据。

HeapTupleHeaderData结构:

类型 长度 描述
t_xmin TransactionId 4 bytes 插入XID标志
t_xmax TransactionId 4 bytes 删除XID标志
t_cid CommandId 4 bytes 插入和/或删除CID标志(覆盖t_xvac)
t_xvac TransactionId 4 bytes VACUUM操作移动一个行版本的XID
t_ctid ItemPointerData 6 bytes 当前版本的TID或者指向更新的行版本
t_infomask2 uint16 2 bytes 一些属性,加上多个标志位
t_infomask uint16 2 bytes 多个标志位
t_hoff uint8 1 byte 到用户数据的偏移量

所有细节都可以在src/include/access/htup_details.h中找到

空闲空间映射

每一个堆和索引关系(除了哈希索引)都有一个空闲空间映射(Free Space Map-FSM)来保持对关系中可用空间的跟踪。它伴随着主关系数据被存储在一个独立的关系分支中,以关系的文件节点号加上一个_fsm后缀命名。例如,如果一个关系的文件节点是12345,那么FSM被存储在一个名为12345_fsm的文件中,该文件与主关系文件在同一个目录中。

空闲空间映射被组织成一棵FSM页面的树。底层FSM页面存储了在每一个堆(或索引)页面中可用的空闲空间,对于每一个这样的页面使用一个字节来表示。上层FSM页面则聚集来自于下层页面的信息。

在每一个FSM页面中是一个二叉树,存储在一个数组中,每一个节点一个字节。每个叶节点表示一个堆页面或者一个下层FSM页面。在每一个非叶节点中存储了它孩子节点中的最大值。因此叶节点中的最大值被存储在根中。

关于如何构建、更新和搜索FSM的更多信息请参考src/backend/storage/freespace/README。pg_freespace()函数可以用来检查存储在空闲空间映射中的信息。

-- 根据 FSM,返回由blkno指定的关系页面上的空闲空间总量。
pg_freespace(rel regclass IN, blkno bigint IN) 返回 int2

-- 根据 FSM,显示关系的每个页面上的空闲空间总量。
pg_freespace(rel regclass IN, blkno OUT bigint, avail OUT int2)

-- 示例
SELECT * FROM pg_freespace('student');

可见性映射

每一个堆关系都有一个可见性映射(VM)用来跟踪只包含已知对所有活动事务可见的元组或未被冻结的元组的页面。它随着主关系数据被存储在一个独立的关系分支中, 以该关系的文件节点号加上一个_vm后缀来命名。例如, 如果一个关系的文件节点为12345,其VM被存储在名为12345_vm的文件中, 该文件域主关系文件在同一个目录中。注意索引没有VM。

可见性映射仅为每个堆页面存储两个位。第一位如果被设置,表示该页面上的元组都是可见的,或者换句话说该页面不含有任何需要被清理的元组。这些信息也可以被使用覆盖索引返回查询结果。第二位如果被设置,表示该页面上的元组都已经被冻结。这也意味着防回卷清理操作也不需要重新访问该页面。

pg_visibility模块可以被用来检查存储在可见性映射中的信息。

Lazy VACUUM使用可见性映射文件中标识的数据块进行快速数据清理工作。

1.4 控制文件

它主要包含初始化静态信息、WAL及检查点的动态信息、一些配置信息

控制文件路径

# 控制文件路径
ls -tr ${PGDATA}/global/pg_control

查看控制文件内容

# 查看控制文件内容
${PGHOME}/bin/pg_controldata

clipboard

1.5 日志文件

1.5.1 文件种类

  • PG运行日志:服务运行过程日志信息(文件路径:$PGDATA/log/*.log,pg10之前为$PGDATA/pg_log)
  • WAL日志:记录数据变更记录($PGDATA/pg_wal/,pg10之前为$PGDATA/pg_xlog
  • 事务提交日志:($PGDATA/pg_xact/,pg10之前为$PGDATA/pg_clog

运行日志

默认没有开启,运行日志记录服务器与DB的状态,比如各种Error信息,定位慢查询SQL,数据库的启动关闭信息,发生checkpoint过于频繁等的告警信息。该日志有.csv格式和.log。

image-20210913112332738

1.5.2 WRITE-AHEAD LOG(WAL)

重做日志是数据库的重要组成部分,存储了数据库系统中所有更改和操作的历史,以确保数据库不会因为故障(例如掉电或其他导致服务器崩溃的故障)而丢失数据。在PostgreSQL(以下简称PG)中,重做日志文件称为Write Ahead Log(以下简称WAL),在较低(pg10以前)的版本中,也称为xlog。当数据库异常退出时,在数据库重启后,通过读取wal日志前滚操作确保数据一致性和完整性。

1.5.2.1 相关术语
  • REDO log:通常称为重做日志,在写入数据文件前,每个变更都会先行写入到Redo log中。其用途和意义在于存储数据库的所有修改历史,用于数据库故障恢复(Recovery)、增量备份(Incremental Backup)、PITR(Point In Time Recovery)和复制(Replication)。

  • WAL segment file:为了便于管理,PG把事务日志文件划分为N个segment,每个segment称为WAL segment file,每个WAL segment file大小默认为16MB。

  • XLOG Record:这是一个逻辑概念,可以理解为PG中的每一个变更都对应一条XLOG Record,这些XLOG Record存储在WAL segment file中。PG读取这些XLOG Record进行故障恢复/基于时间点恢复(point-in-time recovery-PITR)等操作。

  • WAL buffer:WAL缓冲区,不管是WAL segment file的header还是XLOG Record都会先行写入到WAL缓冲区中,在"合适的时候"再通过WAL writer写入到WAL segment file中。

  • LSN:LSN即日志序列号Log Sequence Number。表示XLOG record记录写入到事务日志中位置。LSN的值为无符号64位整型(uint64)。在事务日志中,LSN单调递增且唯一。

  • checkpointer:checkpointer是PG中的一个后台进程,该进程周期性地执行checkpoint。当执行checkpoint时,该进程会把包含checkpoint信息的XLOG Record写入到当前的WAL segment file中,该XLOG Record记录包含了最新Redo pint的位置。

  • checkpoint:检查点checkpoint由checkpointer进程执行,主要的处理流程如下:

    • 获取Redo point,构造包含此Redo point检查点(详细请参考Checkpoint结构体)信息的XLOG Record并写入到WAL segment file中;
    • 刷新Dirty Page到磁盘上;
    • 更新Redo point等信息到pg_control文件中。
  • REDO point:REDO point是PG启动恢复的起始点,是最后一次checkpoint启动时事务日志文件的末尾亦即写入Checkpoint XLOG Record时的位置(这里的位置可以理解为事务日志文件中偏移量)。

1.5.2.2 存储路径

wal默认保存在pg_wal文件夹(低版本保持在wal_log/pg_xlog)中,该目录会产生多个wal日志,每个wal日志默认16M, 不需要的过期wal日志会被自动覆盖掉。数据库初始化($PGHOME/bin/initdb)时,通过参数--wal-segsize指定每个日志文件大小

ls -rt $PGDATA/pg_wal
total 131076
-rw------- 1 postgres postgres 16777216 Oct 31  2019 000000010000000000000001
-rw------- 1 postgres postgres 16777216 Oct 31  2019 000000010000000000000002
-rw------- 1 postgres postgres      337 Oct 31  2019 000000010000000000000002.00000028.backup
-rw------- 1 postgres postgres 16777216 Oct 31  2019 000000010000000000000003
-rw------- 1 postgres postgres 16777216 Nov  7  2019 000000010000000000000004
-rw------- 1 postgres postgres 16777216 Mar  8 11:48 000000010000000000000005
-rw------- 1 postgres postgres 16777216 May 12 10:37 000000010000000000000006
-rw------- 1 postgres postgres 16777216 Jun  4 10:17 000000010000000000000007
-rw------- 1 postgres postgres 16777216 May 30 09:41 000000010000000000000008
drwx------ 2 postgres postgres      281 May 12 10:37 archive_status

相关配置参数
wal_level

wal_level用于确定将多少信息写入WAL。 每个级别都包含所有较低级别记录的信息。 此参数只能在服务器启动时设置。

  • replica: (默认值) 它写入足够的数据以支持WAL归档和复制,包括在备用服务器上运行只读查询
  • minimal:删除除崩溃或立即关闭所需的信息之外的所有日志记录
  • logical: 添加了支持逻辑解码所需的信息
fsync

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

关闭fsync的安全环境示例包括从备份文件初始加载新数据库集群,使用数据库集群处理一批数据,之后将丢弃并重新创建数据库,或者使用只读数据库经常重新创建并且不用于故障转移的克隆。单独使用高质量硬件并不足以成为关闭fsync的理由。

为了在将fsync更改为on时可靠恢复,必须强制内核中的所有已修改缓冲区为持久存储。这可以在群集关闭时或通过运行initdb --sync-only,运行同步,卸载文件系统或重新启动服务器来启用fsync时完成。

在许多情况下,关闭非关键事务的synchronous_commit可以提供关闭fsync的大部分潜在性能优势,而不会伴随数据损坏的风险。

fsync只能在postgresql.conf文件或服务器命令行中设置。如果关闭此参数,还请考虑关闭full_page_writes。

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

启用此参数后, PostgreSQL 服务器会在检查点之后第一次修改该页面时将每个磁盘页面的全部内容写入 WAL 。

这是必需的,因为在操作系统崩溃期间正在处理的页面写入可能仅部分完成,从而导致包含旧数据和新数据混合的磁盘上页面。

通常存储在 WAL 中的行级更改数据将不足以在崩溃后恢复期间完全恢复此类页面。 存储整页图像可确保页面可以正确恢复,但代价是增加必须写入 WAL 的数据量。 (因为 WAL 重放总是从检查点开始,所以在检查点之后的每个页面的第一次更改期间执行此操作就足够了。因此,降低整页写入成本的一种方法是增加检查点间隔参数。)

关闭此参数可加快正常操作,但可能会在系统出现故障后导致无法恢复的数据损坏或静默数据损坏。 风险类似于关闭 fsync ,虽然较小,但应仅根据为该参数推荐的相同情况关闭。

关闭此参数不会影响使用 WAL 归档进行时间点恢复( PITR )(请参阅第 25.3 节)。

此参数只能在 postgresql.conf 文件或服务器命令行中设置。 默认打开。

wal_log_hints

当此参数打开时,PostgreSQL服务器会在检查点之后第一次修改该页面时将每个磁盘页面的全部内容写入WAL,即使对于所谓的提示位的非关键修改也是如此。

如果启用了数据校验和,则提示位更新始终是WAL记录的,并忽略此设置。 您可以使用此设置来测试如果您的数据库启用了数据校验和,将会发生多少额外的WAL日志记录。

此参数只能在服务器启动时设置。 默认值为off。

wal_compression

When this parameter is on , the PostgreSQL server compresses a full page image written to WAL when is on or during a base backup. A compressed page image will be decompressed during WAL replay. The default value is off . Only superusers can change this setting.

Turning this parameter on can reduce the WAL volume without increasing the risk of unrecoverable data corruption, but at the cost of some extra CPU spent on the compression during WAL logging and on the decompression during WAL replay.

wal_writer_delay

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

请注意,在许多系统上,睡眠延迟的有效解决方案是 10 毫秒;将 wal-writer-delay 设置为不是 10 的倍数的值可能与将其设置为 10 的下一个更高倍数的结果相同。此参数只能在 postgresql.conf 文件或服务器命令行中设置。

wal_writer_flush_after

Specifies how often the WAL writer flushes WAL. If the last flush happened less than wal_writer_delay milliseconds ago and less than wal_writer_flush_after bytes of WAL have been produced since, then WAL is only written to the operating system, not flushed to disk. If wal_writer_flush_after is set to 0 then WAL data is flushed immediately. The default is 1MB . This parameter can only be set in the postgresql.conf file or on the server command line.

commit_delay

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

commit_siblings

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

1.5.2.3 WAL文件组成

PG使用无符号64bit整型(uint64)作为事务日志文件的寻址空间,理论上,PG的事务日志空间最大为2^64Bytes(即16EB)。假设每天可以产生16TB的日志文件,那么要达到事务日志文件大小的上限需要的时间是1024*1024/365天≈2800年。

对于16EB的文件,OS是无法高效管理的,为此,PG把事务日志文件划分为N个大小为16M(默认值)的WAL segment file。

img

WAL segment file命名日志格式
  • TimeLineID:取值范围是0x00000000 -> 0xFFFFFFFF
  • 逻辑文件ID:取值范围是0x00000000 -> 0xFFFFFFFF
  • 物理文件ID:取值范围是0x00000000 -> 0x000000FF

逻辑文件ID、物理文件ID和文件大小这三部分的组合,实现了64bit的寻址空间:

  • 逻辑文件ID是32bit的uint32(unsigned int 32bit)

  • 物理文件ID是8bit的unit8

  • 16M的文件大小是24bit的unit24

三者共同组成unit64(32+8+24),达到最大64bit的文件寻址空间。

LSN(事务日志偏移位置)

事务日志文件的LSN表示XLOG Record记录写入到事务日志文件中的位置。LSN可以理解为XLOG Record在事务日志文件中的偏移(Offset)。

LSN由3部分组成,分别是逻辑文件ID,物理文件ID和文件内偏移。如LSN:1/4288E228,其中1为逻辑文件ID,42为物理文件ID,88E228为WAL segment file文件内偏移(注:16MB大小的WAL Segment file需要3 Bytes的寻址空间)。

按此规则,给定一个LSN,很容易根据LSN号推算得到其对应的日志文件(假定时间线TimeLineID为1)。

如:LSN 1/4288E228对应的WAL segment file文件为00000001 00000001 00000042,该文件名称的前8位为时间线ID(00000001),中间8位(00000001)为逻辑文件ID,最后8位(00000042)为物理文件ID。

# 命名格式(文件名称由24个16进制[0~F]的字符组成,每8个字符一组,每组的意义如下:
00000001 00000000 00000001
-------- -------- -------- 
时间线     逻辑id    物理id

# 查看当前日志LSN
SELECT pg_walfile_name(pg_current_wal_lsn()), pg_current_wal_lsn();

# 查看当前日志LSN
postgres->postgres@[local]:5432=# SELECT pg_current_wal_lsn();
 pg_current_wal_lsn 
--------------------
 0/7DD2E70
(1 row)
# LSN对应的文件名称
postgres->postgres@[local]:5432=# SELECT pg_walfile_name(pg_current_wal_lsn());
     pg_walfile_name      
--------------------------
 000000010000000000000007
(1 row)

# WAL段文件
postgres->postgres@[local]:5432=# SELECT name, size, to_char(modification,'yyyy-MM-dd HH24-MI-SS') as modification FROM pg_ls_waldir() ORDER BY modification;
                   name                   |   size   |    modification     
------------------------------------------+----------+---------------------
 000000010000000000000001                 | 16777216 | 2019-10-31 10-10-52
 000000010000000000000002                 | 16777216 | 2019-10-31 10-10-53
 000000010000000000000002.00000028.backup |      337 | 2019-10-31 10-10-53
 000000010000000000000003                 | 16777216 | 2019-10-31 10-31-17
 000000010000000000000004                 | 16777216 | 2019-11-07 13-33-21
 000000010000000000000005                 | 16777216 | 2020-03-08 11-48-24
 000000010000000000000006                 | 16777216 | 2020-05-12 10-37-35
 000000010000000000000008                 | 16777216 | 2020-05-30 09-41-19
 000000010000000000000007                 | 16777216 | 2020-06-04 10-17-25
(9 rows)

postgres->postgres@[local]:5432=#

# pg_waldump查看wal文件内容
${PGHOME}/bin/pg_waldump 000000010000000000000007|more

# 切换Wal日志
SELECT pg_switch_wal();
1.5.2.4 WAL Segment file内部结构

WAL segment file内部划分为N个page(Block),每个page大小为8192 Bytes即8K,每个WAL segment file第1个page的header在PG源码中相应的数据结构是XLogLongPageHeaderData,后续其他page的header对应的数据结构是XLogPageHeaderData。在一个page中,page header之后是N个XLOG Record。

img

XLOG Record

XLOG Record由两部分组成

  • 第一部分是XLOG Record的头部信息,大小固定(24 Bytes),对应的结构体是XLogRecord;
  • 第二部分是XLOG Record data。

XLOG Record按存储的数据内容来划分,大体可以分为三类:

  1. Record for backup block:存储full-write-page的block,这种类型Record是为了解决page部分写的问题。在checkpoint完成后第一次修改数据page,在记录此变更写入事务日志文件时整页写入(需设置相应的初始化参数,默认为打开);
  2. Record for tuple data block:存储page中的tuple变更,使用这种类型的Record记录;
  3. Record for Checkpoint:在checkpoint发生时,在事务日志文件中记录checkpoint信息(其中包括Redo point)。

其中XLOG Record data是存储实际数据的地方,由以下几部分组成:

  1. 0..N个XLogRecordBlockHeader,每一个XLogRecordBlockHeader对应一个block data;
  2. XLogRecordDataHeader[Short|Long],如数据大小<256 Bytes,则使用Short格式,否则使用Long格式;
  3. block data:full-write-page data和tuple data。对于full-write-page data,如启用了压缩,则数据压缩存储,压缩后该page相关的元数据存储在XLogRecordBlockCompressHeader中;
  4. main data: /checkpoint等日志数据

以INSERT数据为例,在插入数据时的XLOG Record data内部结构如下图所示:

img

1.5.2.5 WAL segment file内容剖析
(linux) hexdump工具查看WAL文件中的内容
hexdump -C $PGDATA/pg_wal/000000010000000000000008
pg_waldump工具

PG已提供了dump事务日志的工具:pg_waldump用于查看事务日志文件中的内容

${PGHOME}/bin/pg_waldump --help
pg_waldump decodes and displays PostgreSQL write-ahead logs for debugging.

Usage:
  pg_waldump [OPTION]... [STARTSEG [ENDSEG]]

Options:
  -b, --bkp-details      output detailed information about backup blocks
  -e, --end=RECPTR       stop reading at WAL location RECPTR
  -f, --follow           keep retrying after reaching end of WAL
  -n, --limit=N          number of records to display
  -p, --path=PATH        directory in which to find log segment files or a
                         directory with a ./pg_wal that contains such files
                         (default: current directory, ./pg_wal, $PGDATA/pg_wal)
  -r, --rmgr=RMGR        only show records generated by resource manager RMGR;
                         use --rmgr=list to list valid resource manager names
  -s, --start=RECPTR     start reading at WAL location RECPTR
  -t, --timeline=TLI     timeline from which to read log records
                         (default: 1 or the value used in STARTSEG)
  -V, --version          output version information, then exit
  -x, --xid=XID          only show records with transaction ID XID
  -z, --stats[=record]   show statistics instead of records
                         (optionally, show per-record statistics)
  -?, --help             show this help, then exit

查看文件pg_wal/000000010000000000000007内容,对应起始LSN:0/07000000

# 查看最早的4个XLOG Record
${PGHOME}/bin/pg_waldump -p $PGDATA/pg_wal -s 0/07000000 -n 4
WAL日志解析工具(wal2json)

wal2json作为红帽开源项目Debezium的组成部分,用于提供基于PG库级别的DML日志挖掘工作。

Debezium 目地在于提供一个分布式的平台,将数据库日志中的事件记录转化为事件流,使得外部应用能够对数据库中的行级操作做出快速响应。Debezium 可以建立在Apache kafka的上层,为kafka connect提供可兼容的连接器用于监控和管理特定的数据库。

# 1. 软件地址
Debezium针对几类主流数据库的连接器:https://debezium.io/docs/connectors/
Wal2json安装页面:https://debezium.io/docs/install/postgres-plugins/

# 2.部署配置
$ git clone https://github.com/eulerto/wal2json -b master --single-branch \
&& cd wal2json \
&& git checkout d2b7fef021c46e0d429f2c1768de361069e58696 \
&& make && make install \
&& cd .. \
&& rm -rf wal2json

# 3. 参数配置 postgresql.conf
############ REPLICATION ##############
# MODULES
shared_preload_libraries = 'wal2json'   

# REPLICATION
wal_level = logical                     
max_wal_senders = 4                     
max_replication_slots = 4 

# 4. 创建具有Replication和Login授权的用户
CREATE ROLE name REPLICATION LOGIN;

# 5. pg_hba.conf 远程或本地访问数据库
############ REPLICATION ##############
local   replication     postgres                          trust		
host    replication     postgres  127.0.0.1/32            trust		
host    replication     postgres  ::1/128                 trust		

# 6. 测试环境
# 6.1 连接窗口1创建数据
CREATE DATABASE test;

CREATE TABLE test_table (
    id char(10) NOT NULL,
    code        char(10),
    PRIMARY KEY (id)
);

# 新建一个连接窗口2
# 6.2 创建复制槽 test_slot
pg_recvlogical -d test --slot test_slot --create-slot -P wal2json
# 6.3 接收变更数据输出
pg_recvlogical -d test --slot test_slot --start -o pretty-print=1 -f -

# 6.4 回到连接窗口1 插入数据
test=# INSERT INTO test_table (id, code) VALUES('id1', 'code1');
INSERT 0 1
test=# update test_table set code='code2' where id='id1';
UPDATE 1
test=# delete from test_table where id='id1';
DELETE 1

# 6.5 连接窗口2查看输出

切换wal日志
select pg_switch_wal();
当前日志
select pg_walfile_NAME_OFFSET(pg_current_wal_lsn());
select pg_current_wal_lsn();
-- 10.0以后版本
select pg_current_wal_lsn(),
     pg_walfile_name(pg_current_wal_lsn()),             
     pg_walfile_name_offset(pg_current_wal_lsn());

-- 10.0以前版本
select pg_current_xlog_location(),
     pg_xlogfile_name(pg_current_xlog_location()),
     pg_xlogfile_name_offset(pg_current_xlog_location());

image-20210913123601912

  • pg_current_wal_lsn():获得当前wal日志写入位置。
  • pg_walfile_name(lsn pg_lsn): 转换wal日志位置为文件名。
  • pg_walfile_name_offset(lsn pg_lsn): 返回转换后的wal日志文件名和偏移量。
LSN与WAL 名称

LSN: 0/16E8370

  • LSN 由三部分组成: 0 1 6E8370

1.5.3 事务提交日志(pg_xact)

pg_xact是事务提交日志,记录了事务的元数据。默认开启。内容一般不能直接读。默认存储在目录$PGDATA/pg_xact/

1.5.4 归档日志

将wal日志文件归档存放到指定的一个目录。

参数文件

  • postgresql.conf:默认在$PGDATA下。在9.6后,支持通过alter system命令修改参数配置,而alter命令修改的参数保存在$PGDATA/postgresql.auto.conf文件中。

  • pg_hba.conf:客户端登录认证配置文件

  • pg_ident.conf:用户映射配置文件,用来配置哪些操作系统用户可以映射为数据库用户。结合pg_hba.conf中,method为ident可以用特定的操作系统用户和指定的数据库用户登录数据库。

posted @ 2021-09-13 21:34  KuBee  阅读(1258)  评论(0编辑  收藏  举报