mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
Add the "snapshot too old" feature
This feature is controlled by a new old_snapshot_threshold GUC. A value of -1 disables the feature, and that is the default. The value of 0 is just intended for testing. Above that it is the number of minutes a snapshot can reach before pruning and vacuum are allowed to remove dead tuples which the snapshot would otherwise protect. The xmin associated with a transaction ID does still protect dead tuples. A connection which is using an "old" snapshot does not get an error unless it accesses a page modified recently enough that it might not be able to produce accurate results. This is similar to the Oracle feature, and we use the same SQLSTATE and error message for compatibility.
This commit is contained in:
@ -8,6 +8,7 @@ SUBDIRS = \
|
||||
brin \
|
||||
commit_ts \
|
||||
dummy_seclabel \
|
||||
snapshot_too_old \
|
||||
test_ddl_deparse \
|
||||
test_extensions \
|
||||
test_parser \
|
||||
|
47
src/test/modules/snapshot_too_old/Makefile
Normal file
47
src/test/modules/snapshot_too_old/Makefile
Normal file
@ -0,0 +1,47 @@
|
||||
# src/test/modules/snapshot_too_old/Makefile
|
||||
|
||||
EXTRA_CLEAN = ./isolation_output
|
||||
|
||||
ISOLATIONCHECKS=sto_using_cursor sto_using_select
|
||||
|
||||
ifdef USE_PGXS
|
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS)
|
||||
else
|
||||
subdir = src/test/modules/snapshot_too_old
|
||||
top_builddir = ../../../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
include $(top_srcdir)/contrib/contrib-global.mk
|
||||
endif
|
||||
|
||||
# Disabled because these tests require "old_snapshot_threshold" >= 0, which
|
||||
# typical installcheck users do not have (e.g. buildfarm clients).
|
||||
installcheck:;
|
||||
|
||||
# But it can nonetheless be very helpful to run tests on preexisting
|
||||
# installation, allow to do so, but only if requested explicitly.
|
||||
installcheck-force: isolationcheck-install-force
|
||||
|
||||
check: isolationcheck
|
||||
|
||||
submake-isolation:
|
||||
$(MAKE) -C $(top_builddir)/src/test/isolation all
|
||||
|
||||
submake-test_snapshot_too_old:
|
||||
$(MAKE) -C $(top_builddir)/src/test/modules/snapshot_too_old
|
||||
|
||||
isolationcheck: | submake-isolation submake-test_snapshot_too_old temp-install
|
||||
$(MKDIR_P) isolation_output
|
||||
$(pg_isolation_regress_check) \
|
||||
--temp-config $(top_srcdir)/src/test/modules/snapshot_too_old/sto.conf \
|
||||
--outputdir=./isolation_output \
|
||||
$(ISOLATIONCHECKS)
|
||||
|
||||
isolationcheck-install-force: all | submake-isolation submake-test_snapshot_too_old temp-install
|
||||
$(pg_isolation_regress_installcheck) \
|
||||
$(ISOLATIONCHECKS)
|
||||
|
||||
.PHONY: check submake-test_snapshot_too_old isolationcheck isolationcheck-install-force
|
||||
|
||||
temp-install: EXTRA_INSTALL=src/test/modules/snapshot_too_old
|
@ -0,0 +1,73 @@
|
||||
Parsed test spec with 2 sessions
|
||||
|
||||
starting permutation: s1decl s1f1 s1sleep s1f2 s2u
|
||||
step s1decl: DECLARE cursor1 CURSOR FOR SELECT c FROM sto1;
|
||||
step s1f1: FETCH FIRST FROM cursor1;
|
||||
c
|
||||
|
||||
1
|
||||
step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
|
||||
setting pg_sleep
|
||||
|
||||
0
|
||||
step s1f2: FETCH FIRST FROM cursor1;
|
||||
c
|
||||
|
||||
1
|
||||
step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
|
||||
|
||||
starting permutation: s1decl s1f1 s1sleep s2u s1f2
|
||||
step s1decl: DECLARE cursor1 CURSOR FOR SELECT c FROM sto1;
|
||||
step s1f1: FETCH FIRST FROM cursor1;
|
||||
c
|
||||
|
||||
1
|
||||
step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
|
||||
setting pg_sleep
|
||||
|
||||
0
|
||||
step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
|
||||
step s1f2: FETCH FIRST FROM cursor1;
|
||||
ERROR: snapshot too old
|
||||
|
||||
starting permutation: s1decl s1f1 s2u s1sleep s1f2
|
||||
step s1decl: DECLARE cursor1 CURSOR FOR SELECT c FROM sto1;
|
||||
step s1f1: FETCH FIRST FROM cursor1;
|
||||
c
|
||||
|
||||
1
|
||||
step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
|
||||
step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
|
||||
setting pg_sleep
|
||||
|
||||
0
|
||||
step s1f2: FETCH FIRST FROM cursor1;
|
||||
ERROR: snapshot too old
|
||||
|
||||
starting permutation: s1decl s2u s1f1 s1sleep s1f2
|
||||
step s1decl: DECLARE cursor1 CURSOR FOR SELECT c FROM sto1;
|
||||
step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
|
||||
step s1f1: FETCH FIRST FROM cursor1;
|
||||
c
|
||||
|
||||
1
|
||||
step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
|
||||
setting pg_sleep
|
||||
|
||||
0
|
||||
step s1f2: FETCH FIRST FROM cursor1;
|
||||
ERROR: snapshot too old
|
||||
|
||||
starting permutation: s2u s1decl s1f1 s1sleep s1f2
|
||||
step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
|
||||
step s1decl: DECLARE cursor1 CURSOR FOR SELECT c FROM sto1;
|
||||
step s1f1: FETCH FIRST FROM cursor1;
|
||||
c
|
||||
|
||||
2
|
||||
step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
|
||||
setting pg_sleep
|
||||
|
||||
0
|
||||
step s1f2: FETCH FIRST FROM cursor1;
|
||||
ERROR: snapshot too old
|
@ -0,0 +1,55 @@
|
||||
Parsed test spec with 2 sessions
|
||||
|
||||
starting permutation: s1f1 s1sleep s1f2 s2u
|
||||
step s1f1: SELECT c FROM sto1 ORDER BY c LIMIT 1;
|
||||
c
|
||||
|
||||
1
|
||||
step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
|
||||
setting pg_sleep
|
||||
|
||||
0
|
||||
step s1f2: SELECT c FROM sto1 ORDER BY c LIMIT 1;
|
||||
c
|
||||
|
||||
1
|
||||
step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
|
||||
|
||||
starting permutation: s1f1 s1sleep s2u s1f2
|
||||
step s1f1: SELECT c FROM sto1 ORDER BY c LIMIT 1;
|
||||
c
|
||||
|
||||
1
|
||||
step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
|
||||
setting pg_sleep
|
||||
|
||||
0
|
||||
step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
|
||||
step s1f2: SELECT c FROM sto1 ORDER BY c LIMIT 1;
|
||||
ERROR: snapshot too old
|
||||
|
||||
starting permutation: s1f1 s2u s1sleep s1f2
|
||||
step s1f1: SELECT c FROM sto1 ORDER BY c LIMIT 1;
|
||||
c
|
||||
|
||||
1
|
||||
step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
|
||||
step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
|
||||
setting pg_sleep
|
||||
|
||||
0
|
||||
step s1f2: SELECT c FROM sto1 ORDER BY c LIMIT 1;
|
||||
ERROR: snapshot too old
|
||||
|
||||
starting permutation: s2u s1f1 s1sleep s1f2
|
||||
step s2u: UPDATE sto1 SET c = 1001 WHERE c = 1;
|
||||
step s1f1: SELECT c FROM sto1 ORDER BY c LIMIT 1;
|
||||
c
|
||||
|
||||
2
|
||||
step s1sleep: SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold';
|
||||
setting pg_sleep
|
||||
|
||||
0
|
||||
step s1f2: SELECT c FROM sto1 ORDER BY c LIMIT 1;
|
||||
ERROR: snapshot too old
|
@ -0,0 +1,37 @@
|
||||
# This test provokes a "snapshot too old" error using a cursor.
|
||||
#
|
||||
# The sleep is needed because with a threshold of zero a statement could error
|
||||
# on changes it made. With more normal settings no external delay is needed,
|
||||
# but we don't want these tests to run long enough to see that, since
|
||||
# granularity is in minutes.
|
||||
#
|
||||
# Since results depend on the value of old_snapshot_threshold, sneak that into
|
||||
# the line generated by the sleep, so that a surprising values isn't so hard
|
||||
# to identify.
|
||||
|
||||
setup
|
||||
{
|
||||
CREATE TABLE sto1 (c int NOT NULL);
|
||||
INSERT INTO sto1 SELECT generate_series(1, 1000);
|
||||
CREATE TABLE sto2 (c int NOT NULL);
|
||||
}
|
||||
setup
|
||||
{
|
||||
VACUUM ANALYZE sto1;
|
||||
}
|
||||
|
||||
teardown
|
||||
{
|
||||
DROP TABLE sto1, sto2;
|
||||
}
|
||||
|
||||
session "s1"
|
||||
setup { BEGIN ISOLATION LEVEL REPEATABLE READ; }
|
||||
step "s1decl" { DECLARE cursor1 CURSOR FOR SELECT c FROM sto1; }
|
||||
step "s1f1" { FETCH FIRST FROM cursor1; }
|
||||
step "s1sleep" { SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold'; }
|
||||
step "s1f2" { FETCH FIRST FROM cursor1; }
|
||||
teardown { COMMIT; }
|
||||
|
||||
session "s2"
|
||||
step "s2u" { UPDATE sto1 SET c = 1001 WHERE c = 1; }
|
@ -0,0 +1,36 @@
|
||||
# This test provokes a "snapshot too old" error using SELECT statements.
|
||||
#
|
||||
# The sleep is needed because with a threshold of zero a statement could error
|
||||
# on changes it made. With more normal settings no external delay is needed,
|
||||
# but we don't want these tests to run long enough to see that, since
|
||||
# granularity is in minutes.
|
||||
#
|
||||
# Since results depend on the value of old_snapshot_threshold, sneak that into
|
||||
# the line generated by the sleep, so that a surprising values isn't so hard
|
||||
# to identify.
|
||||
|
||||
setup
|
||||
{
|
||||
CREATE TABLE sto1 (c int NOT NULL);
|
||||
INSERT INTO sto1 SELECT generate_series(1, 1000);
|
||||
CREATE TABLE sto2 (c int NOT NULL);
|
||||
}
|
||||
setup
|
||||
{
|
||||
VACUUM ANALYZE sto1;
|
||||
}
|
||||
|
||||
teardown
|
||||
{
|
||||
DROP TABLE sto1, sto2;
|
||||
}
|
||||
|
||||
session "s1"
|
||||
setup { BEGIN ISOLATION LEVEL REPEATABLE READ; }
|
||||
step "s1f1" { SELECT c FROM sto1 ORDER BY c LIMIT 1; }
|
||||
step "s1sleep" { SELECT setting, pg_sleep(6) FROM pg_settings WHERE name = 'old_snapshot_threshold'; }
|
||||
step "s1f2" { SELECT c FROM sto1 ORDER BY c LIMIT 1; }
|
||||
teardown { COMMIT; }
|
||||
|
||||
session "s2"
|
||||
step "s2u" { UPDATE sto1 SET c = 1001 WHERE c = 1; }
|
3
src/test/modules/snapshot_too_old/sto.conf
Normal file
3
src/test/modules/snapshot_too_old/sto.conf
Normal file
@ -0,0 +1,3 @@
|
||||
autovacuum = off
|
||||
old_snapshot_threshold = 0
|
||||
|
Reference in New Issue
Block a user