Skip to content

Commit fe533e2

Browse files
committed
Use DELETE after compression
The last step of compressing chunk is cleanup the uncompressed chunk and currently it is done by a `TRUNCATE` that requires an `AccessExclusiveLock` preventing concurrent sessions even `SELECT` data from the hypertable. With this PR will be possible to execute a `DELETE` instead of a `TRUNCATE` on the uncompressed chunk relaxing the lock to `RowExclusiveLock`. This new behavior is controled by a new GUC `timescaledb.enable_delete_after_compression` that is `false` by default. The side effect of enabling this behavior will be more WAL generation because we'll delete each row from uncompressed chunk and also bloat due to a lot of dead tuples created.
1 parent b8d958c commit fe533e2

File tree

8 files changed

+343
-33
lines changed

8 files changed

+343
-33
lines changed

.unreleased/pr_7116

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implements: #7116 Use DELETE instead of TRUNCATE after compression

src/guc.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ static char *ts_guc_default_segmentby_fn = NULL;
8585
static char *ts_guc_default_orderby_fn = NULL;
8686
TSDLLEXPORT bool ts_guc_enable_job_execution_logging = false;
8787
bool ts_guc_enable_tss_callbacks = true;
88+
TSDLLEXPORT bool ts_guc_enable_delete_after_compression = false;
8889

8990
/* default value of ts_guc_max_open_chunks_per_insert and ts_guc_max_cached_chunks_per_hypertable
9091
* will be set as their respective boot-value when the GUC mechanism starts up */
@@ -685,6 +686,17 @@ _guc_init(void)
685686
NULL,
686687
NULL);
687688

689+
DefineCustomBoolVariable(MAKE_EXTOPTION("enable_delete_after_compression"),
690+
"Delete all rows after compression instead of truncate",
691+
"Delete all rows after compression instead of truncate",
692+
&ts_guc_enable_delete_after_compression,
693+
false,
694+
PGC_USERSET,
695+
0,
696+
NULL,
697+
NULL,
698+
NULL);
699+
688700
#ifdef USE_TELEMETRY
689701
DefineCustomEnumVariable(MAKE_EXTOPTION("telemetry_level"),
690702
"Telemetry settings level",

src/guc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ extern int ts_guc_max_open_chunks_per_insert;
4242
extern int ts_guc_max_cached_chunks_per_hypertable;
4343
extern TSDLLEXPORT bool ts_guc_enable_job_execution_logging;
4444
extern bool ts_guc_enable_tss_callbacks;
45+
extern TSDLLEXPORT bool ts_guc_enable_delete_after_compression;
4546

4647
#ifdef USE_TELEMETRY
4748
typedef enum TelemetryLevel

tsl/src/compression/compression.c

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,46 @@ truncate_relation(Oid table_oid)
161161
table_close(rel, NoLock);
162162
}
163163

