Skip to content

Commit 59822c0

Browse files
committed
Reduce decompression for INSERT with unique constraints
On INSERT into compressed chunks with unique constraints we can check for conflict without decompressing when no ON CONFLICT clause is present and we only have one unique constraint. With ON CONFLICT clause with DO NOTHING we can just skip the INSERT if we detect conflict and return early. Only for ON CONFLICT DO UPDATE/UPSERT do we need to decompress when there is a constraint conflict. Doing the optimization in the presence of multiple constraints is also possible but not part of this patch.
1 parent 1e04331 commit 59822c0

File tree

8 files changed

+139
-402
lines changed

8 files changed

+139
-402
lines changed

src/nodes/chunk_dispatch/chunk_dispatch.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ typedef struct ChunkDispatchState
7676
bool is_dropped_attr_exists;
7777
int64 batches_decompressed;
7878
int64 tuples_decompressed;
79+
80+
/* Should this INSERT be skipped due to ON CONFLICT DO NOTHING */
81+
bool skip_insert;
7982
} ChunkDispatchState;
8083

8184
extern TSDLLEXPORT bool ts_is_chunk_dispatch_state(PlanState *state);

src/nodes/chunk_dispatch/chunk_insert_state.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,5 @@ extern ChunkInsertState *ts_chunk_insert_state_create(Oid chunk_relid,
6565
const ChunkDispatch *dispatch);
6666
extern void ts_chunk_insert_state_destroy(ChunkInsertState *state);
6767

68-
OnConflictAction chunk_dispatch_get_on_conflict_action(const ChunkDispatch *dispatch);
68+
TSDLLEXPORT OnConflictAction chunk_dispatch_get_on_conflict_action(const ChunkDispatch *dispatch);
6969
void ts_set_compression_status(ChunkInsertState *state, const Chunk *chunk);

src/nodes/hypertable_modify.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,14 @@ ExecModifyTable(CustomScanState *cs_node, PlanState *pstate)
687687

688688
context.planSlot = ExecProcNode(subplanstate);
689689

690+
if (cds && cds->rri && operation == CMD_INSERT && cds->skip_insert)
691+
{
692+
cds->skip_insert = false;
693+
if (node->ps.instrument)
694+
node->ps.instrument->ntuples2++;
695+
return NULL;
696+
}
697+
690698
/* No more tuples to process? */
691699
if (TupIsNull(context.planSlot))
692700
break;

tsl/src/compression/compression_dml.c

Lines changed: 95 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,25 @@
2929
#include <nodes/hypertable_modify.h>
3030
#include <ts_catalog/array_utils.h>
3131

32-
static struct decompress_batches_stats
33-
decompress_batches_indexscan(Relation in_rel, Relation out_rel, Relation index_rel,
34-
Snapshot snapshot, ScanKeyData *index_scankeys, int num_index_scankeys,
35-
ScanKeyData *heap_scankeys, int num_heap_scankeys,
36-
ScanKeyData *mem_scankeys, int num_mem_scankeys,
37-
Bitmapset *null_columns, List *is_nulls);
32+
static struct decompress_batches_stats decompress_batches_indexscan(
33+
Relation in_rel, Relation out_rel, Relation index_rel, Snapshot snapshot,
34+
ScanKeyData *index_scankeys, int num_index_scankeys, ScanKeyData *heap_scankeys,
35+
int num_heap_scankeys, ScanKeyData *mem_scankeys, int num_mem_scankeys,
36+
OnConflictAction on_conflict, bool *skip_insert, Bitmapset *null_columns, List *is_nulls);
3837
static struct decompress_batches_stats
3938
decompress_batches_seqscan(Relation in_rel, Relation out_rel, Snapshot snapshot,
4039
ScanKeyData *scankeys, int num_scankeys, ScanKeyData *mem_scankeys,
41-
int num_mem_scankeys, Bitmapset *null_columns, List *is_nulls);
40+
int num_mem_scankeys, OnConflictAction on_conflict, bool *skip_insert,
41+
Bitmapset *null_columns, List *is_nulls);
4242

43-
static bool batch_matches(RowDecompressor *decompressor, ScanKeyData *scankeys, int num_scankeys);
43+
static bool batch_matches(RowDecompressor *decompressor, ScanKeyData *scankeys, int num_scankeys,
44+
OnConflictAction on_conflict, bool *skip_insert);
4445
static void process_predicates(Chunk *ch, CompressionSettings *settings, List *predicates,
4546
ScanKeyData **mem_scankeys, int *num_mem_scankeys,
4647
List **heap_filters, List **index_filters, List **is_null);
4748
static Relation find_matching_index(Relation comp_chunk_rel, List **index_filters,
4849
List **heap_filters);
49-
static Bitmapset *compressed_insert_key_columns(Relation relation);
50+
static Bitmapset *compressed_insert_key_columns(Relation relation, bool *covering);
5051
static BatchFilter *make_batchfilter(char *column_name, StrategyNumber strategy, Oid collation,
5152
RegProcedure opcode, Const *value, bool is_null_check,
5253
bool is_null, bool is_array_op);
@@ -84,7 +85,20 @@ decompress_batches_for_insert(const ChunkInsertState *cis, TupleTableSlot *slot)
8485
CompressionSettings *settings = ts_compression_settings_get(cis->compressed_chunk_table_id);
8586
Assert(settings);
8687

