Skip to content

Commit 0b53807

Browse files
Add rebuild_sparse_index function
Rebuild sparse index metadata columns on compressed chunks in-place without full recompression. Adds _timescaledb_functions.rebuild_sparse_index(chunk, force) which diffs chunk vs hypertable sparse index settings, drops/adds metadata columns, then populates them by decompressing each batch through the existing RowDecompressor and BatchMetadataBuilder pipeline.
1 parent 468390c commit 0b53807

25 files changed

Lines changed: 1673 additions & 57 deletions

.unreleased/pr_9938

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implements: #9938 Add rebuild_sparse_index function

sql/maintenance_utils.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ CREATE OR REPLACE PROCEDURE _timescaledb_functions.rebuild_columnstore(
5959
chunk REGCLASS
6060
) AS '@MODULE_PATHNAME@', 'ts_rebuild_columnstore' LANGUAGE C;
6161

62+
CREATE OR REPLACE FUNCTION _timescaledb_functions.rebuild_sparse_index(
63+
chunk REGCLASS,
64+
force BOOLEAN = false
65+
) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_rebuild_sparse_index' LANGUAGE C VOLATILE;
66+
6267
CREATE OR REPLACE PROCEDURE _timescaledb_functions.chunk_rewrite_cleanup()
6368
LANGUAGE C AS '@MODULE_PATHNAME@', 'ts_chunk_rewrite_cleanup';
6469

sql/updates/latest-dev.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ WHERE dimension_slice_id IS NULL
4949

5050
ALTER TABLE _timescaledb_catalog.hypertable SET (user_catalog_table = true);
5151
ALTER TABLE _timescaledb_catalog.chunk SET (user_catalog_table = true);
52+
CREATE OR REPLACE FUNCTION _timescaledb_functions.rebuild_sparse_index(
53+
chunk REGCLASS,
54+
force BOOLEAN = false
55+
) RETURNS VOID AS '@MODULE_PATHNAME@', 'ts_update_placeholder' LANGUAGE C VOLATILE;
5256

5357
-- Add chunk_id to `_timescaledb_catalog.dimension_slice`
5458
CREATE TABLE _timescaledb_internal.tmp_dimension_slice AS

sql/updates/reverse-dev.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,4 @@ GRANT SELECT ON _timescaledb_catalog.dimension_slice TO PUBLIC;
184184
GRANT SELECT ON _timescaledb_catalog.dimension_slice_id_seq TO PUBLIC;
185185
-- end rebuild _timescaledb_catalog.dimension_slice table --
186186

187+
DROP FUNCTION IF EXISTS _timescaledb_functions.rebuild_sparse_index(REGCLASS, BOOLEAN);