164+
/* Handle the all rows deletion of a given relation */
165+
static void
166+
RelationDeleteAllRows(Relation rel, Snapshot snap)
167+
{
168+
TupleTableSlot *slot = table_slot_create(rel, NULL);
169+
TableScanDesc scan = table_beginscan(rel, snap, 0, (ScanKey) NULL);
170+
171+
while (table_scan_getnextslot(scan, ForwardScanDirection, slot))
172+
{
173+
simple_table_tuple_delete(rel, &(slot->tts_tid), snap);
174+
}
175+
table_endscan(scan);
176+
ExecDropSingleTupleTableSlot(slot);
177+
}
178+
179+
/*
180+
* Delete the relation WITHOUT applying triggers. This will be used when
181+
* `enable_delete_after_compression = true` instead of truncating the relation.
182+
* Also don't restart sequences.
183+
*/
184+
static void
185+
delete_relation_rows(Oid table_oid)
186+
{
187+
Relation rel = table_open(table_oid, RowExclusiveLock);
188+
Snapshot snap = GetLatestSnapshot();
189+
190+
/* Delete the rows in the table */
191+
RelationDeleteAllRows(rel, snap);
192+
193+
/* Delete the rows in the toast table */
194+
if (OidIsValid(rel->rd_rel->reltoastrelid))
195+
{
196+
Relation toast_rel = table_open(rel->rd_rel->reltoastrelid, RowExclusiveLock);
197+
RelationDeleteAllRows(toast_rel, snap);
198+
table_close(toast_rel, NoLock);
199+
}
200+
201+
table_close(rel, NoLock);
202+
}
203+
164204
/*
165205
* Use reltuples as an estimate for the number of rows that will get compressed. This value
166206
* might be way off the mark in case analyze hasn't happened in quite a while on this input
@@ -191,7 +231,7 @@ compress_chunk(Oid in_table, Oid out_table, int insert_options)
191231
{
192232
int n_keys;
193233
ListCell *lc;
194-
int indexscan_direction = NoMovementScanDirection;
234+
ScanDirection indexscan_direction = NoMovementScanDirection;
195235
Relation matched_index_rel = NULL;
196236
TupleTableSlot *slot;
197237
IndexScanDesc index_scan;
@@ -456,8 +496,17 @@ compress_chunk(Oid in_table, Oid out_table, int insert_options)
456496
}
457497

458498
row_compressor_close(&row_compressor);
459-
DEBUG_WAITPOINT("compression_done_before_truncate_uncompressed");
460-
truncate_relation(in_table);
499+
if (!ts_guc_enable_delete_after_compression)
500+
{
501+
DEBUG_WAITPOINT("compression_done_before_truncate_uncompressed");
502+
truncate_relation(in_table);
503+
DEBUG_WAITPOINT("compression_done_after_truncate_uncompressed");
504+
}
505+
else
506+
{
507+
delete_relation_rows(in_table);
508+
DEBUG_WAITPOINT("compression_done_after_delete_uncompressed");
509+
}
461510

462511
table_close(out_rel, NoLock);
463512
table_close(in_rel, NoLock);

tsl/test/expected/compression.out

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2874,3 +2874,58 @@ SELECT compress_chunk(ch) FROM show_chunks('hyper_84') ch;
28742874

28752875
-- indexscan for decompression: DELETE
28762876
DELETE FROM hyper_84 WHERE device = 1;
2877+
-- Test using DELETE instead of TRUNCATE after compression
2878+
CREATE TABLE hyper_delete (time timestamptz, device int, location int, temp float, t text);
2879+
SELECT table_name FROM create_hypertable('hyper_delete', 'time');
2880+
NOTICE: adding not-null constraint to column "time"
2881+
table_name
2882+
--------------
2883+
hyper_delete
2884+
(1 row)
2885+
2886+
INSERT INTO hyper_delete VALUES ('2024-07-10', 1, 1, 1.0, repeat('X', 10000));
2887+
ANALYZE hyper_delete;
2888+
SELECT ch AS "CHUNK" FROM show_chunks('hyper_delete') ch \gset
2889+
SELECT relpages, reltuples::int AS reltuples FROM pg_catalog.pg_class WHERE oid = :'CHUNK'::regclass;
2890+
relpages | reltuples
2891+
----------+-----------
2892+
1 | 1
2893+
(1 row)
2894+
2895+
-- One uncompressed row
2896+
SELECT count(*) FROM :CHUNK;
2897+
count
2898+
-------
2899+
1
2900+
(1 row)
2901+
2902+
ALTER TABLE hyper_delete SET (timescaledb.compress, timescaledb.compress_segmentby='device');
2903+
NOTICE: default order by for hypertable "hyper_delete" is set to ""time" DESC"
2904+
SET timescaledb.enable_delete_after_compression TO true;
2905+
SELECT FROM compress_chunk(:'CHUNK');
2906+
--
2907+
(1 row)
2908+
2909+
-- still have more than one tuple
2910+
SELECT relpages, reltuples::int AS reltuples FROM pg_catalog.pg_class WHERE oid = :'CHUNK'::regclass;
2911+
relpages | reltuples
2912+
----------+-----------
2913+
1 | 1
2914+
(1 row)
2915+
2916+
ANALYZE hyper_delete;
2917+
-- after ANALYZE we should have no tuples
2918+
SELECT relpages, reltuples::int AS reltuples FROM pg_catalog.pg_class WHERE oid = :'CHUNK'::regclass;
2919+
relpages | reltuples
2920+
----------+-----------
2921+
1 | 0
2922+
(1 row)
2923+
2924+
-- One compressed row
2925+
SELECT count(*) FROM :CHUNK;
2926+
count
2927+
-------
2928+
1
2929+
(1 row)
2930+
2931+
RESET timescaledb.enable_delete_after_compression;

0 commit comments

Comments
 (0)