diff --git a/sql/ddl_api.sql b/sql/ddl_api.sql index 59860586d..4587932c6 100644 --- a/sql/ddl_api.sql +++ b/sql/ddl_api.sql @@ -173,7 +173,8 @@ AS '@MODULE_PATHNAME@', 'ts_data_node_add' LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION delete_data_node( node_name NAME, if_exists BOOLEAN = FALSE, - force BOOLEAN = FALSE + force BOOLEAN = FALSE, + repartition BOOLEAN = TRUE ) RETURNS BOOLEAN AS '@MODULE_PATHNAME@', 'ts_data_node_delete' LANGUAGE C VOLATILE; -- Attach a data node to a distributed hypertable @@ -186,7 +187,12 @@ CREATE OR REPLACE FUNCTION attach_data_node( AS '@MODULE_PATHNAME@', 'ts_data_node_attach' LANGUAGE C VOLATILE; -- Detach a data node from a distributed hypertable. NULL hypertable means it will detach from all distributed hypertables -CREATE OR REPLACE FUNCTION detach_data_node(node_name NAME, hypertable REGCLASS = NULL, force BOOLEAN = FALSE) RETURNS INTEGER +CREATE OR REPLACE FUNCTION detach_data_node( + node_name NAME, + hypertable REGCLASS = NULL, + force BOOLEAN = FALSE, + repartition BOOLEAN = TRUE +) RETURNS INTEGER AS '@MODULE_PATHNAME@', 'ts_data_node_detach' LANGUAGE C VOLATILE; -- Block new chunk creation on a data node for a distributed hypertable. NULL hypertable means it will block diff --git a/tsl/src/data_node.c b/tsl/src/data_node.c index cbef7c044..1b29f287a 100644 --- a/tsl/src/data_node.c +++ b/tsl/src/data_node.c @@ -809,18 +809,18 @@ data_node_attach(PG_FUNCTION_ARGS) num_nodes = list_length(ht->data_nodes) + 1; + if (num_nodes > MAX_NUM_HYPERTABLE_DATA_NODES) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("max number of data nodes already attached"), + errdetail("The number of data nodes in a hypertable cannot exceed %d", + MAX_NUM_HYPERTABLE_DATA_NODES))); + /* If there are less slices (partitions) in the space dimension than there - * are data nodesx, we'd like to expand the number of slices to be able to + * are data nodes, we'd like to expand the number of slices to be able to * make use of the new data node. */ if (NULL != dim && num_nodes > dim->fd.num_slices) { - if (num_nodes > MAX_NUM_HYPERTABLE_DATA_NODES) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("max number of data nodes already attached"), - errdetail("The number of data nodes in a hypertable cannot exceed %d", - MAX_NUM_HYPERTABLE_DATA_NODES))); - if (repartition) { ts_dimension_set_number_of_slices(dim, num_nodes & 0xFFFF); @@ -975,7 +975,7 @@ data_node_detach_validate(const char *node_name, Hypertable *ht, bool force, Ope static int data_node_modify_hypertable_data_nodes(const char *node_name, List *hypertable_data_nodes, bool all_hypertables, OperationType op_type, - bool block_chunks, bool force) + bool block_chunks, bool force, bool repartition) { Cache *hcache = ts_hypertable_cache_pin(); ListCell *lc; @@ -1028,6 +1028,26 @@ data_node_modify_hypertable_data_nodes(const char *node_name, List *hypertable_d /* delete hypertable mapping */ removed += ts_hypertable_data_node_delete_by_node_name_and_hypertable_id(node_name, ht->fd.id); + + if (repartition) + { + Dimension *dim = hyperspace_get_closed_dimension(ht->space, 0); + int num_nodes = list_length(ht->data_nodes) - 1; + + if (dim != NULL && num_nodes < dim->fd.num_slices && num_nodes > 0) + { + ts_dimension_set_number_of_slices(dim, num_nodes & 0xFFFF); + + ereport(NOTICE, + (errmsg("the number of partitions in dimension \"%s\" was decreased to " + "%u", + NameStr(dim->fd.column_name), + num_nodes), + errdetail( + "To make efficient use of all attached data nodes, the number of " + "space partitions was set to match the number of data nodes."))); + } + } } else { @@ -1065,19 +1085,22 @@ data_node_block_hypertable_data_nodes(const char *node_name, List *hypertable_da all_hypertables, OP_BLOCK, block_chunks, - force); + force, + false); } static int data_node_detach_hypertable_data_nodes(const char *node_name, List *hypertable_data_nodes, - bool all_hypertables, bool force, OperationType op_type) + bool all_hypertables, bool force, bool repartition, + OperationType op_type) { return data_node_modify_hypertable_data_nodes(node_name, hypertable_data_nodes, all_hypertables, op_type, false, - force); + force, + repartition); } static HypertableDataNode * @@ -1171,6 +1194,7 @@ data_node_detach(PG_FUNCTION_ARGS) Oid table_id = PG_ARGISNULL(1) ? InvalidOid : PG_GETARG_OID(1); bool all_hypertables = PG_ARGISNULL(1); bool force = PG_ARGISNULL(2) ? InvalidOid : PG_GETARG_OID(2); + bool repartition = PG_ARGISNULL(3) ? false : PG_GETARG_BOOL(3); int removed = 0; List *hypertable_data_nodes = NIL; ForeignServer *server = data_node_get_foreign_server(node_name, ACL_USAGE, true, false); @@ -1197,6 +1221,7 @@ data_node_detach(PG_FUNCTION_ARGS) hypertable_data_nodes, all_hypertables, force, + repartition, OP_DETACH); PG_RETURN_INT32(removed); @@ -1208,6 +1233,7 @@ data_node_delete(PG_FUNCTION_ARGS) const char *node_name = PG_ARGISNULL(0) ? NULL : PG_GETARG_CSTRING(0); bool if_exists = PG_ARGISNULL(1) ? false : PG_GETARG_BOOL(1); bool force = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2); + bool repartition = PG_ARGISNULL(3) ? false : PG_GETARG_BOOL(3); List *hypertable_data_nodes = NIL; DropStmt stmt; ObjectAddress address; @@ -1247,6 +1273,7 @@ data_node_delete(PG_FUNCTION_ARGS) hypertable_data_nodes, true, force, + repartition, OP_DELETE); stmt = (DropStmt){ diff --git a/tsl/test/expected/chunk_api.out b/tsl/test/expected/chunk_api.out index 0dc6b2c64..374778951 100644 --- a/tsl/test/expected/chunk_api.out +++ b/tsl/test/expected/chunk_api.out @@ -458,6 +458,7 @@ RESET ROLE; TRUNCATE disttable; TRUNCATE costtable; SELECT * FROM delete_data_node('data_node_1', force => true); +NOTICE: the number of partitions in dimension "device" was decreased to 1 delete_data_node ------------------ t diff --git a/tsl/test/expected/data_node.out b/tsl/test/expected/data_node.out index 39f526d92..b7d26526f 100644 --- a/tsl/test/expected/data_node.out +++ b/tsl/test/expected/data_node.out @@ -1016,6 +1016,7 @@ ERROR: table "devices" is not a hypertable -- force detach data node to become under-replicated for new data SELECT * FROM detach_data_node('data_node_3', 'disttable_2', force => true); WARNING: new data for hypertable "disttable_2" will be under-replicated due to detaching data node "data_node_3" +NOTICE: the number of partitions in dimension "device" was decreased to 1 detach_data_node ------------------ 1 @@ -1108,6 +1109,7 @@ ORDER BY foreign_table_name; SELECT * FROM detach_data_node('data_node_2', 'disttable', true); WARNING: new data for hypertable "disttable" will be under-replicated due to detaching data node "data_node_2" +NOTICE: the number of partitions in dimension "device" was decreased to 1 detach_data_node ------------------ 1 diff --git a/tsl/test/expected/dist_hypertable-11.out b/tsl/test/expected/dist_hypertable-11.out index b34e84837..9bf9408c4 100644 --- a/tsl/test/expected/dist_hypertable-11.out +++ b/tsl/test/expected/dist_hypertable-11.out @@ -1909,6 +1909,41 @@ ts_insert_blocker| 7|_timescaledb_internal.insert_blocker (1 row) +-- Verify that repartitioning works as expected on detach_data_node +SELECT * FROM detach_data_node('data_node_1', '"Table\\Schema"."Param_Table"', repartition => true); +NOTICE: the number of partitions in dimension "__region" was decreased to 2 + detach_data_node +------------------ + 1 +(1 row) + +SELECT h.table_name, d.column_name, d.num_slices +FROM _timescaledb_catalog.hypertable h, _timescaledb_catalog.dimension d +WHERE h.id = d.hypertable_id +AND h.table_name = 'Param_Table'; + table_name | column_name | num_slices +-------------+------------------+------------ + Param_Table | __region | 2 + Param_Table | time Col %#^#@$# | +(2 rows) + +SELECT * FROM detach_data_node('data_node_2', '"Table\\Schema"."Param_Table"', force => true, repartition => false); +WARNING: new data for hypertable "Param_Table" will be under-replicated due to detaching data node "data_node_2" + detach_data_node +------------------ + 1 +(1 row) + +SELECT h.table_name, d.column_name, d.num_slices +FROM _timescaledb_catalog.hypertable h, _timescaledb_catalog.dimension d +WHERE h.id = d.hypertable_id +AND h.table_name = 'Param_Table'; + table_name | column_name | num_slices +-------------+------------------+------------ + Param_Table | __region | 2 + Param_Table | time Col %#^#@$# | +(2 rows) + -- Test multi-dimensional hypertable. The add_dimension() command -- should be propagated to backends. CREATE TABLE dimented_table (time timestamptz, column1 int, column2 timestamptz, column3 int); @@ -1950,7 +1985,7 @@ SELECT * FROM _timescaledb_catalog.dimension; 2 | 1 | device | integer | f | 3 | _timescaledb_internal | get_partition_hash | | | 3 | 2 | time | timestamp with time zone | t | | | | 604800000000 | | 5 | 4 | time Col %#^#@$# | timestamp with time zone | t | | | | 604800000000 | | - 6 | 4 | __region | text | f | 4 | _timescaledb_internal | get_partition_hash | | | + 6 | 4 | __region | text | f | 2 | _timescaledb_internal | get_partition_hash | | | 7 | 5 | time | timestamp with time zone | t | | | | 604800000000 | | 8 | 5 | column1 | integer | f | 4 | _timescaledb_internal | get_partition_hash | | | 9 | 5 | column2 | timestamp with time zone | t | | | | 604800000000 | | @@ -1970,7 +2005,7 @@ SELECT * FROM _timescaledb_catalog.dimension; 2 | 1 | device | integer | f | 3 | _timescaledb_internal | get_partition_hash | | | 3 | 2 | time | timestamp with time zone | t | | | | 604800000000 | | 5 | 4 | time Col %#^#@$# | timestamp with time zone | t | | | | 604800000000 | | - 6 | 4 | __region | text | f | 4 | _timescaledb_internal | get_partition_hash | | | + 6 | 4 | __region | text | f | 2 | _timescaledb_internal | get_partition_hash | | | 7 | 5 | time | timestamp with time zone | t | | | | 604800000000 | | 8 | 5 | column1 | integer | f | 4 | _timescaledb_internal | get_partition_hash | | | 9 | 5 | column2 | timestamp with time zone | t | | | | 604800000000 | | diff --git a/tsl/test/expected/dist_hypertable-12.out b/tsl/test/expected/dist_hypertable-12.out index 4a9c0367d..58b11ae67 100644 --- a/tsl/test/expected/dist_hypertable-12.out +++ b/tsl/test/expected/dist_hypertable-12.out @@ -1909,6 +1909,41 @@ ts_insert_blocker| 7|_timescaledb_internal.insert_blocker (1 row) +-- Verify that repartitioning works as expected on detach_data_node +SELECT * FROM detach_data_node('data_node_1', '"Table\\Schema"."Param_Table"', repartition => true); +NOTICE: the number of partitions in dimension "__region" was decreased to 2 + detach_data_node +------------------ + 1 +(1 row) + +SELECT h.table_name, d.column_name, d.num_slices +FROM _timescaledb_catalog.hypertable h, _timescaledb_catalog.dimension d +WHERE h.id = d.hypertable_id +AND h.table_name = 'Param_Table'; + table_name | column_name | num_slices +-------------+------------------+------------ + Param_Table | __region | 2 + Param_Table | time Col %#^#@$# | +(2 rows) + +SELECT * FROM detach_data_node('data_node_2', '"Table\\Schema"."Param_Table"', force => true, repartition => false); +WARNING: new data for hypertable "Param_Table" will be under-replicated due to detaching data node "data_node_2" + detach_data_node +------------------ + 1 +(1 row) + +SELECT h.table_name, d.column_name, d.num_slices +FROM _timescaledb_catalog.hypertable h, _timescaledb_catalog.dimension d +WHERE h.id = d.hypertable_id +AND h.table_name = 'Param_Table'; + table_name | column_name | num_slices +-------------+------------------+------------ + Param_Table | __region | 2 + Param_Table | time Col %#^#@$# | +(2 rows) + -- Test multi-dimensional hypertable. The add_dimension() command -- should be propagated to backends. CREATE TABLE dimented_table (time timestamptz, column1 int, column2 timestamptz, column3 int); @@ -1950,7 +1985,7 @@ SELECT * FROM _timescaledb_catalog.dimension; 2 | 1 | device | integer | f | 3 | _timescaledb_internal | get_partition_hash | | | 3 | 2 | time | timestamp with time zone | t | | | | 604800000000 | | 5 | 4 | time Col %#^#@$# | timestamp with time zone | t | | | | 604800000000 | | - 6 | 4 | __region | text | f | 4 | _timescaledb_internal | get_partition_hash | | | + 6 | 4 | __region | text | f | 2 | _timescaledb_internal | get_partition_hash | | | 7 | 5 | time | timestamp with time zone | t | | | | 604800000000 | | 8 | 5 | column1 | integer | f | 4 | _timescaledb_internal | get_partition_hash | | | 9 | 5 | column2 | timestamp with time zone | t | | | | 604800000000 | | @@ -1970,7 +2005,7 @@ SELECT * FROM _timescaledb_catalog.dimension; 2 | 1 | device | integer | f | 3 | _timescaledb_internal | get_partition_hash | | | 3 | 2 | time | timestamp with time zone | t | | | | 604800000000 | | 5 | 4 | time Col %#^#@$# | timestamp with time zone | t | | | | 604800000000 | | - 6 | 4 | __region | text | f | 4 | _timescaledb_internal | get_partition_hash | | | + 6 | 4 | __region | text | f | 2 | _timescaledb_internal | get_partition_hash | | | 7 | 5 | time | timestamp with time zone | t | | | | 604800000000 | | 8 | 5 | column1 | integer | f | 4 | _timescaledb_internal | get_partition_hash | | | 9 | 5 | column2 | timestamp with time zone | t | | | | 604800000000 | | diff --git a/tsl/test/sql/dist_hypertable.sql.in b/tsl/test/sql/dist_hypertable.sql.in index e7e4a8066..9ec9712df 100644 --- a/tsl/test/sql/dist_hypertable.sql.in +++ b/tsl/test/sql/dist_hypertable.sql.in @@ -571,6 +571,18 @@ SELECT t.tgname, t.tgtype, t.tgfoid::regproc FROM pg_trigger t, pg_class c WHERE c.relname = 'Param_Table' AND t.tgrelid = c.oid; $$); +-- Verify that repartitioning works as expected on detach_data_node +SELECT * FROM detach_data_node('data_node_1', '"Table\\Schema"."Param_Table"', repartition => true); +SELECT h.table_name, d.column_name, d.num_slices +FROM _timescaledb_catalog.hypertable h, _timescaledb_catalog.dimension d +WHERE h.id = d.hypertable_id +AND h.table_name = 'Param_Table'; +SELECT * FROM detach_data_node('data_node_2', '"Table\\Schema"."Param_Table"', force => true, repartition => false); +SELECT h.table_name, d.column_name, d.num_slices +FROM _timescaledb_catalog.hypertable h, _timescaledb_catalog.dimension d +WHERE h.id = d.hypertable_id +AND h.table_name = 'Param_Table'; + -- Test multi-dimensional hypertable. The add_dimension() command -- should be propagated to backends. CREATE TABLE dimented_table (time timestamptz, column1 int, column2 timestamptz, column3 int);