Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .unreleased/fix_vacuum_compressed_chunk
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixes: #10286 Propagate VACUUM on a chunk to the compressed relation when running on chunk directly
Thanks: @h0rn3t for reporting a problem with VACUUM not propagating to the compressed relation
17 changes: 17 additions & 0 deletions src/process_utility.c
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,23 @@ process_vacuum(ProcessUtilityArgs *args)
ctx.ht_vacuum_rel = vacuum_rel;
foreach_chunk(ht, add_chunk_to_vacuum, &ctx, false);
}
else
{
/* VACUUM targets a chunk directly. */
Chunk *chunk = ts_chunk_get_by_relid(table_relid, false);
if (chunk && ts_chunk_is_compressed(chunk))
{
Oid compressed_relid = ts_relation_get_compressed_relid(chunk->fd.relid);
/* Compressed chunk might be missing due to concurrent operations */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean we also need to lock the relation itself?

@akuzm akuzm Jul 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lookup apparently locks the settings tuple, but I'm not sure if the lock is kept or if it's sufficient, and the "rebuild if needed" uses NoLock.

if (OidIsValid(compressed_relid))
{
ctx.chunk_rels =
lappend(ctx.chunk_rels,
makeVacuumRelation(NULL, compressed_relid, NIL));
}
}
register_chunk_for_rebuild_if_needed(table_relid, &ctx);
}
}
vacuum_rels = lappend(vacuum_rels, vacuum_rel);
}
Expand Down
32 changes: 32 additions & 0 deletions tsl/test/expected/vacuum.out
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,35 @@ DROP FUNCTION cagg_analyze_count_analyzed(name);
DROP MATERIALIZED VIEW cagg_analyze_view;
NOTICE: drop cascades to table _timescaledb_internal._hyper_6_9_chunk
DROP TABLE cagg_analyze_src;
-- VACUUM FULL on an individual chunk should also rewrite the underlying compressed relation
-- VACUUM FULL assigns a new relfilenode, so we use that as a proxy for the
-- compressed relation actually being processed.
CREATE TABLE vacuum_chunk_test(time timestamptz NOT NULL, device int, value float);
SELECT create_hypertable('vacuum_chunk_test', 'time', chunk_time_interval => interval '1 day');
create_hypertable
--------------------------------
(7,public,vacuum_chunk_test,t)

INSERT INTO vacuum_chunk_test
SELECT '2024-01-01'::timestamptz + (i || ' minute')::interval, i % 4, i::float
FROM generate_series(0, 5000) i;
ALTER TABLE vacuum_chunk_test SET (timescaledb.compress, timescaledb.compress_segmentby = 'device');
SELECT count(compress_chunk(ch)) FROM show_chunks('vacuum_chunk_test') ch;
count
-------
4

SELECT ch AS chunk FROM show_chunks('vacuum_chunk_test') ch ORDER BY ch LIMIT 1 \gset
SELECT cs.compress_relid::oid AS compressed_relid
FROM _timescaledb_catalog.compression_settings cs
WHERE cs.relid = :'chunk'::regclass \gset
SELECT relfilenode AS compressed_relfilenode_before
FROM pg_class WHERE oid = :compressed_relid \gset
VACUUM FULL :chunk;
SELECT relfilenode <> :compressed_relfilenode_before AS compressed_rel_rewritten
FROM pg_class WHERE oid = :compressed_relid;
compressed_rel_rewritten
--------------------------
t

DROP TABLE vacuum_chunk_test;
29 changes: 29 additions & 0 deletions tsl/test/sql/vacuum.sql
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,32 @@ WHERE parent.hypertable_id = (SELECT mat_hypertable_id
DROP FUNCTION cagg_analyze_count_analyzed(name);
DROP MATERIALIZED VIEW cagg_analyze_view;
DROP TABLE cagg_analyze_src;

-- VACUUM FULL on an individual chunk should also rewrite the underlying compressed relation
-- VACUUM FULL assigns a new relfilenode, so we use that as a proxy for the
-- compressed relation actually being processed.
CREATE TABLE vacuum_chunk_test(time timestamptz NOT NULL, device int, value float);
SELECT create_hypertable('vacuum_chunk_test', 'time', chunk_time_interval => interval '1 day');

INSERT INTO vacuum_chunk_test
SELECT '2024-01-01'::timestamptz + (i || ' minute')::interval, i % 4, i::float
FROM generate_series(0, 5000) i;

ALTER TABLE vacuum_chunk_test SET (timescaledb.compress, timescaledb.compress_segmentby = 'device');
SELECT count(compress_chunk(ch)) FROM show_chunks('vacuum_chunk_test') ch;

SELECT ch AS chunk FROM show_chunks('vacuum_chunk_test') ch ORDER BY ch LIMIT 1 \gset

SELECT cs.compress_relid::oid AS compressed_relid
FROM _timescaledb_catalog.compression_settings cs
WHERE cs.relid = :'chunk'::regclass \gset

SELECT relfilenode AS compressed_relfilenode_before
FROM pg_class WHERE oid = :compressed_relid \gset

VACUUM FULL :chunk;

SELECT relfilenode <> :compressed_relfilenode_before AS compressed_rel_rewritten
FROM pg_class WHERE oid = :compressed_relid;

DROP TABLE vacuum_chunk_test;
Loading