From 803b1301e8c9aac478abeec62824a5d09664ffff Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 4 Oct 2018 09:00:33 +0900 Subject: [PATCH] Add option SKIP_LOCKED to VACUUM and ANALYZE When specified, this option allows VACUUM to skip the work on a relation if there is a conflicting lock on it when trying to open it at the beginning of its processing. Similarly to autovacuum, this comes with a couple of limitations while the relation is processed which can cause the process to still block: - when opening the relation indexes. - when acquiring row samples for table inheritance trees, partition trees or certain types of foreign tables, and that a lock is taken on some leaves of such trees. Author: Nathan Bossart Reviewed-by: Michael Paquier, Andres Freund, Masahiko Sawada Discussion: https://postgr.es/m/9EF7EBE4-720D-4CF1-9D0E-4403D7E92990@amazon.com Discussion: https://postgr.es/m/20171201160907.27110.74730@wrigleys.postgresql.org --- doc/src/sgml/ref/analyze.sgml | 19 ++ doc/src/sgml/ref/vacuum.sgml | 21 +++ src/backend/commands/vacuum.c | 26 ++- src/backend/parser/gram.y | 12 ++ src/include/nodes/parsenodes.h | 3 +- .../isolation/expected/vacuum-skip-locked.out | 171 ++++++++++++++++++ src/test/isolation/isolation_schedule | 1 + .../isolation/specs/vacuum-skip-locked.spec | 59 ++++++ src/test/regress/expected/vacuum.out | 16 +- src/test/regress/sql/vacuum.sql | 12 +- 10 files changed, 333 insertions(+), 7 deletions(-) create mode 100644 src/test/isolation/expected/vacuum-skip-locked.out create mode 100644 src/test/isolation/specs/vacuum-skip-locked.spec diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml index 10b3a9a673..fea7f46521 100644 --- a/doc/src/sgml/ref/analyze.sgml +++ b/doc/src/sgml/ref/analyze.sgml @@ -27,6 +27,7 @@ ANALYZE [ VERBOSE ] [ table_and_columnswhere option can be one of: VERBOSE + SKIP_LOCKED and table_and_columns is: @@ -76,6 +77,24 @@ ANALYZE [ VERBOSE ] [ table_and_columns + + SKIP_LOCKED + + + Specifies that ANALYZE should not wait for any + conflicting locks to be released when beginning work on a relation: + if a relation cannot be locked immediately without waiting, the relation + is skipped. Note that even with this option, ANALYZE + may still block when opening the relation's indexes or when acquiring + sample rows from partitions, table inheritance children, and some + types of foreign tables. Also, while ANALYZE + ordinarily processes all partitions of specified partitioned tables, + this option will cause ANALYZE to skip all + partitions if there is a conflicting lock on the partitioned table. + + + + table_name diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index b760e8ede1..fd911f5776 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -31,6 +31,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ table_and_columns is: @@ -160,6 +161,26 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ table_name diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 4e3823b0f0..a86963fc86 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -622,6 +622,7 @@ expand_vacuum_rel(VacuumRelation *vrel, int options) HeapTuple tuple; Form_pg_class classForm; bool include_parts; + int rvr_opts; /* * Since autovacuum workers supply OIDs when calling vacuum(), no @@ -634,7 +635,30 @@ expand_vacuum_rel(VacuumRelation *vrel, int options) * below, as well as find_all_inheritors's expectation that the caller * holds some lock on the starting relation. */ - relid = RangeVarGetRelid(vrel->relation, AccessShareLock, false); + rvr_opts = (options & VACOPT_SKIP_LOCKED) ? RVR_SKIP_LOCKED : 0; + relid = RangeVarGetRelidExtended(vrel->relation, + AccessShareLock, + rvr_opts, + NULL, NULL); + + /* + * If the lock is unavailable, emit the same log statement that + * vacuum_rel() and analyze_rel() would. + */ + if (!OidIsValid(relid)) + { + if (options & VACOPT_VACUUM) + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping vacuum of \"%s\" --- lock not available", + vrel->relation->relname))); + else + ereport(WARNING, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("skipping analyze of \"%s\" --- lock not available", + vrel->relation->relname))); + return vacrels; + } /* * To check whether the relation is a partitioned table and its diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ce60e99cff..6d23bfb0b3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10538,6 +10538,8 @@ vacuum_option_elem: { if (strcmp($1, "disable_page_skipping") == 0) $$ = VACOPT_DISABLE_PAGE_SKIPPING; + else if (strcmp($1, "skip_locked") == 0) + $$ = VACOPT_SKIP_LOCKED; else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -10571,6 +10573,16 @@ analyze_option_list: analyze_option_elem: VERBOSE { $$ = VACOPT_VERBOSE; } + | IDENT + { + if (strcmp($1, "skip_locked") == 0) + $$ = VACOPT_SKIP_LOCKED; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized ANALYZE option \"%s\"", $1), + parser_errposition(@1))); + } ; analyze_keyword: diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 200df8e659..aa4a0dba2a 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3154,8 +3154,7 @@ typedef enum VacuumOption VACOPT_VERBOSE = 1 << 2, /* print progress info */ VACOPT_FREEZE = 1 << 3, /* FREEZE option */ VACOPT_FULL = 1 << 4, /* FULL (non-concurrent) vacuum */ - VACOPT_SKIP_LOCKED = 1 << 5, /* skip if cannot get lock (autovacuum - * only) */ + VACOPT_SKIP_LOCKED = 1 << 5, /* skip if cannot get lock */ VACOPT_SKIPTOAST = 1 << 6, /* don't process the TOAST table, if any */ VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */ } VacuumOption; diff --git a/src/test/isolation/expected/vacuum-skip-locked.out b/src/test/isolation/expected/vacuum-skip-locked.out new file mode 100644 index 0000000000..95ca4569cc --- /dev/null +++ b/src/test/isolation/expected/vacuum-skip-locked.out @@ -0,0 +1,171 @@ +Parsed test spec with 2 sessions + +starting permutation: lock_share vac_specified commit +step lock_share: + BEGIN; + LOCK part1 IN SHARE MODE; + +WARNING: skipping vacuum of "part1" --- lock not available +step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; +step commit: + COMMIT; + + +starting permutation: lock_share vac_all_parts commit +step lock_share: + BEGIN; + LOCK part1 IN SHARE MODE; + +step vac_all_parts: VACUUM (SKIP_LOCKED) parted; +step commit: + COMMIT; + + +starting permutation: lock_share analyze_specified commit +step lock_share: + BEGIN; + LOCK part1 IN SHARE MODE; + +WARNING: skipping analyze of "part1" --- lock not available +step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; +step commit: + COMMIT; + + +starting permutation: lock_share analyze_all_parts commit +step lock_share: + BEGIN; + LOCK part1 IN SHARE MODE; + +step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; +step commit: + COMMIT; + + +starting permutation: lock_share vac_analyze_specified commit +step lock_share: + BEGIN; + LOCK part1 IN SHARE MODE; + +WARNING: skipping vacuum of "part1" --- lock not available +step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; +step commit: + COMMIT; + + +starting permutation: lock_share vac_analyze_all_parts commit +step lock_share: + BEGIN; + LOCK part1 IN SHARE MODE; + +step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; +step commit: + COMMIT; + + +starting permutation: lock_share vac_full_specified commit +step lock_share: + BEGIN; + LOCK part1 IN SHARE MODE; + +WARNING: skipping vacuum of "part1" --- lock not available +step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; +step commit: + COMMIT; + + +starting permutation: lock_share vac_full_all_parts commit +step lock_share: + BEGIN; + LOCK part1 IN SHARE MODE; + +step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; +step commit: + COMMIT; + + +starting permutation: lock_access_exclusive vac_specified commit +step lock_access_exclusive: + BEGIN; + LOCK part1 IN ACCESS EXCLUSIVE MODE; + +WARNING: skipping vacuum of "part1" --- lock not available +step vac_specified: VACUUM (SKIP_LOCKED) part1, part2; +step commit: + COMMIT; + + +starting permutation: lock_access_exclusive vac_all_parts commit +step lock_access_exclusive: + BEGIN; + LOCK part1 IN ACCESS EXCLUSIVE MODE; + +step vac_all_parts: VACUUM (SKIP_LOCKED) parted; +step commit: + COMMIT; + + +starting permutation: lock_access_exclusive analyze_specified commit +step lock_access_exclusive: + BEGIN; + LOCK part1 IN ACCESS EXCLUSIVE MODE; + +WARNING: skipping analyze of "part1" --- lock not available +step analyze_specified: ANALYZE (SKIP_LOCKED) part1, part2; +step commit: + COMMIT; + + +starting permutation: lock_access_exclusive analyze_all_parts commit +step lock_access_exclusive: + BEGIN; + LOCK part1 IN ACCESS EXCLUSIVE MODE; + +step analyze_all_parts: ANALYZE (SKIP_LOCKED) parted; +step commit: + COMMIT; + +step analyze_all_parts: <... completed> + +starting permutation: lock_access_exclusive vac_analyze_specified commit +step lock_access_exclusive: + BEGIN; + LOCK part1 IN ACCESS EXCLUSIVE MODE; + +WARNING: skipping vacuum of "part1" --- lock not available +step vac_analyze_specified: VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; +step commit: + COMMIT; + + +starting permutation: lock_access_exclusive vac_analyze_all_parts commit +step lock_access_exclusive: + BEGIN; + LOCK part1 IN ACCESS EXCLUSIVE MODE; + +step vac_analyze_all_parts: VACUUM (ANALYZE, SKIP_LOCKED) parted; +step commit: + COMMIT; + +step vac_analyze_all_parts: <... completed> + +starting permutation: lock_access_exclusive vac_full_specified commit +step lock_access_exclusive: + BEGIN; + LOCK part1 IN ACCESS EXCLUSIVE MODE; + +WARNING: skipping vacuum of "part1" --- lock not available +step vac_full_specified: VACUUM (SKIP_LOCKED, FULL) part1, part2; +step commit: + COMMIT; + + +starting permutation: lock_access_exclusive vac_full_all_parts commit +step lock_access_exclusive: + BEGIN; + LOCK part1 IN ACCESS EXCLUSIVE MODE; + +step vac_full_all_parts: VACUUM (SKIP_LOCKED, FULL) parted; +step commit: + COMMIT; + diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index c23b401225..dd57a96e78 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -67,6 +67,7 @@ test: vacuum-reltuples test: timeouts test: vacuum-concurrent-drop test: vacuum-conflict +test: vacuum-skip-locked test: predicate-hash test: predicate-gist test: predicate-gin diff --git a/src/test/isolation/specs/vacuum-skip-locked.spec b/src/test/isolation/specs/vacuum-skip-locked.spec new file mode 100644 index 0000000000..4d59b294ca --- /dev/null +++ b/src/test/isolation/specs/vacuum-skip-locked.spec @@ -0,0 +1,59 @@ +# Test for SKIP_LOCKED option of VACUUM and ANALYZE commands. +# +# This also verifies that log messages are not emitted for skipped relations +# that were not specified in the VACUUM or ANALYZE command. + +setup +{ + CREATE TABLE parted (a INT) PARTITION BY LIST (a); + CREATE TABLE part1 PARTITION OF parted FOR VALUES IN (1); + CREATE TABLE part2 PARTITION OF parted FOR VALUES IN (2); +} + +teardown +{ + DROP TABLE IF EXISTS parted; +} + +session "s1" +step "lock_share" +{ + BEGIN; + LOCK part1 IN SHARE MODE; +} +step "lock_access_exclusive" +{ + BEGIN; + LOCK part1 IN ACCESS EXCLUSIVE MODE; +} +step "commit" +{ + COMMIT; +} + +session "s2" +step "vac_specified" { VACUUM (SKIP_LOCKED) part1, part2; } +step "vac_all_parts" { VACUUM (SKIP_LOCKED) parted; } +step "analyze_specified" { ANALYZE (SKIP_LOCKED) part1, part2; } +step "analyze_all_parts" { ANALYZE (SKIP_LOCKED) parted; } +step "vac_analyze_specified" { VACUUM (ANALYZE, SKIP_LOCKED) part1, part2; } +step "vac_analyze_all_parts" { VACUUM (ANALYZE, SKIP_LOCKED) parted; } +step "vac_full_specified" { VACUUM (SKIP_LOCKED, FULL) part1, part2; } +step "vac_full_all_parts" { VACUUM (SKIP_LOCKED, FULL) parted; } + +permutation "lock_share" "vac_specified" "commit" +permutation "lock_share" "vac_all_parts" "commit" +permutation "lock_share" "analyze_specified" "commit" +permutation "lock_share" "analyze_all_parts" "commit" +permutation "lock_share" "vac_analyze_specified" "commit" +permutation "lock_share" "vac_analyze_all_parts" "commit" +permutation "lock_share" "vac_full_specified" "commit" +permutation "lock_share" "vac_full_all_parts" "commit" +permutation "lock_access_exclusive" "vac_specified" "commit" +permutation "lock_access_exclusive" "vac_all_parts" "commit" +permutation "lock_access_exclusive" "analyze_specified" "commit" +permutation "lock_access_exclusive" "analyze_all_parts" "commit" +permutation "lock_access_exclusive" "vac_analyze_specified" "commit" +permutation "lock_access_exclusive" "vac_analyze_all_parts" "commit" +permutation "lock_access_exclusive" "vac_full_specified" "commit" +permutation "lock_access_exclusive" "vac_full_all_parts" "commit" diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out index 5993a90247..fa9d663abd 100644 --- a/src/test/regress/expected/vacuum.out +++ b/src/test/regress/expected/vacuum.out @@ -115,10 +115,20 @@ ERROR: column "does_not_exist" of relation "vacparted" does not exist -- parenthesized syntax for ANALYZE ANALYZE (VERBOSE) does_not_exist; ERROR: relation "does_not_exist" does not exist -ANALYZE (nonexistant-arg) does_not_exist; -ERROR: syntax error at or near "nonexistant" -LINE 1: ANALYZE (nonexistant-arg) does_not_exist; +ANALYZE (nonexistent-arg) does_not_exist; +ERROR: unrecognized ANALYZE option "nonexistent" +LINE 1: ANALYZE (nonexistent-arg) does_not_exist; ^ +-- ensure argument order independence, and that SKIP_LOCKED on non-existing +-- relation still errors out. +ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist; +ERROR: relation "does_not_exist" does not exist +ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist; +ERROR: relation "does_not_exist" does not exist +-- SKIP_LOCKED option +VACUUM (SKIP_LOCKED) vactst; +VACUUM (SKIP_LOCKED, FULL) vactst; +ANALYZE (SKIP_LOCKED) vactst; DROP TABLE vaccluster; DROP TABLE vactst; DROP TABLE vacparted; diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql index 7f74da3cbd..9defa0d8b2 100644 --- a/src/test/regress/sql/vacuum.sql +++ b/src/test/regress/sql/vacuum.sql @@ -91,7 +91,17 @@ ANALYZE vactst (i), vacparted (does_not_exist); -- parenthesized syntax for ANALYZE ANALYZE (VERBOSE) does_not_exist; -ANALYZE (nonexistant-arg) does_not_exist; +ANALYZE (nonexistent-arg) does_not_exist; + +-- ensure argument order independence, and that SKIP_LOCKED on non-existing +-- relation still errors out. +ANALYZE (SKIP_LOCKED, VERBOSE) does_not_exist; +ANALYZE (VERBOSE, SKIP_LOCKED) does_not_exist; + +-- SKIP_LOCKED option +VACUUM (SKIP_LOCKED) vactst; +VACUUM (SKIP_LOCKED, FULL) vactst; +ANALYZE (SKIP_LOCKED) vactst; DROP TABLE vaccluster; DROP TABLE vactst; -- 2.40.0