87-
Bitmapset *key_columns = compressed_insert_key_columns(out_rel);
88+
bool covering;
89+
Bitmapset *key_columns = compressed_insert_key_columns(out_rel, &covering);
90+
OnConflictAction on_conflict = ONCONFLICT_UPDATE;
91+
/*
92+
* When no on conflict clause is specified and the index is covering, we can
93+
* error out before decompressing anything.
94+
* For ON CONFLICT DO NOTHING with covering index we can skip decompression
95+
* and abort the insert when we find a matching tuple.
96+
* For ON CONFLICT DO UPDATE we need to decompress the tuple on match.
97+
*/
98+
if (covering && cis->cds->dispatch)
99+
{
100+
on_conflict = chunk_dispatch_get_on_conflict_action(cis->cds->dispatch);
101+
}
88102
Bitmapset *index_columns = NULL;
89103
Bitmapset *null_columns = NULL;
90104
struct decompress_batches_stats stats;
@@ -113,6 +127,7 @@ decompress_batches_for_insert(const ChunkInsertState *cis, TupleTableSlot *slot)
113127
&index_columns,
114128
&num_index_scankeys);
115129

130+
bool skip_insert = false;
116131
if (index_rel)
117132
{
118133
/*
@@ -147,6 +162,8 @@ decompress_batches_for_insert(const ChunkInsertState *cis, TupleTableSlot *slot)
147162
num_heap_scankeys,
148163
mem_scankeys,
149164
num_mem_scankeys,
165+
on_conflict,
166+
&skip_insert,
150167
NULL, /* no null column check for non-segmentby
151168
columns */
152169
NIL);
@@ -171,12 +188,19 @@ decompress_batches_for_insert(const ChunkInsertState *cis, TupleTableSlot *slot)
171188
num_heap_scankeys,
172189
mem_scankeys,
173190
num_mem_scankeys,
191+
on_conflict,
192+
&skip_insert,
174193
null_columns,
175194
NIL);
176195
bms_free(key_columns);
177196
}
178197

179198
Assert(cis->cds != NULL);
199+
if (skip_insert)
200+
{
201+
cis->cds->skip_insert = true;
202+
}
203+
180204
cis->cds->batches_decompressed += stats.batches_decompressed;
181205
cis->cds->tuples_decompressed += stats.tuples_decompressed;
182206

