-
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
240 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
130 changes: 130 additions & 0 deletions
130
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,130 @@ | ||
# 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. For this reason we need two processes that end up | ||
# in the deadlock (VACUUM ANALYZE and DROP INDEX respectively) and | ||
# then two extra sessions working in lock-step to create the deadlock. | ||
# | ||
# It can be illustrated with this sequence, where we have a chunk | ||
# table and a chunk index. The sessions between the brackets ([]) is | ||
# the lock queue and session holding the lock is in angle brackets | ||
# (<>) is the session that holds the lock on the object in question: | ||
# | ||
# S1: Lock chunk from hypertable | ||
# index: [] | ||
# table: <S1> [] | ||
# S3: Start VACUUM ANALYZE, will attempt to lock chunk table, but get queued. | ||
# index: [] | ||
# table: <S1> [S3] | ||
# S2: Lock chunk table from hypertable, which will be queued | ||
# index: [] | ||
# table: <S1> [S3 S2] | ||
# S1: Unlock chunk table | ||
# index: [] | ||
# table: [S3 S2] | ||
# S3: VACUUM ANALYZE continues and takes the lock on the chunk table. | ||
# index: [] | ||
# table: <S3> [S2] | ||
# S3: VACUUM ANALYZE will release the lock on the chunk table. | ||
# index: [] | ||
# table: [S2] | ||
# S3: VACUUM ANALYZE will attempt to lock the chunk table again | ||
# index: [] | ||
# table: [S2 S3] | ||
# S2: The LOCK statement gets 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 table | ||
# index: <S4> [] | ||
# table: <S2> [S3 S4] | ||
# S2: Release lock on chunk table | ||
# 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); | ||
|
||
-- 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 |