timescaledb/sql/maintenance_utils.sql
Mats Kindahl aae19319c0 Rewrite recompress_chunk as procedure
When executing `recompress_chunk` and a query at the same time, a
deadlock can be generated because the chunk relation and the chunk
index and the compressed and uncompressd chunks are locked in different
orders. In particular, when `recompress_chunk` is executing, it will
first decompress the chunk and as part of that lock the uncompressed
chunk index in AccessExclusive mode and when trying to compress the
chunk again it will try to lock the uncompressed chunk in
AccessExclusive as part of truncating it.

Note that `decompress_chunk` and `compress_chunk` lock the relations in
the same order and the issue arises because the procedures are combined
inth a single transaction.

To avoid the deadlock, this commit rewrites the `recompress_chunk` to
be a procedure and adds a commit between the decompression and
compression. Committing the transaction after the decompress will allow
reads and inserts to proceed by working on the uncompressed chunk, and
the compression part of the procedure will take the necessary locks in
strict order, thereby avoiding a deadlock.

In addition, the isolation test is rewritten so that instead of adding
a waitpoint in the PL/SQL function, we implement the isolation test by
taking a lock on the compressed table after the decompression.

Fixes #3846
2021-12-09 19:42:12 +01:00

71 lines
2.5 KiB
PL/PgSQL

-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
-- chunk - the OID of the chunk to be CLUSTERed
-- index - the OID of the index to be CLUSTERed on, or NULL to use the index
-- last used
CREATE OR REPLACE FUNCTION reorder_chunk(
chunk REGCLASS,
index REGCLASS=NULL,
verbose BOOLEAN=FALSE
) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_reorder_chunk' LANGUAGE C VOLATILE;
CREATE OR REPLACE FUNCTION move_chunk(
chunk REGCLASS,
destination_tablespace Name,
index_destination_tablespace Name=NULL,
reorder_index REGCLASS=NULL,
verbose BOOLEAN=FALSE
) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_move_chunk' LANGUAGE C VOLATILE;
CREATE OR REPLACE FUNCTION compress_chunk(
uncompressed_chunk REGCLASS,
if_not_compressed BOOLEAN = false
) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_compress_chunk' LANGUAGE C STRICT VOLATILE;
CREATE OR REPLACE FUNCTION decompress_chunk(
uncompressed_chunk REGCLASS,
if_compressed BOOLEAN = false
) RETURNS REGCLASS AS '@MODULE_PATHNAME@', 'ts_decompress_chunk' LANGUAGE C STRICT VOLATILE;
-- Recompress a chunk
--
-- Will give an error if the chunk was not already compressed. In this
-- case, the user should use compress_chunk instead. Note that this
-- function cannot be executed in an explicit transaction since it
-- contains transaction control commands.
--
-- Parameters:
-- chunk: Chunk to recompress.
-- if_not_compressed: Print notice instead of error if chunk is already compressed.
CREATE OR REPLACE PROCEDURE recompress_chunk(chunk REGCLASS,
if_not_compressed BOOLEAN = false)
AS $$
DECLARE
status INT;
chunk_name TEXT[];
BEGIN
status := _timescaledb_internal.chunk_status(chunk);
-- Chunk names are in the internal catalog, but we only care about
-- the chunk name here.
chunk_name := parse_ident(chunk::text);
CASE status
WHEN 0 THEN
RAISE EXCEPTION 'call compress_chunk instead of recompress_chunk';
WHEN 1 THEN
IF if_not_compressed THEN
RAISE NOTICE 'nothing to recompress in chunk "%"', chunk_name[array_upper(chunk_name,1)];
RETURN;
ELSE
RAISE EXCEPTION 'nothing to recompress in chunk "%"', chunk_name[array_upper(chunk_name,1)];
END IF;
WHEN 3 THEN
PERFORM decompress_chunk(chunk);
COMMIT;
END CASE;
PERFORM compress_chunk(chunk, if_not_compressed);
END
$$ LANGUAGE plpgsql;