mirror of
https://github.com/neondatabase/neon.git
synced 2025-12-27 16:12:56 +00:00
Because the t_cid field was missing from the XlHeapDelete struct that
corresponds to the PostgreSQL xl_heap_delete struct, the check for the
XLH_DELETE_ALL_VISIBLE_CLEARED flag did not work correctly.
Decoding XlHeapUpdate struct was also missing the t_cid field, but that
didn't cause any immediate problems because in that struct, the t_cid
field is after all the fields that the page server cares about. But fix
that too, as it was an accident waiting to happen.
The bug was mostly hidden by the VM page handling in zenith_wallog_page,
where it forcibly generates a FPW record whenever a VM page is evicted:
else if (forknum == VISIBILITYMAP_FORKNUM && !RecoveryInProgress())
{
/*
* Always WAL-log vm.
* We should never miss clearing visibility map bits.
*
* TODO Is it too bad for performance?
* Hopefully we do not evict actively used vm too often.
*/
XLogRecPtr recptr;
recptr = log_newpage_copy(&reln->smgr_rnode.node, forknum, blocknum, buffer, false);
XLogFlush(recptr);
lsn = recptr;
But that was just hiding the issue: it's still visible if you had a
read-only node relying on the data in the page server, or you killed and
restarted the primary node, or you started a branch. In the included test
case, I used a new branch to expose this.
Fixes https://github.com/zenithdb/zenith/issues/461
80 lines
2.9 KiB
Python
80 lines
2.9 KiB
Python
from fixtures.zenith_fixtures import PostgresFactory, ZenithPageserver
|
|
|
|
pytest_plugins = ("fixtures.zenith_fixtures")
|
|
|
|
#
|
|
# Test that the VM bit is cleared correctly at a HEAP_DELETE and
|
|
# HEAP_UPDATE record.
|
|
#
|
|
def test_vm_bit_clear(pageserver: ZenithPageserver, postgres: PostgresFactory, pg_bin, zenith_cli, base_dir):
|
|
# Create a branch for us
|
|
zenith_cli.run(["branch", "test_vm_bit_clear", "empty"])
|
|
pg = postgres.create_start('test_vm_bit_clear')
|
|
|
|
print("postgres is running on 'test_vm_bit_clear' branch")
|
|
pg_conn = pg.connect()
|
|
cur = pg_conn.cursor()
|
|
|
|
# Install extension containing function needed for test
|
|
cur.execute('CREATE EXTENSION zenith_test_utils')
|
|
|
|
# Create a test table and freeze it to set the VM bit.
|
|
cur.execute('CREATE TABLE vmtest_delete (id integer PRIMARY KEY)')
|
|
cur.execute('INSERT INTO vmtest_delete VALUES (1)')
|
|
cur.execute('VACUUM FREEZE vmtest_delete')
|
|
|
|
cur.execute('CREATE TABLE vmtest_update (id integer PRIMARY KEY)')
|
|
cur.execute('INSERT INTO vmtest_update SELECT g FROM generate_series(1, 1000) g')
|
|
cur.execute('VACUUM FREEZE vmtest_update')
|
|
|
|
# DELETE and UDPATE the rows.
|
|
cur.execute('DELETE FROM vmtest_delete WHERE id = 1')
|
|
cur.execute('UPDATE vmtest_update SET id = 5000 WHERE id = 1')
|
|
|
|
# Branch at this point, to test that later
|
|
zenith_cli.run(["branch", "test_vm_bit_clear_new", "test_vm_bit_clear"])
|
|
|
|
# Clear the buffer cache, to force the VM page to be re-fetched from
|
|
# the page server
|
|
cur.execute('SELECT clear_buffer_cache()')
|
|
|
|
# Check that an index-only scan doesn't see the deleted row. If the
|
|
# clearing of the VM bit was not replayed correctly, this would incorrectly
|
|
# return deleted row.
|
|
cur.execute('''
|
|
set enable_seqscan=off;
|
|
set enable_indexscan=on;
|
|
set enable_bitmapscan=off;
|
|
''')
|
|
|
|
cur.execute('SELECT * FROM vmtest_delete WHERE id = 1')
|
|
assert(cur.fetchall() == []);
|
|
cur.execute('SELECT * FROM vmtest_update WHERE id = 1')
|
|
assert(cur.fetchall() == []);
|
|
|
|
cur.close()
|
|
|
|
|
|
# Check the same thing on the branch that we created right after the DELETE
|
|
#
|
|
# As of this writing, the code in smgrwrite() creates a full-page image whenever
|
|
# a dirty VM page is evicted. If the VM bit was not correctly cleared by the
|
|
# earlier WAL record, the full-page image hides the problem. Starting a new
|
|
# server at the right point-in-time avoids that full-page image.
|
|
pg_new = postgres.create_start('test_vm_bit_clear_new')
|
|
|
|
print("postgres is running on 'test_vm_bit_clear_new' branch")
|
|
pg_new_conn = pg_new.connect()
|
|
cur_new = pg_new_conn.cursor()
|
|
|
|
cur_new.execute('''
|
|
set enable_seqscan=off;
|
|
set enable_indexscan=on;
|
|
set enable_bitmapscan=off;
|
|
''')
|
|
|
|
cur_new.execute('SELECT * FROM vmtest_delete WHERE id = 1')
|
|
assert(cur_new.fetchall() == []);
|
|
cur_new.execute('SELECT * FROM vmtest_update WHERE id = 1')
|
|
assert(cur_new.fetchall() == []);
|