需熟悉的,PG数据库运行机制
文章目录
1.数据库时间线,lsn,wal日志:
简单来说,wal文件的命名是由时间线、lsn
组成的,其中lsn由日志id和偏移量
组成。时间线
是记录数据库变更操作的标识,lsn
是WAL日志唯一的、全局的标识。
LSN与数据恢复:
从检查点(数据落盘的点)
开始,回放WAL日志,如果数据页面的LSN小于WAL记录的LSN
,则说明数据页面比较旧,需要进行回放,反之则不需要回放,就会跳过回放过程。
1.1、数据库时间线
是记录数据库变更操作的标识
,每个实例都有一个递增的数字表示时间线,作用是数据复制时确定数据的变更顺序和一致性,新主库会创建新的时间线保证数据一致性。
会改变时间线的情况:
- 数据库恢复完成后,自动另起一条个时间线
- 数据库重启
1.2、lsn
LSN即Log sequence number
,日志序列号,这是WAL日志
唯一的、全局的标识。
获取当前lsn号
postgres=# select pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
12/6000148
(1 row)
wal日志LSN编号规则
:
12/6000148(高32位/低32位)
LSN 形式为 X/Y
,其中 X 是 Log File Number,Y 是 Byte Offset(通常是从文件的开始位置算起的偏移量,以字节为单位)。
1.3、wal日志
WAL日志的命名说明如下:
一个段文件的名称由24个十六进制组成
,分为三个部分
,每个部分由8个十六进制字符组成。第一部分表示时间线
,第二部分表示日志文件标号
,第三部分表示日志文件的段标号
。时间线由1开始,日志文件标号和日志文件的段标号由0开始。
postgres=# select pg_walfile_name('12/6000148');
pg_walfile_name
--------------------------
000000390000001200000006
(1 row)
postgres=#
### 偏移量计算
postgres=# select pg_walfile_NAME_OFFSET(pg_current_wal_lsn());
pg_walfile_name_offset
--------------------------------
(000000390000001200000006,328)
(1 row)
postgres=# select pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
12/6000148
(1 row)
postgres=# select x'148'::int;
int4
------
328
(1 row)
postgres=#
wal命名规则
:
wal日志的logseg前6位始终是0,后两位是LSN低32位/16MB(2*24)
LSN在wal日志中的偏移量即LSN低32位中后24位对应的十进制值。即LSN的前两位。如上例中logseg最后两位是06,LSN低32前两位也是06。
组成部分 | 十六进制值 | 十进制值 | 说明 |
---|---|---|---|
文件前缀 | 00000039 | 57 | 表示timeline; |
日志段号 | 00000012 | 18 | 表示logid; |
文件内偏移 | 00000006 | 6 | 表示表示logseg |
2.检查点、数据恢复
简单来说,如果要进行数据恢复,需要找到最近一次数据落盘的点(检查点),然后根据归档日志的信息重放sql操作,使其达到数据一致
- 检查点是
checkpoint进程
刷脏的记录点,说明这个点之前的数据已经落盘,这个点之前的wal日志都不需要了,不需要关心两个检查点之间的wal日志。 - 数据恢复:恢复数据库需要找到上一次落盘的点,根据上次落盘的点,去通过wal日志回放,wal日志是否完整是数据库自身保证的,只要事务提交了,那么wal日志一定会写入磁盘(也有不正常关机的情况)
1、检查点发生时,checkpoint进程会把共享缓冲区中的`脏块写入磁盘`,永久保存,以防主机断电等故障导致的数据丢失。此`频率由参数checkpoint_timeout`控制。
2、实例恢复:当实例发生崩溃后,再次启动需进行实例恢复,此时数据库将以`最近一次ckpt的位置`作为参考,开始`recovery`。
3、介质恢复:`物理备份时都会生成一个检查点`,用于判断将来恢复时的起点。开始备份的位置就是`将来recovery的位置`,在wal归档日志的帮助下使其同步。
3.复制槽:
-
本质上是一个内存,
主库保证不会删除备库(逻辑复制订阅端)没有应用的日志
。 -
可以通过
pg_replication_slots
动态视图的Active
字段记录从库(订阅端)状态,可以在动态视图pg_stat_replication
上查看sent_lsn
和reply_lsn
,而且,replication slot 的状态信息是持久化保存的
,即便从库断掉或主库重启,这些信息仍然不会丢掉或失效。
3.1、查看主备、库之间的复制延迟
核心思路就是:
- 在主库上对比
sent_lsn
和replay_lsn
之间的差值,使用函数pg_wal_lsn_diff(sent_lsn ,replay_lsn)
。 - 在备库上去对比内置函数
pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()
。
postgres=# select * from pg_stat_replication;
-[ RECORD 1 ]----+------------------------------
pid | 3512
usesysid | 16388
usename | repuser
application_name | postgresql110
client_addr | 192.168.6.110
client_hostname |
client_port | 7291
backend_start | 2024-08-19 13:20:31.400284+08
backend_xmin |
state | streaming
sent_lsn | 12/6000148
write_lsn | 12/6000148
flush_lsn | 12/6000148
replay_lsn | 12/6000148
write_lag |
flush_lag |
replay_lag |
sync_priority | 0
sync_state | async
reply_time | 2024-08-19 17:19:28.043247+08
postgres=#
#### 在备库上
SELECT
pg_last_wal_receive_lsn() AS received_lsn,
pg_last_wal_replay_lsn() AS replayed_lsn,
pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()) AS wal_delay_bytes
FROM
pg_stat_wal_receiver;
postgres=# SELECT
postgres-# pg_last_wal_receive_lsn() AS received_lsn,
postgres-# pg_last_wal_replay_lsn() AS replayed_lsn,
postgres-# pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()) AS wal_delay_bytes
postgres-# FROM
postgres-# pg_stat_wal_receiver;
received_lsn | replayed_lsn | wal_delay_bytes
--------------+--------------+-----------------
12/6000148 | 12/6000148 | 0
(1 row)
postgres=#
###在主库上
SELECT
client_addr,
pg_current_wal_lsn() AS current_lsn,
sent_lsn,
replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS wal_delay_bytes
FROM
pg_stat_replication;
postgres=# SELECT
postgres-# client_addr,
postgres-# pg_current_wal_lsn() AS current_lsn,
postgres-# sent_lsn,
postgres-# replay_lsn,
postgres-# pg_wal_lsn_diff(sent_lsn, replay_lsn) AS wal_delay_bytes
postgres-# FROM
postgres-# pg_stat_replication;
-[ RECORD 1 ]---+--------------
client_addr | 192.168.6.110
current_lsn | 12/6000148
sent_lsn | 12/6000148
replay_lsn | 12/6000148
wal_delay_bytes | 0
postgres=#
postgres=#
pg_replication_slots
视图中的字段解释整理成表格格式。以下是一个示例表格:
字段 | 描述 | 示例值 |
---|---|---|
slot_name | 复制槽的名称 | fd |
plugin | 用于逻辑复制的插件名称 | logical1 |
slot_type | 复制槽的类型,physical 或 logical | logical |
datoid | 数据库的 OID(对象标识符) | 5 |
database | 逻辑复制槽所关联的数据库名称 | postgres |
temporary | 指示复制槽是否是临时的 | f |
active | 指示复制槽是否处于活动状态 | f |
active_pid | 当前与复制槽关联的进程 ID(PID) | 空 |
xmin | 复制槽所关心的最小事务 ID(逻辑复制槽通常为空) | 空 |
catalog_xmin | 系统目录中最小的事务 ID,逻辑复制槽需要保留这些事务 ID | 57755 |
restart_lsn | 复制槽重新启动时的 WAL 位置 | 11/F6000180 |
confirmed_flush_lsn | 复制槽确认已被消费的 WAL 位置 | 11/F60001B8 |
wal_status | 表示 WAL 的状态(通常为 reserved ) | reserved |
safe_wal_size | 与复制槽相关的安全 WAL 大小(逻辑复制槽通常为空) | 空 |
two_phase | 指示是否支持两阶段提交(通常与物理复制相关) | f |
conflicting | 指示是否存在与复制槽相关的冲突 | f |
3.2、查看wal的堆积情况
总结:使用pg_current_wal_insert_lsn()
获取PostgreSQL 当前正在写入的 WAL 位置与restart_lsn
复制槽开始复制的位置之间的差值。
引起这种情况的原因:
VACUUM
风暴,通常是指在数据库中同时发生大量的自动或手动VACUUM操作,这些操作可能会产生大量WAL日志,从而导致I/O负载
显著增加,影响数据库性能,同时也可能导致WAL无法被及时清理。- 大量的数据写入,大规模写入时出现的情况与VACUUM风暴相似。
- wal_keep_size和max_wal_size参数设置过大都可能会导致实例保留大量WAL日志
- 备库的wal堆积, 只读库上有长事务运行,
hot_standby_feedback和max_standby_streaming_delay
(hot_standby_feedback 控制是否在从库查询时发送反馈给主库,防止主库丢弃可能还需要的 WAL 数据。
max_standby_streaming_delay 设定从库允许的最大 WAL 数据延迟时间,以便在处理长时间运行的查询时保持同步)设置不合理。
pg_stat_archiver
视图可以查看关于 WAL 日志归档的统计信息,包括归档进度和错误信息。
postgres=# SELECT * FROM pg_stat_archiver;
-[ RECORD 1 ]------+---------------------------------
archived_count | 13
last_archived_wal | 0000003800000011000000FE.partial
last_archived_time | 2024-08-19 11:03:11.469306+08
failed_count | 0
last_failed_wal |
last_failed_time |
stats_reset | 2024-08-15 09:54:55.715856+08
参数说明
参数 | 说明 | 实际值 |
---|---|---|
archived_count | 已成功归档的 WAL 文件数量。 | 13 |
last_archived_wal | 最后一次成功归档的 WAL 文件名称。 | 0000003800000011000000FE.partial |
last_archived_time | 最后一次成功归档的时间戳。 | 2024-08-19 11:03:11.469306+08 |
failed_count | 归档失败的 WAL 文件数量。 | 0 |
last_failed_wal | 最后一次归档失败的 WAL 文件名称(如果有)。 | (空) |
last_failed_time | 最后一次归档失败的时间戳(如果有)。 | (空) |
stats_reset | 统计信息最后重置的时间戳。 | 2024-08-15 09:54:55.715856+08 |
这个表格展示了 pg_stat_archiver
视图中各个参数的说明和查询结果的实际值。
查询复制槽上的数据堆积量
postgres=# SELECT slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_insert_lsn(), restart_lsn)) AS delay_size
FROM pg_replication_slots;
slot_name | delay_size
---------------+------------
fdmq2 | 256 MB
fdmq1 | 256 MB
logicreplia | 256 MB
postgresql110 | 0 bytes
(4 rows)
postgres=#
参数说明:
参数 | 描述 |
---|---|
slot_name | 复制槽的名称。 |
pg_current_wal_insert_lsn() | 当前 WAL 插入的位置(Log Sequence Number, LSN)。表示 PostgreSQL 当前正在写入的 WAL 位置。 |
restart_lsn | 复制槽的重启位置。对于逻辑复制槽,表示从这个位置开始重新进行数据流式复制。 |
pg_wal_lsn_diff() | 计算两个 LSN 之间的差异(即 WAL 的堆积量)。在本查询中,计算 pg_current_wal_insert_lsn() 和 restart_lsn 之间的差异。 |
pg_size_pretty() | 将字节数转换为更易读的格式(例如,KB、MB 或 GB)。用于将计算得到的 WAL 差异转换为可读格式。 |
delay_size | WAL 延迟大小,表示自复制槽的重启位置到当前 WAL 插入位置的 WAL 数据量。 |
在 PostgreSQL 中,pg_current_wal_lsn()
和 pg_current_wal_insert_lsn()
这两个函数有以下区别:
pg_current_wal_lsn()
: 返回当前的 WAL(Write-Ahead Log)插入位置。它反映了数据库当前正在写入 WAL 的位置。pg_current_wal_insert_lsn()
: 这个函数用于逻辑复制槽,返回当前的 WAL 插入位置与复制槽的起始位置之间的差异。它用于监视逻辑复制槽的进度。
总的来说,pg_current_wal_lsn()
是用于一般情况的函数,而 pg_current_wal_insert_lsn()
主要用于逻辑复制的监控。
4、PG生产环境,故障转移
总结:这里主要介绍了Patroni集群环境参数配置,和主备切换、故障转移保证数据不丢失的原理。
生产环境,参数配置需要注意的地方
-
一定注意
shared_buffers
和max_connections
这两个参数,数据库最大连接一般默认100到200 。 -
一个连接就是一个单独的进程,这些进程都会去数据库里跑SQL,都会占用共享内存,64核心的cpu物理机,配置的数据库连接数过大会影响性能。
-
Patroni集群中的全局参数,涉及到数据一致性和复制要求,必须在所有节点上保持一致(比如:
wal_level 和 max_wal_senders
直接影响到 WAL 日志的生成和传输)。 -
Patroni集群中的本地参数,则可以根据各个节点的具体需求进行调整(比如:通过为
从节点配置更大的 work_mem 来优化查询性能
)
Patroni保证高可用和故障转移
高可用性
是指系统能够在较长时间内持续正常运行
,并能在出现故障时迅速恢复。故障转移
是指在主节点发生故障时,自动将服务切换到一个健康的从节点,以保证服务的连续性
。
高可用直观表现为:
系统故障时间越短,故障次数间隔时间越长,高可用越好。
可用性 = 工作时间 /(工作时间 + 停机时 间)
Patroni通过流复制机制
实现异步或同步的高可用切换策略。它允许设置“maximum_lag_on_failover”
,确保在主节点故障时,备节点的数据滞后不会超出预定阈值。- Patroni可以检测节点状态,把节点状态信息存入到
zookeeper
中,当Leader
节点不可用时,Patroni会自动触发选举过程,选择一个Replica
节点作为新的Leader
。
Patroni的主备切换和故障转移,不丢失数据的原理如下:
- 1、如果是在Patroni的主、备库流复制之间
发生主备切换(或者故障转移)
(因为备库这时候没有复制槽),是通过pg_rewind
工具来修正新的主备节点的wal日志同步
,是使用参数use_pg_rewind: true
来控制的。 - 2、如果是在Patroni的主、备库流复制之间
某个备库节点宕机重启
,是通过主库的复制槽
来确保数据同步。 - 3、如果在Patroni集群的环境上,有个
逻辑复制到MQ
的业务,这时发生主备切换是通过永久性复制槽
来保证高可用,使用patronictl -c postgres0.yml edit-config
命令去编辑。 - 4、永久性复制槽在非主库节点上是不活跃的。
5、逻辑复制槽的推进机制
总结:逻辑复制槽推进,通过restart_lsn
和catalog_xmin
,以保护xlog和系统表。
物理复制restart lsn的推进比较简单,当备机flush某个lsn以后,告诉主机,主机收到后,立即就会推进restart lsn
5.1、推进逻辑复制槽进度的作用,控制的参数
推进逻辑复制槽的作用
- 一个是保护系统表避免被vacuum,一个是保护xlog,防止需要解码的xlog被回收。
- 如果系统表不往前推进,则系统表就会发生膨胀。如果xlog不往前推进,xlog就会堆积在磁盘。
- 所以,我们根据当前逻辑复制的进度,推进逻辑复制槽。
控制的参数
- 对于xlog,与物理复制槽相同,通过
restart_lsn
控制。 - 对于系统表,通过
catalog_xmin
控制。
5.2、restart_lsn的推进
物理、逻辑复制restart_lsn的推进过程
- 物理复制
restart lsn
的推进比较简单,当备机
flush某个lsn以后,告诉主机,主机收到后
,立即就会推进restart lsn - 而逻辑复制,则相对比较复杂。
- 我们用
confirmed_flush
表示当前订阅端逻辑复制的进度。但是,不是说confirmed_flush之前的xlog就可以回收。 - 因为逻辑解码需要历史快照,而历史快照的构建本质就是根据
running_xacts
以及后续的commit
等xlog来完成的 - 所以为了能够解析后面的xlog,
restart lsn
还要对后续解码需要用的快照xlog进行保护。 - 那么此时就得看confirmed_flush更新时,需要用到的最老的快照lsn是多少了。
- 所以我们用
两个lsn实现restart_lsn的推进
,candidate_restart_valid
就表示当前解码的进度lsn
,而candidate_restart_lsn
表示解码到candidate_restart_valid的时候,需要用的最老的快照的lsn。 - 当
confirmed_flush
超过candidate_restart_valid时
,就可以推进restart_lsn到candidate_restart_lsn了,可以进行更新了。
- 我们用
5.3、catalog_xmin的推进
catalog_xmin的推进
- 与restart lsn的推进是类似的。
- 在序列化快照的时候,使用
candidate_xmin_lsn
表示当前解码的进度,使用candidate_catalog_xmin
表示当前解码需要用的最老xmin - 当
confirmed_flush
超过candidate_xmin_lsn
的时候,就可以更新复制槽的catalog_xmin
为candidate_catalog_xmin
了
5.4、参数说明:
参数 | 类型 | 解释 |
---|---|---|
restart_lsn | 逻辑复制 | 控制xlog回收,保护需要解码的xlog和历史快照。 |
catalog_xmin | 逻辑复制 | 控制系统表回收,保护当前解码进度所需的系统快照。 |
confirmed_flush | 逻辑复制 | 表示当前订阅端逻辑复制的进度。 |
candidate_restart_valid | 逻辑复制 | 当前解码进度的lsn。 |
candidate_restart_lsn | 逻辑复制 | 解码到candidate_restart_valid 所需的最老快照lsn。 |
candidate_xmin_lsn | 逻辑复制 | 当前解码进度的xmin lsn。 |
candidate_catalog_xmin | 逻辑复制 | 解码所需的最老xmin。 |
这些参数用于确保复制过程中的数据完整性和性能。
这一部分的源码请参考,其他文章
参考文章:
【1】https://blog.csdn.net/forrest_hu/article/details/134338007