mirror of https://github.com/postgres/postgres
This fixes a bug in the edge case where, for a temp table, heap_page_prune() can end up with a different horizon than heap_vacuum_rel(). Which can trigger errors like "ERROR: cannot freeze committed xmax ...". The bug was introduced due to interaction ofpull/57/heada7212be8b9
"Set cutoff xmin more aggressively when vacuuming a temporary table." withdc7420c2c9
"snapshot scalability: Don't compute global horizons while building snapshots.". The problem is caused by lazy_scan_heap() assuming that the only reason its HeapTupleSatisfiesVacuum() call would return HEAPTUPLE_DEAD is if the tuple is a HOT tuple, or if the tuple's inserting transaction has aborted since the heap_page_prune() call. But aftera7212be8b9
that was also possible in other cases for temp tables, because heap_page_prune() uses a different visibility test afterdc7420c2c9
. The fix is fairly simple: Move the special case logic for temp tables from vacuum_set_xid_limits() to the infrastructure introduced indc7420c2c9
. That ensures that the horizon used for pruning is at least as aggressive as the one used by lazy_scan_heap(). The concrete horizon used for temp tables is slightly different than the logic indc7420c2c9
, but should always be as aggressive as before (see comments). A significant benefit to centralizing the logic procarray.c is that now the more aggressive horizons for temp tables does not just apply to VACUUM but also to e.g. HOT pruning and the nbtree killtuples logic. Because isTopLevel is not needed by vacuum_set_xid_limits() anymore, I undid the the related changes froma7212be8b9
. This commit also adds an isolation test ensuring that the more aggressive vacuuming and pruning of temp tables keeps working. Debugged-By: Amit Kapila <amit.kapila16@gmail.com> Debugged-By: Tom Lane <tgl@sss.pgh.pa.us> Debugged-By: Ashutosh Sharma <ashu.coek88@gmail.com> Author: Andres Freund <andres@anarazel.de> Discussion: https://postgr.es/m/20201014203103.72oke6hqywcyhx7s@alap3.anarazel.de Discussion: https://postgr.es/m/20201015083735.derdzysdtqdvxshp@alap3.anarazel.de
parent
60a51c6b32
commit
94bc27b576
@ -0,0 +1,281 @@ |
||||
Parsed test spec with 2 sessions |
||||
|
||||
starting permutation: pruner_create_perm ll_start pruner_query_plan pruner_query pruner_query pruner_delete pruner_query pruner_query ll_commit pruner_drop |
||||
step pruner_create_perm: |
||||
CREATE TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); |
||||
INSERT INTO horizons_tst(data) VALUES(1),(2); |
||||
|
||||
step ll_start: |
||||
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
||||
SELECT 1; |
||||
|
||||
?column? |
||||
|
||||
1 |
||||
step pruner_query_plan: |
||||
EXPLAIN (COSTS OFF) SELECT * FROM horizons_tst ORDER BY data; |
||||
|
||||
QUERY PLAN |
||||
|
||||
Index Only Scan using horizons_tst_data_key on horizons_tst |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_delete: |
||||
DELETE FROM horizons_tst; |
||||
|
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step ll_commit: COMMIT; |
||||
step pruner_drop: |
||||
DROP TABLE horizons_tst; |
||||
|
||||
|
||||
starting permutation: pruner_create_temp ll_start pruner_query_plan pruner_query pruner_query pruner_delete pruner_query pruner_query ll_commit pruner_drop |
||||
step pruner_create_temp: |
||||
CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); |
||||
INSERT INTO horizons_tst(data) VALUES(1),(2); |
||||
|
||||
step ll_start: |
||||
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
||||
SELECT 1; |
||||
|
||||
?column? |
||||
|
||||
1 |
||||
step pruner_query_plan: |
||||
EXPLAIN (COSTS OFF) SELECT * FROM horizons_tst ORDER BY data; |
||||
|
||||
QUERY PLAN |
||||
|
||||
Index Only Scan using horizons_tst_data_key on horizons_tst |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_delete: |
||||
DELETE FROM horizons_tst; |
||||
|
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
0 |
||||
step ll_commit: COMMIT; |
||||
step pruner_drop: |
||||
DROP TABLE horizons_tst; |
||||
|
||||
|
||||
starting permutation: pruner_create_temp ll_start pruner_query pruner_query pruner_begin pruner_delete pruner_query pruner_query ll_commit pruner_commit pruner_drop |
||||
step pruner_create_temp: |
||||
CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); |
||||
INSERT INTO horizons_tst(data) VALUES(1),(2); |
||||
|
||||
step ll_start: |
||||
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
||||
SELECT 1; |
||||
|
||||
?column? |
||||
|
||||
1 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_begin: BEGIN; |
||||
step pruner_delete: |
||||
DELETE FROM horizons_tst; |
||||
|
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step ll_commit: COMMIT; |
||||
step pruner_commit: COMMIT; |
||||
step pruner_drop: |
||||
DROP TABLE horizons_tst; |
||||
|
||||
|
||||
starting permutation: pruner_create_perm ll_start pruner_query pruner_query pruner_delete pruner_vacuum pruner_query pruner_query ll_commit pruner_drop |
||||
step pruner_create_perm: |
||||
CREATE TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); |
||||
INSERT INTO horizons_tst(data) VALUES(1),(2); |
||||
|
||||
step ll_start: |
||||
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
||||
SELECT 1; |
||||
|
||||
?column? |
||||
|
||||
1 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_delete: |
||||
DELETE FROM horizons_tst; |
||||
|
||||
step pruner_vacuum: |
||||
VACUUM horizons_tst; |
||||
|
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step ll_commit: COMMIT; |
||||
step pruner_drop: |
||||
DROP TABLE horizons_tst; |
||||
|
||||
|
||||
starting permutation: pruner_create_temp ll_start pruner_query pruner_query pruner_delete pruner_vacuum pruner_query pruner_query ll_commit pruner_drop |
||||
step pruner_create_temp: |
||||
CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); |
||||
INSERT INTO horizons_tst(data) VALUES(1),(2); |
||||
|
||||
step ll_start: |
||||
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
||||
SELECT 1; |
||||
|
||||
?column? |
||||
|
||||
1 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
2 |
||||
step pruner_delete: |
||||
DELETE FROM horizons_tst; |
||||
|
||||
step pruner_vacuum: |
||||
VACUUM horizons_tst; |
||||
|
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
0 |
||||
step pruner_query: |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
|
||||
?column? |
||||
|
||||
0 |
||||
step ll_commit: COMMIT; |
||||
step pruner_drop: |
||||
DROP TABLE horizons_tst; |
||||
|
@ -0,0 +1,169 @@ |
||||
# Test that pruning and vacuuming pay attention to concurrent sessions |
||||
# in the right way. For normal relations that means that rows cannot |
||||
# be pruned away if there's an older snapshot, in contrast to that |
||||
# temporary tables should nearly always be prunable. |
||||
# |
||||
# NB: Think hard before adding a test showing that rows in permanent |
||||
# tables get pruned - it's quite likely that it'd be racy, e.g. due to |
||||
# an autovacuum worker holding a snapshot. |
||||
|
||||
setup { |
||||
CREATE OR REPLACE FUNCTION explain_json(p_query text) |
||||
RETURNS json |
||||
LANGUAGE plpgsql AS $$ |
||||
DECLARE |
||||
v_ret json; |
||||
BEGIN |
||||
EXECUTE p_query INTO STRICT v_ret; |
||||
RETURN v_ret; |
||||
END;$$; |
||||
} |
||||
|
||||
teardown { |
||||
DROP FUNCTION explain_json(text); |
||||
} |
||||
|
||||
session "lifeline" |
||||
|
||||
# Start a transaction, force a snapshot to be held |
||||
step "ll_start" |
||||
{ |
||||
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; |
||||
SELECT 1; |
||||
} |
||||
|
||||
step "ll_commit" { COMMIT; } |
||||
|
||||
|
||||
session "pruner" |
||||
|
||||
setup |
||||
{ |
||||
SET enable_seqscan = false; |
||||
SET enable_indexscan = false; |
||||
SET enable_bitmapscan = false; |
||||
} |
||||
|
||||
step "pruner_create_temp" |
||||
{ |
||||
CREATE TEMPORARY TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); |
||||
INSERT INTO horizons_tst(data) VALUES(1),(2); |
||||
} |
||||
|
||||
step "pruner_create_perm" |
||||
{ |
||||
CREATE TABLE horizons_tst (data int unique) WITH (autovacuum_enabled = off); |
||||
INSERT INTO horizons_tst(data) VALUES(1),(2); |
||||
} |
||||
|
||||
# Temp tables cannot be dropped in the teardown, so just always do so |
||||
# as part of the permutation |
||||
step "pruner_drop" |
||||
{ |
||||
DROP TABLE horizons_tst; |
||||
} |
||||
|
||||
step "pruner_delete" |
||||
{ |
||||
DELETE FROM horizons_tst; |
||||
} |
||||
|
||||
step "pruner_begin" { BEGIN; } |
||||
step "pruner_commit" { COMMIT; } |
||||
|
||||
step "pruner_vacuum" |
||||
{ |
||||
VACUUM horizons_tst; |
||||
} |
||||
|
||||
# Show the heap fetches of an ordered index-only-scan (other plans |
||||
# have been forbidden above) - that tells us how many non-killed leaf |
||||
# entries there are. |
||||
step "pruner_query" |
||||
{ |
||||
SELECT explain_json($$ |
||||
EXPLAIN (FORMAT json, BUFFERS, ANALYZE) |
||||
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches'; |
||||
} |
||||
|
||||
# Verify that the query plan still is an IOS |
||||
step "pruner_query_plan" |
||||
{ |
||||
EXPLAIN (COSTS OFF) SELECT * FROM horizons_tst ORDER BY data; |
||||
} |
||||
|
||||
|
||||
# Show that with a permanent relation deleted rows cannot be pruned |
||||
# away if there's a concurrent session still seeing the rows. |
||||
permutation |
||||
"pruner_create_perm" |
||||
"ll_start" |
||||
"pruner_query_plan" |
||||
# Run query that could do pruning twice, first has chance to prune, |
||||
# second would not perform heap fetches if first query did. |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"pruner_delete" |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"ll_commit" |
||||
"pruner_drop" |
||||
|
||||
# Show that with a temporary relation deleted rows can be pruned away, |
||||
# even if there's a concurrent session with a snapshot from before the |
||||
# deletion. That's safe because the session with the older snapshot |
||||
# cannot access the temporary table. |
||||
permutation |
||||
"pruner_create_temp" |
||||
"ll_start" |
||||
"pruner_query_plan" |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"pruner_delete" |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"ll_commit" |
||||
"pruner_drop" |
||||
|
||||
# Verify that pruning in temporary relations doesn't remove rows still |
||||
# visible in the current session |
||||
permutation |
||||
"pruner_create_temp" |
||||
"ll_start" |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"pruner_begin" |
||||
"pruner_delete" |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"ll_commit" |
||||
"pruner_commit" |
||||
"pruner_drop" |
||||
|
||||
# Show that vacuum cannot remove deleted rows still visible to another |
||||
# session's snapshot, when accessing a permanent table. |
||||
permutation |
||||
"pruner_create_perm" |
||||
"ll_start" |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"pruner_delete" |
||||
"pruner_vacuum" |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"ll_commit" |
||||
"pruner_drop" |
||||
|
||||
# Show that vacuum can remove deleted rows still visible to another |
||||
# session's snapshot, when accessing a temporary table. |
||||
permutation |
||||
"pruner_create_temp" |
||||
"ll_start" |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"pruner_delete" |
||||
"pruner_vacuum" |
||||
"pruner_query" |
||||
"pruner_query" |
||||
"ll_commit" |
||||
"pruner_drop" |
Loading…
Reference in new issue