探索Postgres超前写入日志(WAL)
我每天都与Postgres打交道,尽管通常是由AWS RDS这样的服务提供商管理。热议--在这里,你为RDS支付了一定的费用,以适应你无知的奢侈--这是理所当然的。RDS抽象了Postgres的内部结构,允许你越来越像一个直观的数据存储,而不是纠缠于实施细节。
这些服务通常提供的一些个人感兴趣的功能是:
- 时间点恢复(PITR)
- 加热(或准备好)的备份副本
- 逻辑复制
但是我了解到,这些都是通过代理的方式提供的,而不是通过PostgresQL组件的扩展。
通过最近对Postgres WAL的研究,使我有能力清楚地了解Postgres提供的服务内容。
WAL简介
数据库系统的目的是保证数据的有效性。反过来,我们发现这直接假定了数据库事务(所做的改变的单位)的行为是正确的。这里的正确性是由四个被称为ACID的属性来定义的。WAL是一种直接针对原子速度和耐久性的技术。
由于对数据库系统的数据有效性有如此重大的影响,你可以理解我对它的兴趣。
Postgres中的WAL--在应用之前,所有的修改都被写到日志中。重做和撤销的信息都存储在日志中。
他们住在哪里:
% docker-compose up --detach postgres
Creating network "postgres-wal_default" with the default driver
Creating postgres-wal_postgres_1 ... done
% docker-compose exec postgres bash
[email protected]:/# cd $PGDATA/pg_wal
[email protected]:/var/lib/postgresql/data# ls -l
total 16388
-rw------- 1 postgres postgres 16777216 Oct 17 21:59 000000010000000000000001
drwx------ 2 postgres postgres 4096 Oct 17 21:59 archive_status
笔记:
- LSN派生的文件名惯例在这里,前8个十六进制的值代表一个时间元素(与DB集群首次启动的时间相呼应)。剩下的16个值根据需要递增。
- WAL文件是二进制文件,分配为16MB - 这是可改变的。
你可以--follow 这些WAL文件:
[email protected]:/var/lib/postgresql/data/pg_wal# pg_waldump 000000010000000000000001 -f
并在另一个终端创建DB变化
% pgcli -h localhost -U postgres postgres
Password for postgres:
Server: PostgreSQL 14.0 (Debian 14.0-1.pgdg110+1)
Version: 3.2.0
Home: http://pgcli.com
[email protected]:postgres> CREATE TABLE tmp(val int);
CREATE TABLE
Time: 0.005s
[email protected]:postgres> INSERT INTO tmp(val) SELECT g.id FROM generate_series(1, 10) as g(id);
INSERT 0 10
Time: 0.005s
你可以看到这些WAL文件是如何在先前的终端会话中返回查询之前被写入的。
为什么我们要有WAL文件?
tldr:在坚持严格的数据完整性要求时,速度更快:
如果我们遵循这个程序,我们不需要在每次事务提交时将数据页刷入磁盘,因为我们知道在崩溃的情况下,我们将能够使用日志来恢复数据库:任何没有应用到数据页的改变都可以从日志记录中重新进行。(这就是向前滚动恢复,也被称为REDO。)
-文档
checkpointer硬性限制(最大2分钟),坚持每隔一段时间对脏缓冲区进行刷新,暂停一切,找出可以和不可以刷新的东西。background writer基于LRU算法的冲刷,增加干净的页面,以廉价的方式进行冲刷。
流动这些WAL文件(备份背后的原理)
Postgres输出了一个工具 pg_receivewal作为一个一次读、不可变的消息队列,允许你将这些墙文件流向......任何地方(例如归档):
% docker-compose exec postgres bash
[email protected]:/# su postgres
[email protected]:/$ cd $PGDATA/
[email protected]:~/data$ cd ..
[email protected]:~$ mkdir stream
[email protected]:~$ pg_receivewal -D stream/
在另一个终端,你可以查看这些文件
[email protected]:/# ls -l $PGDATA/../stream/
total 16384
-rw------- 1 postgres postgres 16777216 Oct 17 23:36 000000010000000000000001.partial
.partial 文件是主动流式写入当前WAL文件的。一旦这个 文件被填满或被切换,例如。WAL
[email protected]:postgres> select pg_switch_wal();
+-----------------+
| pg_switch_wal |
|-----------------|
| 0/16FAC80 |
+-----------------+
SELECT 1
Time: 0.041s
你会发现
[email protected]:/# ls -l $PGDATA/../stream/
total 32768
-rw------- 1 postgres postgres 16777216 Oct 17 23:41 000000010000000000000001
-rw------- 1 postgres postgres 16777216 Oct 17 23:41 000000010000000000000002.partial
虽然这只是简单的归档,但你会发现当涉及到一般的归档、备份和有趣的背后的基本工具时,这是一个需要了解的重要概念。DB复制。
复制服务器
俗称复制槽,是更正式地包裹pg_receivewal ,提供方便的复制连接的机制,目的是在复制连接器之间提供一个一致的接口。
你可以通过以下方式创建这些复制槽
[email protected]:postgres> select * from pg_create_physical_replication_slot('replica');
+-------------+--------+
| slot_name | lsn |
|-------------+--------|
| replica | <null> |
+-------------+--------+
SELECT 1
Time: 0.018s
[email protected]:postgres> select slot_name, active from pg_replication_slots
+-------------+----------+
| slot_name | active |
|-------------+----------|
| replica | False |
+-------------+----------+
SELECT 1
Time: 0.009s
有趣的是,你也可以使用pg_receivewal ,将WAL文件流转到某个地方!这在技术上是将pg_receivewal.-S, --slot-name 链接到一个复制槽。
[email protected]:~$ pg_receivewal -D stream/ -S replica
逻辑复制
默认情况下,WAL文件为基本的复制支持提供了足够的信息,它写入了足够的数据来支持WAL归档和复制,包括在备用服务器上运行只读查询。这在物理上是知情的,它使用精确的块地址和逐个字节的复制。我们可以改变wal_level ,允许对WAL文件进行逻辑解码,允许一个更通用的消费者(逻辑复制和物理复制之间的区别)。
编辑postgres.conf ,以改变 wal_level
[email protected]:/# cd $PGDATA/
[email protected]:/var/lib/postgresql/data# vim postgresql.conf
里面postgresql.conf
- 搜索 "WRITE-AHEAD LOG"
- 找到选项
wal_level,将其改为logical
让postgres集群通过集群重启对这个配置变化做出反应
[email protected]:/var/lib/postgresql/data# su postgres
[email protected]:~/data$ pg_ctl restart
waiting for server to shut down....
% docker-compose up postgres
pg_receivewal 工作原理是一样的,你只会注意到每个日志行包含了描述所发生的事情的附加逻辑语言。我们可以通过以下方式提取和解释这些WAL文件 pg_recvlogical尽管pg_recvlogical 直接依赖于复制槽,(即我们不能直接包裹pg_receivewal )。
我们可以直接使用pg_recvlogical 来创建一个逻辑复制槽。
su postgres
pg_recvlogical -d postgres --slot extract --create-slot
pg_recvlogical -d postgres --slot extract --start -f -
注意:逻辑流区分了来自不同数据库的日志
在postgres 数据库中插入一些东西
% pgcli -h localhost -U postgres postgres
Password for postgres:
Server: PostgreSQL 14.0 (Debian 14.0-1.pgdg110+1)
Version: 3.2.0
Home: http://pgcli.com
[email protected]:postgres> create table tmp(val int);
CREATE TABLE
Time: 0.006s
[email protected]:postgres> INSERT INTO tmp(val) SELECT g.id FROM generate_series(1, 10) as g(id);
INSERT 0 10
Time: 0.006s
而在前面的终端中,你会注意到正在发生的逻辑输出,远比我们之前得到的物理级别的日志更容易理解。
BEGIN 736
table public.tmp: INSERT: val[integer]:1
table public.tmp: INSERT: val[integer]:2
table public.tmp: INSERT: val[integer]:3
table public.tmp: INSERT: val[integer]:4
table public.tmp: INSERT: val[integer]:5
table public.tmp: INSERT: val[integer]:6
table public.tmp: INSERT: val[integer]:7
table public.tmp: INSERT: val[integer]:8
table public.tmp: INSERT: val[integer]:9
table public.tmp: INSERT: val[integer]:10
COMMIT 736
当然,你可以在Postgres内部通过pg_create_logical_replication_slot (见复制功能)来实现这个功能。
因为你已经在上面创建了一个复制槽--你可以立即使用它(在这里寻求更完整的例子)。
[email protected]:postgres> -- \df pg_logical_slot_get_changes
[email protected]:postgres> SELECT * FROM pg_logical_slot_get_changes('extract', NULL, NULL);
+-------+-------+--------+
| lsn | xid | data |
|-------+-------+--------|
+-------+-------+--------+
SELECT 0
Time: 0.018s
[email protected]:postgres> INSERT INTO tmp(val) SELECT g.id FROM generate_series(1, 10) as g(id);
INSERT 0 10
Time: 0.006s
[email protected]:postgres> SELECT * FROM pg_logical_slot_get_changes('extract', NULL, NULL);
+-----------+-------+-------------------------------------------+
| lsn | xid | data |
|-----------+-------+-------------------------------------------|
| 0/17143F8 | 737 | BEGIN 737 |
| 0/17143F8 | 737 | table public.tmp: INSERT: val[integer]:1 |
| 0/1714748 | 737 | table public.tmp: INSERT: val[integer]:2 |
| 0/1714788 | 737 | table public.tmp: INSERT: val[integer]:3 |
| 0/17147C8 | 737 | table public.tmp: INSERT: val[integer]:4 |
| 0/1714808 | 737 | table public.tmp: INSERT: val[integer]:5 |
| 0/1714848 | 737 | table public.tmp: INSERT: val[integer]:6 |
| 0/1714888 | 737 | table public.tmp: INSERT: val[integer]:7 |
| 0/17148C8 | 737 | table public.tmp: INSERT: val[integer]:8 |
| 0/1714908 | 737 | table public.tmp: INSERT: val[integer]:9 |
| 0/1714948 | 737 | table public.tmp: INSERT: val[integer]:10 |
| 0/17149B8 | 737 | COMMIT 737 |
+-----------+-------+-------------------------------------------+
SELECT 12
Time: 0.015s
[email protected]:postgres> SELECT * FROM pg_logical_slot_get_changes('extract', NULL, NULL);
+-------+-------+--------+
| lsn | xid | data |
|-------+-------+--------|
+-------+-------+--------+
SELECT 0
Time: 0.016s
作为JSON变化集
通过 wal2json通过-P, --plugin 标志。
su postgres
pg_recvlogical -d postgres --slot test_slot --create-slot -P wal2json
pg_recvlogical -d postgres --slot test_slot --start -o pretty-print=1 -o add-msg-prefixes=wal2json -f -
现在你会收到更丰富的JSON信息。
演示
pg_recvlogical 有
TODO
- 整合Tom Arrells的消息队列?
上述知识的有趣应用
- 未记录的表(为什么你会这样做)
- 异步提交
- 出版物