@@ -260,6 +284,8 @@ decompress_batches_for_update_delete(HypertableModifyState *ht_state, Chunk *chu
260284
num_scankeys,
261285
mem_scankeys,
262286
num_mem_scankeys,
287+
ONCONFLICT_UPDATE,
288+
NULL,
263289
null_columns,
264290
is_null);
265291
/* close the selected index */
@@ -274,6 +300,8 @@ decompress_batches_for_update_delete(HypertableModifyState *ht_state, Chunk *chu
274300
num_scankeys,
275301
mem_scankeys,
276302
num_mem_scankeys,
303+
ONCONFLICT_UPDATE,
304+
NULL,
277305
null_columns,
278306
is_null);
279307
}
@@ -318,6 +346,7 @@ decompress_batches_indexscan(Relation in_rel, Relation out_rel, Relation index_r
318346
Snapshot snapshot, ScanKeyData *index_scankeys, int num_index_scankeys,
319347
ScanKeyData *heap_scankeys, int num_heap_scankeys,
320348
ScanKeyData *mem_scankeys, int num_mem_scankeys,
349+
OnConflictAction on_conflict, bool *skip_insert,
321350
Bitmapset *null_columns, List *is_nulls)
322351
{
323352
HeapTuple compressed_tuple;
@@ -404,12 +433,22 @@ decompress_batches_indexscan(Relation in_rel, Relation out_rel, Relation index_r
404433
decompressor.compressed_datums,
405434
decompressor.compressed_is_nulls);
406435

407-
if (num_mem_scankeys && !batch_matches(&decompressor, mem_scankeys, num_mem_scankeys))
436+
if (num_mem_scankeys &&
437+
!batch_matches(&decompressor, mem_scankeys, num_mem_scankeys, on_conflict, skip_insert))
408438
{
409439
row_decompressor_reset(&decompressor);
410440
continue;
411441
}
412442

443+
if (skip_insert && *skip_insert)
444+
{
445+
row_decompressor_close(&decompressor);
446+
index_endscan(scan);
447+
index_close(index_rel, AccessShareLock);
448+
ExecDropSingleTupleTableSlot(slot);
449+
return stats;
450+
}
451+
413452
write_logical_replication_msg_decompression_start();
414453
result = delete_compressed_tuple(&decompressor, snapshot, compressed_tuple);
415454
/* skip reporting error if isolation level is < Repeatable Read
@@ -470,7 +509,8 @@ decompress_batches_indexscan(Relation in_rel, Relation out_rel, Relation index_r
470509
static struct decompress_batches_stats
471510
decompress_batches_seqscan(Relation in_rel, Relation out_rel, Snapshot snapshot,
472511
ScanKeyData *scankeys, int num_scankeys, ScanKeyData *mem_scankeys,
473-
int num_mem_scankeys, Bitmapset *null_columns, List *is_nulls)
512+
int num_mem_scankeys, OnConflictAction on_conflict, bool *skip_insert,
513+
Bitmapset *null_columns, List *is_nulls)
474514
{
475515
RowDecompressor decompressor;
476516
bool decompressor_initialized = false;
@@ -534,12 +574,21 @@ decompress_batches_seqscan(Relation in_rel, Relation out_rel, Snapshot snapshot,
534574
decompressor.compressed_datums,
535575
decompressor.compressed_is_nulls);
536576

537-
if (num_mem_scankeys && !batch_matches(&decompressor, mem_scankeys, num_mem_scankeys))
577+
if (num_mem_scankeys &&
578+
!batch_matches(&decompressor, mem_scankeys, num_mem_scankeys, on_conflict, skip_insert))
538579
{
539580
row_decompressor_reset(&decompressor);
540581
continue;
541582
}
542583

584+
if (skip_insert && *skip_insert)
585+
{
586+
row_decompressor_close(&decompressor);
587+
ExecDropSingleTupleTableSlot(slot);
588+
table_endscan(scan);
589+
return stats;
590+
}
591+
543592
write_logical_replication_msg_decompression_start();
544593
result = delete_compressed_tuple(&decompressor, snapshot, compressed_tuple);
545594
/* skip reporting error if isolation level is < Repeatable Read
@@ -582,7 +631,8 @@ decompress_batches_seqscan(Relation in_rel, Relation out_rel, Snapshot snapshot,
582631
}
583632

584633
static bool
585-
batch_matches(RowDecompressor *decompressor, ScanKeyData *scankeys, int num_scankeys)
634+
batch_matches(RowDecompressor *decompressor, ScanKeyData *scankeys, int num_scankeys,
635+
OnConflictAction on_conflict, bool *skip_insert)
586636
{
587637
int num_tuples = decompress_batch(decompressor);
588638

@@ -599,6 +649,16 @@ batch_matches(RowDecompressor *decompressor, ScanKeyData *scankeys, int num_scan
599649
#endif
600650
if (valid)
601651
{
652+
if (on_conflict == ONCONFLICT_NONE)
653+
{
654+
ereport(ERROR,
655+
(errcode(ERRCODE_UNIQUE_VIOLATION),
656+
errmsg("duplicate key value violates unique constraint")));
657+
}
658+
if (on_conflict == ONCONFLICT_NOTHING)
659+
{
660+
*skip_insert = true;
661+
}
602662
return true;
603663
}
604664
}
@@ -743,9 +803,12 @@ decompress_chunk_walker(PlanState *ps, struct decompress_chunk_context *ctx)
743803
* In case of multiple unique indexes we have to return the shared columns.
744804
* For expression indexes we ignore the columns with expressions, for partial
745805
* indexes we ignore predicate.
806+
*
807+
* The covering flag is set to true if we have a single constraint that is covered
808+
* by all the columns present in the Bitmapset.
746809
*/
747810
static Bitmapset *
748-
compressed_insert_key_columns(Relation relation)
811+
compressed_insert_key_columns(Relation relation, bool *covering)
749812
{
750813
Bitmapset *shared_attrs = NULL; /* indexed columns */
751814
ListCell *l;
@@ -792,8 +855,24 @@ compressed_insert_key_columns(Relation relation)
792855
}
793856
index_close(indexDesc, AccessShareLock);
794857

795-
shared_attrs = shared_attrs ? bms_intersect(idx_attrs, shared_attrs) : idx_attrs;
858+
if (!shared_attrs)
859+
{
860+
/* First iteration */
861+
shared_attrs = idx_attrs;
862+
/* We only optimize unique constraint checks for non-partial indexes. */
863+
*covering = indexDesc->rd_indpred == NIL;
864+
}
865+
else
866+
{
867+
shared_attrs = bms_intersect(idx_attrs, shared_attrs);
868+
*covering = true;
869+
}
796870

871+
/* When multiple unique indexes are present, in theory there could be no shared
872+
* columns even though that is very unlikely as they will probably at least share
873+
* the partitioning columns. But since we are looking at chunk indexes here that
874+
* is not guaranteed.
875+
*/
797876
if (!shared_attrs)
798877
return NULL;
799878
}

0 commit comments

Comments
 (0)