mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-08 05:52:55 +00:00
Fix test_vm_bit_clear_on_heap_lock test
The test was supposed to reproduce the bug fixed in commit66fa176cc8, i.e. that the clearing of the VM bit was not replayed in the pageserver on HEAP_LOCK records. But it was broken in many ways and failed to reproduce the original problem if you reverted the fix: - The comparison of XIDs was broken. The test read the XID in to a variable in python, but it was treated as a string rather than an integer. As a result, e.g. "999" > "1000". - The test accessed the locked tuple too early, in the loop. Accessing it early, before the pg_xact page had been removed, set the hint bits. That masked the problem on subsequent accesses. - The on-demand SLRU download that was introduced in commit9a9d9beaeehid the issue. Even though an SLRU segment was removed by Postgres, when it later tried to access it, it could still download it from the pageserver. To ensure that doesn't happen, shorten the GC period and compact and GC aggressively in the test. I also added a more direct check that the VM page is updated, using the get_page_at_lsn() debugging function. Right after locking the row, we now fetch the VM page from pageserver and directly compare it with the VM page in the page cache. They should match. That assertion is more robust to things like on-demand SLRU download that could mask the bug.
This commit is contained in:
committed by
Heikki Linnakangas
parent
84a0e7b022
commit
5239cdc29f
@@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
import time
|
||||
|
||||
from fixtures.log_helper import log
|
||||
from fixtures.neon_fixtures import NeonEnv, fork_at_current_lsn
|
||||
from fixtures.neon_fixtures import NeonEnv, NeonEnvBuilder, fork_at_current_lsn
|
||||
|
||||
|
||||
#
|
||||
@@ -118,12 +119,20 @@ def test_vm_bit_clear(neon_simple_env: NeonEnv):
|
||||
# Test that the ALL_FROZEN VM bit is cleared correctly at a HEAP_LOCK
|
||||
# record.
|
||||
#
|
||||
# FIXME: This test is broken
|
||||
@pytest.mark.skip("See https://github.com/neondatabase/neon/pull/6412#issuecomment-1902072541")
|
||||
def test_vm_bit_clear_on_heap_lock(neon_simple_env: NeonEnv):
|
||||
env = neon_simple_env
|
||||
def test_vm_bit_clear_on_heap_lock(neon_env_builder: NeonEnvBuilder):
|
||||
tenant_conf = {
|
||||
"checkpoint_distance": f"{128 * 1024}",
|
||||
"compaction_target_size": f"{128 * 1024}",
|
||||
"compaction_threshold": "1",
|
||||
# create image layers eagerly, so that GC can remove some layers
|
||||
"image_creation_threshold": "1",
|
||||
# set PITR interval to be small, so we can do GC
|
||||
"pitr_interval": "0 s",
|
||||
}
|
||||
env = neon_env_builder.init_start(initial_tenant_conf=tenant_conf)
|
||||
|
||||
env.neon_cli.create_branch("test_vm_bit_clear_on_heap_lock", "empty")
|
||||
tenant_id = env.initial_tenant
|
||||
timeline_id = env.neon_cli.create_branch("test_vm_bit_clear_on_heap_lock")
|
||||
endpoint = env.endpoints.create_start(
|
||||
"test_vm_bit_clear_on_heap_lock",
|
||||
config_lines=[
|
||||
@@ -139,72 +148,88 @@ def test_vm_bit_clear_on_heap_lock(neon_simple_env: NeonEnv):
|
||||
|
||||
# Install extension containing function needed for test
|
||||
cur.execute("CREATE EXTENSION neon_test_utils")
|
||||
|
||||
cur.execute("SELECT pg_switch_wal()")
|
||||
cur.execute("CREATE EXTENSION pageinspect")
|
||||
|
||||
# Create a test table and freeze it to set the all-frozen VM bit on all pages.
|
||||
cur.execute("CREATE TABLE vmtest_lock (id integer PRIMARY KEY)")
|
||||
cur.execute("INSERT INTO vmtest_lock SELECT g FROM generate_series(1, 50000) g")
|
||||
cur.execute("VACUUM FREEZE vmtest_lock")
|
||||
|
||||
cur.execute("VACUUM (FREEZE, DISABLE_PAGE_SKIPPING true) vmtest_lock")
|
||||
|
||||
# Lock a row. This clears the all-frozen VM bit for that page.
|
||||
cur.execute("BEGIN")
|
||||
cur.execute("SELECT * FROM vmtest_lock WHERE id = 40000 FOR UPDATE")
|
||||
|
||||
# Remember the XID. We will use it later to verify that we have consumed a lot of
|
||||
# XIDs after this.
|
||||
cur.execute("select pg_current_xact_id()")
|
||||
locking_xid = cur.fetchall()[0][0]
|
||||
locking_xid = int(cur.fetchall()[0][0])
|
||||
|
||||
# Stop and restart postgres, to clear the buffer cache.
|
||||
cur.execute("COMMIT")
|
||||
|
||||
# The VM page in shared buffer cache, and the same page as reconstructed
|
||||
# by the pageserver, should be equal.
|
||||
cur.execute("select get_raw_page( 'vmtest_lock', 'vm', 0 )")
|
||||
vm_page_in_cache = (cur.fetchall()[0][0])[:100].hex()
|
||||
cur.execute("select get_raw_page_at_lsn( 'vmtest_lock', 'vm', 0, pg_current_wal_insert_lsn() )")
|
||||
vm_page_at_pageserver = (cur.fetchall()[0][0])[:100].hex()
|
||||
|
||||
assert vm_page_at_pageserver == vm_page_in_cache
|
||||
|
||||
# The above assert is enough to verify the bug that was fixed in
|
||||
# commit 66fa176cc8. But for good measure, we also reproduce the
|
||||
# original problem that the missing VM page update caused. The
|
||||
# rest of the test does that.
|
||||
|
||||
# Kill and restart postgres, to clear the buffer cache.
|
||||
#
|
||||
# NOTE: clear_buffer_cache() will not do, because it evicts the dirty pages
|
||||
# in a "clean" way. Our neon extension will write a full-page image of the VM
|
||||
# page, and we want to avoid that.
|
||||
endpoint.stop()
|
||||
# page, and we want to avoid that. A clean shutdown will also not do, for the
|
||||
# same reason.
|
||||
endpoint.stop(mode="immediate")
|
||||
|
||||
endpoint.start()
|
||||
pg_conn = endpoint.connect()
|
||||
cur = pg_conn.cursor()
|
||||
|
||||
cur.execute("select xmin, xmax, * from vmtest_lock where id = 40000 ")
|
||||
tup = cur.fetchall()
|
||||
xmax_before = tup[0][1]
|
||||
|
||||
# Consume a lot of XIDs, so that anti-wraparound autovacuum kicks
|
||||
# in and the clog gets truncated. We set autovacuum_freeze_max_age to a very
|
||||
# low value, so it doesn't take all that many XIDs for autovacuum to kick in.
|
||||
for i in range(1000):
|
||||
#
|
||||
# We could use test_consume_xids() to consume XIDs much faster,
|
||||
# but it wouldn't speed up the overall test, because we'd still
|
||||
# need to wait for autovacuum to run.
|
||||
for _ in range(1000):
|
||||
cur.execute("select test_consume_xids(10000);")
|
||||
for _ in range(1000):
|
||||
cur.execute(
|
||||
"""
|
||||
CREATE TEMP TABLE othertable (i int) ON COMMIT DROP;
|
||||
do $$
|
||||
begin
|
||||
for i in 1..100000 loop
|
||||
-- Use a begin-exception block to generate a new subtransaction on each iteration
|
||||
begin
|
||||
insert into othertable values (i);
|
||||
exception when others then
|
||||
raise 'not expected %', sqlerrm;
|
||||
end;
|
||||
end loop;
|
||||
end;
|
||||
$$;
|
||||
"""
|
||||
"select get_raw_page_at_lsn( 'vmtest_lock', 'vm', 0, pg_current_wal_insert_lsn() )"
|
||||
)
|
||||
cur.execute("select xmin, xmax, * from vmtest_lock where id = 40000 ")
|
||||
tup = cur.fetchall()
|
||||
log.info(f"tuple = {tup}")
|
||||
xmax = tup[0][1]
|
||||
assert xmax == xmax_before
|
||||
page = (cur.fetchall()[0][0])[:100].hex()
|
||||
log.info(f"VM page contents: {page}")
|
||||
|
||||
if i % 50 == 0:
|
||||
cur.execute("select datfrozenxid from pg_database where datname='postgres'")
|
||||
datfrozenxid = cur.fetchall()[0][0]
|
||||
if datfrozenxid > locking_xid:
|
||||
break
|
||||
cur.execute("select get_raw_page( 'vmtest_lock', 'vm', 0 )")
|
||||
page = (cur.fetchall()[0][0])[:100].hex()
|
||||
log.info(f"VM page contents in cache: {page}")
|
||||
|
||||
cur.execute("select min(datfrozenxid::text::int) from pg_database")
|
||||
datfrozenxid = int(cur.fetchall()[0][0])
|
||||
log.info(f"datfrozenxid {datfrozenxid} locking_xid: {locking_xid}")
|
||||
if datfrozenxid > locking_xid + 3000000:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
cur.execute("select pg_current_xact_id()")
|
||||
curr_xid = cur.fetchall()[0][0]
|
||||
assert int(curr_xid) - int(locking_xid) >= 100000
|
||||
curr_xid = int(cur.fetchall()[0][0])
|
||||
assert curr_xid - locking_xid >= 100000
|
||||
|
||||
# Perform GC in the pageserver. Otherwise the compute might still
|
||||
# be able to download the already-deleted SLRU segment from the
|
||||
# pageserver. That masks the original bug.
|
||||
env.pageserver.http_client().timeline_checkpoint(tenant_id, timeline_id)
|
||||
env.pageserver.http_client().timeline_compact(tenant_id, timeline_id)
|
||||
env.pageserver.http_client().timeline_gc(tenant_id, timeline_id, 0)
|
||||
|
||||
# Now, if the VM all-frozen bit was not correctly cleared on
|
||||
# replay, we will try to fetch the status of the XID that was
|
||||
@@ -214,3 +239,4 @@ def test_vm_bit_clear_on_heap_lock(neon_simple_env: NeonEnv):
|
||||
cur.execute("select xmin, xmax, * from vmtest_lock where id = 40000 for update")
|
||||
tup = cur.fetchall()
|
||||
log.info(f"tuple = {tup}")
|
||||
cur.execute("commit transaction")
|
||||
|
||||
Reference in New Issue
Block a user