src/cross_module_fn.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ CROSSMODULE_WRAPPER(create_compressed_chunk);
8282
CROSSMODULE_WRAPPER(compress_chunk);
8383
CROSSMODULE_WRAPPER(decompress_chunk);
8484
CROSSMODULE_WRAPPER(rebuild_columnstore);
85+
CROSSMODULE_WRAPPER(rebuild_sparse_index);
8586
CROSSMODULE_WRAPPER(bloom1_contains);
8687
CROSSMODULE_WRAPPER(bloom1_contains_any);
8788
CROSSMODULE_WRAPPER(bloom1_contains_any_hashes);
@@ -353,6 +354,7 @@ TSDLLEXPORT CrossModuleFunctions ts_cm_functions_default = {
353354
.compress_chunk = error_no_default_fn_pg_community,
354355
.decompress_chunk = error_no_default_fn_pg_community,
355356
.rebuild_columnstore = error_no_default_fn_pg_community,
357+
.rebuild_sparse_index = error_no_default_fn_pg_community,
356358
.compressed_data_decompress_forward = error_no_default_fn_pg_community,
357359
.compressed_data_decompress_reverse = error_no_default_fn_pg_community,
358360
.compressed_data_column_size = error_no_default_fn_pg_community,

src/cross_module_fn.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ typedef struct CrossModuleFunctions
124124
PGFunction compress_chunk;
125125
PGFunction decompress_chunk;
126126
PGFunction rebuild_columnstore;
127+
PGFunction rebuild_sparse_index;
127128
void (*decompress_batches_for_insert)(ChunkInsertState *state, TupleTableSlot *slot);
128129
void (*init_decompress_state_for_insert)(ChunkInsertState *state, TupleTableSlot *slot);
129130
bool (*decompress_target_segments)(ModifyHypertableState *ht_state);

src/ts_catalog/compression_settings.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ static HeapTuple compression_settings_formdata_make_tuple(const FormData_compres
2828
TupleDesc desc);
2929
static Bitmapset *resolve_columns_to_attnos(List *column_names, Oid relid);
3030
static bool sparse_index_values_equal(List *left, List *right);
31-
static bool sparse_index_object_equal(SparseIndexSettingsObject *left,
32-
SparseIndexSettingsObject *right);
3331

3432
/*
3533
* Compare two compression settings for equality
@@ -94,8 +92,22 @@ sparse_index_values_equal(List *left, List *right)
9492
* Compare two sparse index objects for equality.
9593
* Two objects are equal if they have the same pairs with the same values.
9694
*/
97-
static bool
98-
sparse_index_object_equal(SparseIndexSettingsObject *left, SparseIndexSettingsObject *right)
95+
bool
96+
ts_sparse_index_is_orderby_source(SparseIndexSettingsObject *obj)
97+
{
98+
foreach_ptr(SparseIndexSettingsPair, pair, obj->pairs)
99+
{
100+
if (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeySource]) == 0)
101+
{
102+
const char *source = (const char *) lfirst(list_head(pair->values));
103+
return strcmp(source, ts_sparse_index_source_names[_SparseIndexSourceEnumOrderby]) == 0;
104+
}
105+
}
106+
return false;
107+
}
108+
109+
bool
110+
ts_sparse_index_object_equal(SparseIndexSettingsObject *left, SparseIndexSettingsObject *right)
99111
{
100112
if (list_length(left->pairs) != list_length(right->pairs))
101113
{
@@ -165,7 +177,7 @@ ts_sparse_index_equal(const Jsonb *left, const Jsonb *right)
165177
bool found = false;
166178
foreach_ptr(SparseIndexSettingsObject, robj, right_settings->objects)
167179
{
168-
if (!already_found[ri] && sparse_index_object_equal(lobj, robj))
180+
if (!already_found[ri] && ts_sparse_index_object_equal(lobj, robj))
169181
{
170182
already_found[ri] = true;
171183
found = true;

src/ts_catalog/compression_settings.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ TSDLLEXPORT bool ts_compression_settings_equal(const CompressionSettings *left,
152152
TSDLLEXPORT bool ts_compression_settings_equal_with_defaults(const CompressionSettings *ht,
153153
const CompressionSettings *chunk);
154154
TSDLLEXPORT bool ts_sparse_index_equal(const Jsonb *left, const Jsonb *right);
155+
TSDLLEXPORT bool ts_sparse_index_object_equal(SparseIndexSettingsObject *left,
156+
SparseIndexSettingsObject *right);
157+
TSDLLEXPORT bool ts_sparse_index_is_orderby_source(SparseIndexSettingsObject *obj);
155158

156159
TSDLLEXPORT int ts_compression_settings_update(CompressionSettings *settings);
157160
TSDLLEXPORT void ts_compression_settings_rename_column_cascade(Oid parent_relid, const char *old,

tsl/src/compression/api.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,73 @@ tsl_rebuild_columnstore(PG_FUNCTION_ARGS)
10711071
PG_RETURN_VOID();
10721072
}
10731073

1074+
Datum
1075+
tsl_rebuild_sparse_index(PG_FUNCTION_ARGS)
1076+
{
1077+
Oid chunk_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
1078+
bool force = PG_ARGISNULL(1) ? false : PG_GETARG_BOOL(1);
1079+
1080+
ts_feature_flag_check(FEATURE_HYPERTABLE_COMPRESSION);
1081+
1082+
TS_PREVENT_FUNC_IF_READ_ONLY();
1083+
1084+
if (!OidIsValid(chunk_relid))
1085+
{
1086+
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid chunk OID")));
1087+
}
1088+
1089+
Chunk *chunk = ts_chunk_get_by_relid(chunk_relid, true);
1090+
1091+
if (!ts_chunk_is_compressed(chunk) || ts_chunk_is_frozen(chunk))
1092+
{
1093+
ereport(NOTICE,
1094+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1095+
errmsg("chunk \"%s.%s\" is uncompressed or frozen, skipping",
1096+
NameStr(chunk->fd.schema_name),
1097+
NameStr(chunk->fd.table_name))));
1098+
PG_RETURN_VOID();
1099+
}
1100+
1101+
CompressionSettings *chunk_settings = ts_compression_settings_get(chunk->table_id);
1102+
CompressionSettings *ht_settings = ts_compression_settings_get(chunk->hypertable_relid);
1103+
1104+
if (ht_settings->fd.index == NULL)
1105+
{
1106+
ereport(NOTICE,
1107+
(errmsg("no sparse index configured on hypertable \"%s\" for chunk \"%s.%s\", "
1108+
"skipping",
1109+
get_rel_name(chunk->hypertable_relid),
1110+
NameStr(chunk->fd.schema_name),
1111+
NameStr(chunk->fd.table_name))));
1112+
PG_RETURN_VOID();
1113+
}
1114+
1115+
/* Orderby changes require recompression since batch data is physically sorted by orderby */
1116+
if (!ts_array_equal(chunk_settings->fd.orderby, ht_settings->fd.orderby))
1117+
{
1118+
ereport(NOTICE,
1119+
(errmsg("orderby settings for chunk \"%s.%s\" differ from hypertable \"%s\"",
1120+
NameStr(chunk->fd.schema_name),
1121+
NameStr(chunk->fd.table_name),
1122+
get_rel_name(chunk->hypertable_relid)),
1123+
errhint("Use compress_chunk(chunk, recompress => true) to recompress.")));
1124+
PG_RETURN_VOID();
1125+
}
1126+
1127+
if (!force && ts_sparse_index_equal(chunk_settings->fd.index, ht_settings->fd.index))
1128+
{
1129+
ereport(NOTICE,
1130+
(errmsg("sparse index settings for chunk \"%s.%s\" already match hypertable, "
1131+
"skipping (use force => true to override)",
1132+
NameStr(chunk->fd.schema_name),
1133+
NameStr(chunk->fd.table_name))));
1134+
PG_RETURN_VOID();
1135+
}
1136+
1137+
rebuild_sparse_index_impl(chunk, force);
1138+
PG_RETURN_VOID();
1139+
}
1140+
10741141
/*
10751142
* This is hacky but it doesn't matter. We just want to check for the existence of such an index
10761143
* on the compressed chunk.

tsl/src/compression/api.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extern Datum tsl_create_compressed_chunk(PG_FUNCTION_ARGS);
1616
extern Datum tsl_compress_chunk(PG_FUNCTION_ARGS);
1717
extern Datum tsl_decompress_chunk(PG_FUNCTION_ARGS);
1818
extern Datum tsl_rebuild_columnstore(PG_FUNCTION_ARGS);
19+
extern Datum tsl_rebuild_sparse_index(PG_FUNCTION_ARGS);
1920
extern Oid tsl_compress_chunk_wrapper(Chunk *chunk, bool if_not_compressed, bool recompress);
2021
extern Chunk *tsl_compression_chunk_create(Hypertable *compressed_ht, Chunk *src_chunk);
2122

0 commit comments

Comments
 (0)