探索Postgres超前写入日志(WAL)

419 阅读5分钟

探索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

  1. 搜索 "WRITE-AHEAD LOG"
  2. 找到选项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的消息队列?

上述知识的有趣应用

  • 未记录的表(为什么你会这样做)
  • 异步提交
  • 出版物