Skip to content

Commit 2d64725

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 24b7c83 commit 2d64725

25 files changed

Lines changed: 1693 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/reverse-dev.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,4 @@ DROP FUNCTION IF EXISTS @extschema@.create_hypertable(relation REGCLASS, time_co
188188
-- Restore the chunk_target_size check constraint dropped in the forward path.
189189
ALTER TABLE _timescaledb_catalog.hypertable
190190
ADD CONSTRAINT hypertable_chunk_target_size_check CHECK (chunk_target_size >= 0);
191+
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: 47 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
@@ -90,12 +88,56 @@ sparse_index_values_equal(List *left, List *right)
9088
return true;
9189
}
9290

91+
bool
92+
ts_sparse_index_object_get_type_and_columns(SparseIndexSettingsObject *obj, const char **type_out,
93+
List **columns_out)
94+
{
95+
const char *type = NULL;
96+
List *columns = NIL;
97+
98+
foreach_ptr(SparseIndexSettingsPair, pair, obj->pairs)
99+
{
100+
if (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeyType]) == 0)
101+
{
102+
Assert(list_length(pair->values) == 1);
103+
type = (const char *) lfirst(list_head(pair->values));
104+
}
105+
else if (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeyCol]) == 0)
106+
{
107+
columns = pair->values;
108+
}
109+
}
110+
111+
if (!type || !columns)
112+
{
113+
return false;
114+
}
115+
116+
*type_out = type;
117+
*columns_out = columns;
118+
return true;
119+
}
120+
121+
bool
122+
ts_sparse_index_is_orderby_source(SparseIndexSettingsObject *obj)
123+
{
124+
foreach_ptr(SparseIndexSettingsPair, pair, obj->pairs)
125+
{
126+
if (strcmp(pair->key, ts_sparse_index_common_keys[SparseIndexKeySource]) == 0)
127+
{
128+
const char *source = (const char *) lfirst(list_head(pair->values));
129+
return strcmp(source, ts_sparse_index_source_names[_SparseIndexSourceEnumOrderby]) == 0;
130+
}
131+
}
132+
return false;
133+
}
134+
93135
/*
94136
* Compare two sparse index objects for equality.
95137
* Two objects are equal if they have the same pairs with the same values.
96138
*/
97-
static bool
98-
sparse_index_object_equal(SparseIndexSettingsObject *left, SparseIndexSettingsObject *right)
139+
bool
140+
ts_sparse_index_object_equal(SparseIndexSettingsObject *left, SparseIndexSettingsObject *right)
99141
{
100142
if (list_length(left->pairs) != list_length(right->pairs))
101143
{
@@ -165,7 +207,7 @@ ts_sparse_index_equal(const Jsonb *left, const Jsonb *right)
165207
bool found = false;
166208
foreach_ptr(SparseIndexSettingsObject, robj, right_settings->objects)
167209
{
168-
if (!already_found[ri] && sparse_index_object_equal(lobj, robj))
210+
if (!already_found[ri] && ts_sparse_index_object_equal(lobj, robj))
169211
{
170212
already_found[ri] = true;
171213
found = true;

src/ts_catalog/compression_settings.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ TSDLLEXPORT bool ts_compression_settings_equal(const CompressionSettings *left,
154154
TSDLLEXPORT bool ts_compression_settings_equal_with_defaults(const CompressionSettings *ht,
155155
const CompressionSettings *chunk);
156156
TSDLLEXPORT bool ts_sparse_index_equal(const Jsonb *left, const Jsonb *right);
157+
TSDLLEXPORT bool ts_sparse_index_object_equal(SparseIndexSettingsObject *left,
158+
SparseIndexSettingsObject *right);
159+
TSDLLEXPORT bool ts_sparse_index_object_get_type_and_columns(SparseIndexSettingsObject *obj,
160+
const char **type_out,
161+
List **columns_out);
162+
TSDLLEXPORT bool ts_sparse_index_is_orderby_source(SparseIndexSettingsObject *obj);
157163

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

tsl/src/compression/api.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,74 @@ 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+
ts_hypertable_permissions_check(chunk->hypertable_relid, GetUserId());
1091+
1092+
if (!ts_chunk_is_compressed(chunk) || ts_chunk_is_frozen(chunk))
1093+
{
1094+
ereport(NOTICE,
1095+
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1096+
errmsg("chunk \"%s.%s\" is uncompressed or frozen, skipping",
1097+
NameStr(chunk->fd.schema_name),
1098+
NameStr(chunk->fd.table_name))));
1099+
PG_RETURN_VOID();
1100+
}
1101+
1102+
CompressionSettings *chunk_settings = ts_compression_settings_get(chunk->table_id);
1103+
CompressionSettings *ht_settings = ts_compression_settings_get(chunk->hypertable_relid);
1104+
1105+
if (ht_settings->fd.index == NULL)
1106+
{
1107+
ereport(NOTICE,
1108+
(errmsg("no sparse index configured on hypertable \"%s\" for chunk \"%s.%s\", "
1109+
"skipping",
1110+
get_rel_name(chunk->hypertable_relid),
1111+
NameStr(chunk->fd.schema_name),
1112+
NameStr(chunk->fd.table_name))));
1113+
PG_RETURN_VOID();
1114+
}
1115+
1116+
/* Orderby changes require recompression since batch data is physically sorted by orderby */
1117+
if (!ts_array_equal(chunk_settings->fd.orderby, ht_settings->fd.orderby))
1118+
{
1119+
ereport(NOTICE,
1120+
(errmsg("orderby settings for chunk \"%s.%s\" differ from hypertable \"%s\"",
1121+
NameStr(chunk->fd.schema_name),
1122+
NameStr(chunk->fd.table_name),
1123+
get_rel_name(chunk->hypertable_relid)),
1124+
errhint("Use compress_chunk(chunk, recompress => true) to recompress.")));
1125+
PG_RETURN_VOID();
1126+
}
1127+
1128+
if (!force && ts_sparse_index_equal(chunk_settings->fd.index, ht_settings->fd.index))
1129+
{
1130+
ereport(NOTICE,
1131+
(errmsg("sparse index settings for chunk \"%s.%s\" already match hypertable, "
1132+
"skipping (use force => true to override)",
1133+
NameStr(chunk->fd.schema_name),
1134+
NameStr(chunk->fd.table_name))));
1135+
PG_RETURN_VOID();
1136+
}
1137+
1138+
rebuild_sparse_index_impl(chunk, force);
1139+
PG_RETURN_VOID();
1140+
}
1141+
10741142
/*
10751143
* This is hacky but it doesn't matter. We just want to check for the existence of such an index
10761144
* 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

tsl/src/compression/batch_metadata_builder.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
#include <postgres.h>
99
#include "funcapi.h" /* for PGFunction, FmgrInfo */
1010

11-
typedef struct RowCompressor RowCompressor;
12-
1311
enum BatchMetadataBuilderType
1412
{
1513
METADATA_BUILDER_MINMAX,
@@ -20,8 +18,9 @@ enum BatchMetadataBuilderType
2018
typedef struct BatchMetadataBuilder
2119
{
2220
void (*update_row)(void *builder, TupleTableSlot *slot);
23-
void (*insert_to_compressed_row)(void *builder, RowCompressor *compressor);
24-
void (*reset)(void *builder, RowCompressor *compressor);
21+
void (*insert_to_compressed_row)(void *builder, Datum *compressed_values,
22+
bool *compressed_is_null);
23+
void (*reset)(void *builder, Datum *compressed_values, bool *compressed_is_null);
2524
enum BatchMetadataBuilderType builder_type;
2625
} BatchMetadataBuilder;
2726

@@ -54,7 +53,8 @@ uint64 batch_metadata_builder_bloom1_calculate_hash(PGFunction hash_function, Fm
5453
void batch_metadata_builder_bloom1_update_bloom_filter_with_hash(void *varlena_ptr, uint64 hash);
5554
void batch_metadata_builder_bloom1_insert_bloom_filter_to_compressed_row(void *bloom_varlena,
5655
int16 bloom_attr_offset,
57-
RowCompressor *compressor);
56+
Datum *compressed_values,
57+
bool *compressed_is_null);
5858

5959
/* Returns true if the hash is maybe present in a bloom filter, if the bloom filter data is
6060
* NULL, it returns true, because we cannot be sure if the hash is present or not. */

0 commit comments

Comments
 (0)