-
Notifications
You must be signed in to change notification settings - Fork 901
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
If an index is dropped, it is necessary to lock the heap table (of the index) before the index since all normal operations do it in this order. When dropping an index, we did not take all the necessary locks in the right order before calling `performMultipleDeletions`, which can cause deadlocks when dropping an index on a hypertable at the same time as running a utility statement that takes heavy locks, e.g., VACUUM or ANALYZE. Adding a isolation test as well that will generate a deadlock if the index and table locks are not taken in the correct order.
- Loading branch information
Showing
5 changed files
with
245 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fixes: #7600 Fix lock order when dropping index |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
tsl/test/isolation/expected/deadlock_drop_index_vacuum.out
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
Parsed test spec with 4 sessions | ||
|
||
starting permutation: S1_lock S3_vacuum S2_lock S1_commit S4_drop_index S2_commit | ||
step S1_lock: | ||
LOCK TABLE _timescaledb_internal.metrics_chunk_2 IN ACCESS EXCLUSIVE MODE; | ||
|
||
step S3_vacuum: | ||
VACUUM ANALYZE _timescaledb_internal.metrics_chunk_2; | ||
<waiting ...> | ||
step S2_lock: | ||
LOCK TABLE _timescaledb_internal.metrics_chunk_2 IN ACCESS EXCLUSIVE MODE; | ||
<waiting ...> | ||
step S1_commit: | ||
COMMIT; | ||
|
||
step S2_lock: <... completed> | ||
step S4_drop_index: | ||
DROP INDEX metrics_device_time_idx; | ||
<waiting ...> | ||
step S2_commit: | ||
COMMIT; | ||
|
||
step S3_vacuum: <... completed> | ||
step S4_drop_index: <... completed> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
tsl/test/isolation/specs/deadlock_drop_index_vacuum.spec
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
# This file and its contents are licensed under the Timescale License. | ||
# Please see the included NOTICE for copyright information and | ||
# LICENSE-TIMESCALE for a copy of the license. | ||
|
||
# Order of locks in drop index and vacuum analyze was wrong, and DROP | ||
# INDEX took the locks in order index-table, while VACUUM ANALYZE took | ||
# them in order table-index. | ||
# | ||
# Create deadlock if the locking order is wrong. Problem here is that | ||
# vacuum takes two locks. First one to read a table and then one to do | ||
# the actual vacuum, so we need two sessions working in lock-step to | ||
# create the deadlock. | ||
# | ||
# S1: Lock chunk from hypertable | ||
# index: [] | ||
# table: S1 [] | ||
# S3: Start VACUUM ANALYZE, will attempt to lock chunk, but get queued. | ||
# index: [] | ||
# table: S1 [S3] | ||
# S2: Lock chunk from hypertable, which will be queued | ||
# index: [] | ||
# table: S1 [S3 S2] | ||
# S1: Unlock chunk | ||
# index: [] | ||
# table: [S3 S2] | ||
# S3: VACUUM ANALYZE continues and takes the lock on the chunk. | ||
# index: [] | ||
# table: S3 [S2] | ||
# S3: VACUUM ANALYZE will release the lock on the chunk. | ||
# index: [] | ||
# table: [S2] | ||
# S3: VACUUM ANALYZE will attempt to lock the chunk again | ||
# index: [] | ||
# table: [S2 S3] | ||
# S2: The LOCK statement get the lock and VACUUM will wait | ||
# index: [] | ||
# table: S2 [S3] | ||
# S4: DROP INDEX starts and takes lock in index first and then is | ||
# queued for the chunk | ||
# index: S4 [] | ||
# table: S2 [S3 S4] | ||
# S2: Release lock on chunk | ||
# index: S4 [] | ||
# table: [S3 S4] | ||
# S3: VACUUM continues and takes table lock and then tries index lock | ||
# index: S4 [S3] | ||
# table: S3 [S4] | ||
# Deadlock | ||
|
||
setup { | ||
CREATE TABLE metrics (time timestamptz, device_id integer, temp float); | ||
SELECT create_hypertable('metrics', 'time', chunk_time_interval => interval '1 day'); | ||
INSERT INTO metrics | ||
SELECT generate_series('2018-12-01 00:00'::timestamp, | ||
'2018-12-03 00:00', | ||
'1 hour'), | ||
(random()*30)::int, | ||
random()*80 - 40; | ||
|
||
CREATE INDEX metrics_device_time_idx ON metrics(device_id, time NULLS FIRST); | ||
|
||
CREATE VIEW backend_status(pid, relation, mode, granted, query) AS | ||
SELECT pid, | ||
(SELECT relname FROM pg_class WHERE relation = oid) AS relname, | ||
mode, | ||
granted, | ||
query | ||
FROM pg_locks JOIN pg_stat_activity USING (pid) | ||
WHERE locktype = 'relation' AND pid != pg_backend_pid() | ||
AND backend_type = 'client backend'; | ||
|
||
|
||
-- Rename chunks so that we have known names. We cannot execute | ||
-- VACUUM in a function block. | ||
DO $$ | ||
DECLARE | ||
chunk regclass; | ||
count int = 1; | ||
BEGIN | ||
FOR chunk IN SELECT ch FROM show_chunks('metrics') ch | ||
LOOP | ||
EXECUTE format('ALTER TABLE %s RENAME TO metrics_chunk_%s', chunk, count); | ||
count = count + 1; | ||
END LOOP; | ||
END | ||
$$; | ||
} | ||
|
||
teardown { | ||
DROP TABLE metrics; | ||
} | ||
|
||
session "S1" | ||
setup { | ||
START TRANSACTION; | ||
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; | ||
SET LOCAL lock_timeout = '500ms'; | ||
SET LOCAL deadlock_timeout = '300ms'; | ||
} | ||
|
||
step "S1_lock" { | ||
LOCK TABLE _timescaledb_internal.metrics_chunk_2 IN ACCESS EXCLUSIVE MODE; | ||
} | ||
|
||
step "S1_commit" { | ||
COMMIT; | ||
} | ||
|
||
session "S2" | ||
setup { | ||
START TRANSACTION; | ||
SET TRANSACTION ISOLATION LEVEL READ COMMITTED; | ||
SET LOCAL lock_timeout = '500ms'; | ||
SET LOCAL deadlock_timeout = '300ms'; | ||
} | ||
|
||
step "S2_lock" { | ||
LOCK TABLE _timescaledb_internal.metrics_chunk_2 IN ACCESS EXCLUSIVE MODE; | ||
} | ||
|
||
step "S2_commit" { | ||
COMMIT; | ||
} | ||
|
||
session "S3" | ||
step "S3_vacuum" { | ||
VACUUM ANALYZE _timescaledb_internal.metrics_chunk_2; | ||
} | ||
|
||
session "S4" | ||
step "S4_drop_index" { | ||
DROP INDEX metrics_device_time_idx; | ||
} | ||
|
||
permutation S1_lock S3_vacuum S2_lock S1_commit S4_drop_index S2_commit |