Hi, On 2023-04-11 22:00:00 +0300, Alexander Lakhin wrote: > A few days later I've found a new defect introduced with 31966b151.
That's the same issue that Tom also just reported, at https://postgr.es/m/392271.1681238924%40sss.pgh.pa.us Attached is my WIP fix, including a test. Greetings, Andres Freund
>From fbe84381fa39a48f906358ade0a64e93b81c05c9 Mon Sep 17 00:00:00 2001 From: Andres Freund <and...@anarazel.de> Date: Tue, 11 Apr 2023 16:04:44 -0700 Subject: [PATCH v1] WIP: Fix ExtendBufferedRelTo() assert failure in recovery + tests Reported-by: Alexander Lakhin <exclus...@gmail.com> Reported-by: Tom Lane <t...@sss.pgh.pa.us> Discussion: https://postgr.es/m/392271.1681238924%40sss.pgh.pa.us Discussion: https://postgr.es/m/0b5eb82b-cb99-e0a4-b932-3dc60e2e3...@gmail.com --- src/backend/storage/buffer/bufmgr.c | 4 +- src/test/recovery/meson.build | 1 + src/test/recovery/t/036_truncated_dropped.pl | 99 ++++++++++++++++++++ 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 src/test/recovery/t/036_truncated_dropped.pl diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 7778dde3e57..4f2c46a4339 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -889,7 +889,7 @@ ExtendBufferedRelTo(ExtendBufferedWhat eb, Assert(eb.smgr == NULL || eb.relpersistence != 0); Assert(extend_to != InvalidBlockNumber && extend_to > 0); Assert(mode == RBM_NORMAL || mode == RBM_ZERO_ON_ERROR || - mode == RBM_ZERO_AND_LOCK); + mode == RBM_ZERO_AND_LOCK || mode == RBM_ZERO_AND_CLEANUP_LOCK); if (eb.smgr == NULL) { @@ -933,7 +933,7 @@ ExtendBufferedRelTo(ExtendBufferedWhat eb, */ current_size = smgrnblocks(eb.smgr, fork); - if (mode == RBM_ZERO_AND_LOCK) + if (mode == RBM_ZERO_AND_LOCK || mode == RBM_ZERO_AND_CLEANUP_LOCK) flags |= EB_LOCK_TARGET; while (current_size < extend_to) diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build index e834ad5e0dc..20089580100 100644 --- a/src/test/recovery/meson.build +++ b/src/test/recovery/meson.build @@ -41,6 +41,7 @@ tests += { 't/033_replay_tsp_drops.pl', 't/034_create_database.pl', 't/035_standby_logical_decoding.pl', + 't/036_truncated_dropped.pl', ], }, } diff --git a/src/test/recovery/t/036_truncated_dropped.pl b/src/test/recovery/t/036_truncated_dropped.pl new file mode 100644 index 00000000000..571cff9639a --- /dev/null +++ b/src/test/recovery/t/036_truncated_dropped.pl @@ -0,0 +1,99 @@ +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# Tests recovery scenarios where the files are shorter than in the common +# cases, e.g. due to replaying WAL records of a relation that was subsequently +# truncated. + +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $node = PostgreSQL::Test::Cluster->new('n1'); + +$node->init(); + +# Disable autovacuum to guarantee VACUUM can remove rows / truncate relations +$node->append_conf('postgresql.conf', qq[ +wal_level = 'replica' +autovacuum = off +]); + +$node->start(); + + +# Test replay of PRUNE records for blocks that are later truncated. With FPIs +# used for PRUNE. + +$node->safe_psql( + 'postgres', qq[ +CREATE TABLE truncme(i int) WITH (fillfactor = 50); +INSERT INTO truncme SELECT generate_series(1, 1000); +UPDATE truncme SET i = 1; +CHECKPOINT; -- generate FPIs +VACUUM truncme; -- generate prune records +TRUNCATE truncme; -- make blocks non-existing +INSERT INTO truncme SELECT generate_series(1, 10); +]); + +$node->stop('immediate'); + +ok($node->start(), 'replay of PRUNE records affecting truncated block (FPIs)'); + +is($node->safe_psql('postgres', 'select count(*), sum(i) FROM truncme'), + '10|55', + 'table contents as expected after recovery'); +$node->safe_psql('postgres', 'DROP TABLE truncme'); + + +# Test replay of PRUNE records for blocks that are later truncated. Without +# FPIs used for PRUNE. + +$node->safe_psql( + 'postgres', qq[ +CREATE TABLE truncme(i int) WITH (fillfactor = 50); +INSERT INTO truncme SELECT generate_series(1, 1000); +UPDATE truncme SET i = 1; +VACUUM truncme; -- generate prune records +TRUNCATE truncme; -- make blocks non-existing +INSERT INTO truncme SELECT generate_series(1, 10); +]); + +$node->stop('immediate'); + +ok($node->start(), 'replay of PRUNE records affecting truncated block (no FPIs)'); + +is($node->safe_psql('postgres', 'select count(*), sum(i) FROM truncme'), + '10|55', + 'table contents as expected after recovery'); +$node->safe_psql('postgres', 'DROP TABLE truncme'); + + +# Test replay of partial relation truncation via VACUUM + +$node->safe_psql( + 'postgres', qq[ +CREATE TABLE truncme(i int) WITH (fillfactor = 50); +INSERT INTO truncme SELECT generate_series(1, 1000); +UPDATE truncme SET i = i + 1; +-- ensure a mix of pre/post truncation rows +DELETE FROM truncme WHERE i > 500; + +VACUUM truncme; -- should truncate relation + +-- rows at TIDs that previously existed +INSERT INTO truncme SELECT generate_series(1000, 1010); +]); + +$node->stop('immediate'); + +ok($node->start(), 'replay of partial truncation via VACUUM'); + +is($node->safe_psql('postgres', 'select count(*), sum(i), min(i), max(i) FROM truncme'), + '510|136304|2|1010', + 'table contents as expected after recovery'); +$node->safe_psql('postgres', 'DROP TABLE truncme'); + + +done_testing(); -- 2.38.0