Postgres:了解生成的 WAL 文件数量/大小

mar*_*ark 4 postgresql write-ahead-logging

我经常惊讶于某些操作如何为我生成大量WAL 文件。

我想要这些 WAL 文件用于时间点恢复(此外,我还执行夜间完全转储)所以需要提供的基本功能,我不想改变它(即我不是在寻找一种方法来打开 WAL存档关闭等)

使用具有以下设置的 Postgres 9.5:

wal_level = archive
checkpoint_timeout = 20min
max_wal_size = 1GB
min_wal_size = 80MB
archive_command = 'test ! -f /backup/wal/%f && cp %p /backup/wal/%f'
Run Code Online (Sandbox Code Playgroud)

我今天运行了这个语句:

WITH table2_only_names AS (
    SELECT id , name FROM table2
)
UPDATE table1
SET table2_name = table2_only_names.name
FROM table2_only_names
WHERE table1.table2_id = table2_only_names.id;
Run Code Online (Sandbox Code Playgroud)

表格1

CREATE TABLE public.table1 (
  id BIGINT PRIMARY KEY NOT NULL DEFAULT nextval('table1_id_seq'::regclass),
  table2_id BIGINT,
  table3_id BIGINT,
  positive_count INTEGER NOT NULL DEFAULT 0,
  neutral_count INTEGER NOT NULL DEFAULT 0,
  negative_count INTEGER NOT NULL DEFAULT 0,
  is_blocked BOOLEAN NOT NULL DEFAULT false,
  blocker_id BIGINT,
  group BIGINT,
  created TIMESTAMP WITH TIME ZONE,
  modified TIMESTAMP WITH TIME ZONE,
  table4_id BIGINT,
  name CHARACTER VARYING(255)
);
CREATE UNIQUE INDEX idx1 ON table1 USING BTREE (table3_id, table2_id);
CREATE INDEX idx2 ON table1 USING BTREE (table3_id);
CREATE INDEX idx3 ON table1 USING BTREE (table2_id);
CREATE INDEX idx4 ON table1 USING BTREE (group);
CREATE INDEX idx5 ON table1 USING BTREE (blocker_id);
CREATE INDEX idx6 ON table1 USING BTREE (table4_id);
Run Code Online (Sandbox Code Playgroud)

15 Mio 行,表大小 ~3,4GB,索引大小 7GB

表2

CREATE TABLE public.table2 (
  id BIGINT PRIMARY KEY NOT NULL DEFAULT nextval('table2_id_seq'::regclass),
  name CHARACTER VARYING(255),
);
Run Code Online (Sandbox Code Playgroud)

10 Mio 行,表大小 ~2GB,索引大小 3,4GB

运行时间约为 55 分钟(这里没有抱怨)。

生成的 WAL 文件的数量出乎意料地巨大。前面提到的 WAL 档案在一个有 100GB 的专用分区上,在查询开始时,大约有 30GB 是免费的。

这还不够,因为 15-20 分钟后磁盘空间小于 10GB,我开始删除“较旧的”WAL 档案。我必须不断地这样做,直到我非常确定我已经不得不删除由这个语句生成的 WAL 存档文件。

在此期间,有问题的表没有被任何其他进程使用,但其他表的“正常”操作仍在继续。

磁盘使用图

我已经标记了查询开始和结束的时间。您可以清楚地看到我在哪里删除了 WAL 存档文件:-)

对我来说,为什么会产生这么多是一个谜,它目前是一个问题,因为几乎无法预见需要多少以及何时需要进行操作。

我缺少什么才能更好地了解需要多少空间?这些事情可以避免吗?难道我做错了什么?

jja*_*nes 6

当您在 PostgreSQL 中更新一行时,它通常会复制整行(不仅仅是更新的列)并将旧行标记为已删除。新副本将需要完整记录 WAL。平均而言,如果您打开了 full_page_writes 并且检查点过于紧密,则旧行也可能会被完整地记录在 WAL 中。

几乎所有更新的行也可能需要为其更新所有索引。那是因为该行的新版本与旧版本不适合在同一页面上,因此索引必须知道在哪里可以找到新版本。

因此,您将整个表记录两次(一次用于旧行,一次用于新行),并且如果其索引也是如此。WAL 记录有相当多的开销。如果您打开 full_page_writes 并经常检查点,那会使情况变得更糟。

那么你有什么选择来减少音量?

1) 如果您的许多更新是退化的(更新为它们已有的值),您可以使用额外的 where 子句抑制这些更新:

WITH table2_only_names AS (
    SELECT id , name FROM table2
)
UPDATE table1
SET table2_name = table2_only_names.name
FROM table2_only_names
WHERE table1.table2_id = table2_only_names.id
AND table2_name is distinct from table2_only_names.name;
Run Code Online (Sandbox Code Playgroud)

2) 大多数 WAL 文件都具有极强的可压缩性。你可以在你的 archive_command 中包含一个压缩命令,比如

 archive_command = 'set -C -o pipefail; xz -2 -c %p > /backup/wal/%f.xz'
Run Code Online (Sandbox Code Playgroud)

当然,你必须让你的 recovery_command 做相反的事情。

3)由于您使用的是9.5,您可以尝试打开wal_compression。

4) 您可以尝试关闭 full_page_writes,尽管这样做但在大多数存储硬件上,您的数据在崩溃的情况下有损坏的风险。或者,如果您在此操作期间有频繁的检查点,则可以使检查点的出现频率降低很多,这将减少启用 full_page_writes 的影响。