Skip to content

Commit

Permalink
Repartition hypertables when attaching data node
Browse files Browse the repository at this point in the history
Distributed hypertables are now repartitioned when attaching new data
nodes and the current number of partition (slices) in the first closed
(space) dimension is less than the number of data nodes. Increasing
the number of partitions is necessary to make use of a newly attached
data node. However, repartitioning is optional and can be avoided via
a boolean parameter in `attach_server()`.

In addition to the above repartitioning, this change also adds
informational messages to `create_hypertable` and
`set_number_partitions` to raise awareness of situations when the
number of partitions in the space dimensions is lower than the number
of attached data nodes.
  • Loading branch information
erimatnor committed May 27, 2020
1 parent 9108dda commit 5309cd6
Show file tree
Hide file tree
Showing 14 changed files with 584 additions and 136 deletions.
12 changes: 6 additions & 6 deletions sql/ddl_api.sql
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,7 @@ AS '@MODULE_PATHNAME@', 'ts_tablespace_detach_all_from_hypertable' LANGUAGE C VO
CREATE OR REPLACE FUNCTION show_tablespaces(hypertable REGCLASS) RETURNS SETOF NAME
AS '@MODULE_PATHNAME@', 'ts_tablespace_show' LANGUAGE C VOLATILE STRICT;

-- Add a data node to a TimescaleDB distributed database. This also add a
-- corresponding user mapping, if one does not already exist.
-- Add a data node to a TimescaleDB distributed database.
CREATE OR REPLACE FUNCTION add_data_node(
node_name NAME,
host TEXT = 'localhost',
Expand All @@ -173,9 +172,9 @@ CREATE OR REPLACE FUNCTION add_data_node(
node_created BOOL, database_created BOOL, extension_created BOOL)
AS '@MODULE_PATHNAME@', 'ts_data_node_add' LANGUAGE C VOLATILE;

-- Delete a data node from the distributed database
-- Delete a data node from a distributed database
CREATE OR REPLACE FUNCTION delete_data_node(
node_name NAME,
node_name NAME,
if_exists BOOLEAN = FALSE,
cascade BOOLEAN = FALSE,
force BOOLEAN = FALSE
Expand All @@ -184,8 +183,9 @@ CREATE OR REPLACE FUNCTION delete_data_node(
-- Attach a data node to a distributed hypertable
CREATE OR REPLACE FUNCTION attach_data_node(
hypertable REGCLASS,
node_name NAME,
if_not_attached BOOLEAN = FALSE
node_name NAME,
if_not_attached BOOLEAN = FALSE,
repartition BOOLEAN = TRUE
) RETURNS TABLE(hypertable_id INTEGER, node_hypertable_id INTEGER, node_name NAME)
AS '@MODULE_PATHNAME@', 'ts_data_node_attach' LANGUAGE C VOLATILE;

Expand Down
36 changes: 33 additions & 3 deletions src/dimension.c
Original file line number Diff line number Diff line change
Expand Up @@ -811,11 +811,23 @@ ts_dimension_set_name(Dimension *dim, const char *newname)
int
ts_dimension_set_chunk_interval(Dimension *dim, int64 chunk_interval)
{
Assert(IS_OPEN_DIMENSION(dim));

dim->fd.interval_length = chunk_interval;

return dimension_scan_update(dim->fd.id, dimension_tuple_update, dim, RowExclusiveLock);
}

int
ts_dimension_set_number_of_slices(Dimension *dim, int16 num_slices)
{
Assert(IS_CLOSED_DIMENSION(dim));

dim->fd.num_slices = num_slices;

return dimension_scan_update(dim->fd.id, dimension_tuple_update, dim, RowExclusiveLock);
}

/*
* Apply any dimension-specific transformations on a value, i.e., apply
* partitioning function. Optionally get the type of the resulting value via
Expand Down Expand Up @@ -1103,6 +1115,8 @@ ts_dimension_update(Oid table_relid, Name dimname, DimensionType dimtype, Datum
Oid dimtype = ts_dimension_get_partition_type(dim);
Assert(NULL != intervaltype);

Assert(IS_OPEN_DIMENSION(dim));

dim->fd.interval_length =
dimension_interval_to_internal(NameStr(dim->fd.column_name),
dimtype,
Expand All @@ -1112,7 +1126,10 @@ ts_dimension_update(Oid table_relid, Name dimname, DimensionType dimtype, Datum
}

if (NULL != num_slices)
{
Assert(IS_CLOSED_DIMENSION(dim));
dim->fd.num_slices = *num_slices;
}

if (NULL != integer_now_func)
{
Expand All @@ -1129,6 +1146,7 @@ ts_dimension_update(Oid table_relid, Name dimname, DimensionType dimtype, Datum
}

dimension_scan_update(dim->fd.id, dimension_tuple_update, dim, RowExclusiveLock);
ts_hypertable_check_partitioning(ht, dim->fd.id);

ts_cache_release(hcache);
}
Expand All @@ -1141,13 +1159,16 @@ ts_dimension_set_num_slices(PG_FUNCTION_ARGS)
Oid table_relid = PG_GETARG_OID(0);
int32 num_slices_arg = PG_ARGISNULL(1) ? -1 : PG_GETARG_INT32(1);
Name colname = PG_ARGISNULL(2) ? NULL : PG_GETARG_NAME(2);
Cache *hcache = ts_hypertable_cache_pin();
Hypertable *ht;
int16 num_slices;

if (PG_ARGISNULL(0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid main_table: cannot be NULL")));
errmsg("invalid hypertable name: cannot be NULL")));

ht = ts_hypertable_cache_get_entry(hcache, table_relid, CACHE_FLAG_NONE);
ts_hypertable_permissions_check(table_relid, GetUserId());

if (PG_ARGISNULL(1) || !IS_VALID_NUM_SLICES(num_slices_arg))
Expand All @@ -1165,6 +1186,8 @@ ts_dimension_set_num_slices(PG_FUNCTION_ARGS)

ts_dimension_update(table_relid, colname, DIMENSION_TYPE_CLOSED, NULL, NULL, &num_slices, NULL);

ts_cache_release(hcache);

PG_RETURN_VOID();
}

Expand Down Expand Up @@ -1390,7 +1413,7 @@ ts_dimension_info_validate(DimensionInfo *info)
}
}

void
int32
ts_dimension_add_from_info(DimensionInfo *info)
{
if (info->set_not_null && info->type == DIMENSION_TYPE_OPEN)
Expand All @@ -1404,6 +1427,8 @@ ts_dimension_add_from_info(DimensionInfo *info)
info->num_slices,
info->partitioning_func,
info->interval);

return info->dimension_id;
}

/*
Expand Down Expand Up @@ -1507,6 +1532,8 @@ ts_dimension_add(PG_FUNCTION_ARGS)

if (!info.skip)
{
int32 dimension_id;

if (ts_hypertable_has_chunks(info.table_relid, AccessShareLock))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
Expand All @@ -1521,7 +1548,7 @@ ts_dimension_add(PG_FUNCTION_ARGS)
* table.
*/
ts_hypertable_set_num_dimensions(info.ht, info.ht->space->num_dimensions + 1);
ts_dimension_add_from_info(&info);
dimension_id = ts_dimension_add_from_info(&info);

/* Verify that existing indexes are compatible with a hypertable */

Expand All @@ -1532,6 +1559,9 @@ ts_dimension_add(PG_FUNCTION_ARGS)
*/
info.ht = ts_hypertable_get_by_id(info.ht->fd.id);
ts_indexing_verify_indexes(info.ht);

/* Check that partitioning is sane */
ts_hypertable_check_partitioning(info.ht, dimension_id);
}

retval = dimension_create_datum(fcinfo, &info);
Expand Down
3 changes: 2 additions & 1 deletion src/dimension.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ extern int ts_dimension_set_type(Dimension *dim, Oid newtype);
extern TSDLLEXPORT Oid ts_dimension_get_partition_type(Dimension *dim);
extern int ts_dimension_set_name(Dimension *dim, const char *newname);
extern int ts_dimension_set_chunk_interval(Dimension *dim, int64 chunk_interval);
extern TSDLLEXPORT int ts_dimension_set_number_of_slices(Dimension *dim, int16 num_slices);
extern Datum ts_dimension_transform_value(Dimension *dim, Oid collation, Datum value,
Oid const_datum_type, Oid *restype);
extern int ts_dimension_delete_by_hypertable_id(int32 hypertable_id, bool delete_slices);
Expand All @@ -164,7 +165,7 @@ extern TSDLLEXPORT DimensionInfo *ts_dimension_info_create_closed(Oid table_reli
regproc partitioning_func);

extern void ts_dimension_info_validate(DimensionInfo *info);
extern void ts_dimension_add_from_info(DimensionInfo *info);
extern int32 ts_dimension_add_from_info(DimensionInfo *info);
extern void ts_dimensions_rename_schema_name(char *oldname, char *newname);
extern TSDLLEXPORT void ts_dimension_update(Oid table_relid, Name dimname, DimensionType dimtype,
Datum *interval, Oid *intervaltype, int16 *num_slices,
Expand Down
5 changes: 3 additions & 2 deletions src/errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@
#define ERRCODE_TS_TABLESPACE_NOT_ATTACHED MAKE_SQLSTATE('T', 'S', '1', '5', '0')
#define ERRCODE_TS_DUPLICATE_DIMENSION MAKE_SQLSTATE('T', 'S', '1', '6', '0')
#define ERRCODE_TS_NO_DATA_NODES MAKE_SQLSTATE('T', 'S', '1', '7', '0')
#define ERRCODE_TS_DATA_NODE_ASSIGNMENT_ALREADY_EXISTS MAKE_SQLSTATE('T', 'S', '1', '8', '0')
#define ERRCODE_TS_DATA_NODE_NOT_ATTACHED MAKE_SQLSTATE('T', 'S', '1', '9', '0')
#define ERRCODE_TS_DATA_NODE_ASSIGNMENT_ALREADY_EXISTS MAKE_SQLSTATE('T', 'S', '1', '7', '1')
#define ERRCODE_TS_DATA_NODE_ALREADY_ATTACHED MAKE_SQLSTATE('T', 'S', '1', '7', '2')
#define ERRCODE_TS_DATA_NODE_NOT_ATTACHED MAKE_SQLSTATE('T', 'S', '1', '7', '3')

/*
--IO500 - GROUP: internal error
Expand Down
60 changes: 59 additions & 1 deletion src/hypertable.c
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,36 @@ create_hypertable_datum(FunctionCallInfo fcinfo, Hypertable *ht, bool created)
return HeapTupleGetDatum(tuple);
}

/*
* Check that the partitioning is reasonable and raise warnings if
* not. Typically called after applying updates to a partitioning dimension.
*/
void
ts_hypertable_check_partitioning(Hypertable *ht, int32 id_of_updated_dimension)
{
Dimension *dim = ts_hyperspace_get_dimension_by_id(ht->space, id_of_updated_dimension);

Assert(NULL != dim);

if (hypertable_is_distributed(ht))
{
Dimension *first_closed_dim = hyperspace_get_closed_dimension(ht->space, 0);
int num_nodes = list_length(ht->data_nodes);

/* Warn the user that there aren't enough slices to make use of all
* servers. Only do this if this is the first closed (space) dimension. */
if (first_closed_dim != NULL && dim->fd.id == first_closed_dim->fd.id &&
num_nodes > first_closed_dim->fd.num_slices)
ereport(WARNING,
(errmsg("the number of partitions in dimension \"%s\" is too low to "
"make use of all attached data nodes",
NameStr(dim->fd.column_name)),
errhint("Increase the number of partitions in dimension \"%s\" to match or "
"exceed the number of attached data nodes.",
NameStr(dim->fd.column_name))));
}
}

static int16
validate_replication_factor(int32 replication_factor, bool is_null, bool is_dist_call)
{
Expand Down Expand Up @@ -1752,14 +1782,38 @@ ts_hypertable_create_internal(PG_FUNCTION_ARGS, bool is_dist_call)
bool created;
uint32 flags = 0;

if (NULL != data_nodes && ARR_NDIM(data_nodes) > 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid data nodes format"),
errhint("Specify a one-dimensional array of data nodes.")));

if (NULL != space_dim_name)
{
int16 num_partitions = PG_ARGISNULL(3) ? -1 : PG_GETARG_INT16(3);

/* If the number of partitions isn't specified, default to setting it
* to the number of data nodes */
if (num_partitions < 1 && NULL != data_nodes)
{
int num_nodes = ArrayGetNItems(ARR_NDIM(data_nodes), ARR_DIMS(data_nodes));

if (num_nodes > MAX_NUM_HYPERTABLE_DATA_NODES)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("max number of data nodes exceeded"),
errhint("The number of data nodes cannot exceed %d.",
MAX_NUM_HYPERTABLE_DATA_NODES)));

num_partitions = num_nodes & 0xFFFF;
}

space_dim_info =
ts_dimension_info_create_closed(table_relid,
/* column name */
space_dim_name,
/* number partitions */
PG_ARGISNULL(3) ? -1 : PG_GETARG_INT16(3),
num_partitions,
/* partitioning func */
PG_ARGISNULL(9) ? InvalidOid : PG_GETARG_OID(9));
}
Expand Down Expand Up @@ -1800,6 +1854,10 @@ ts_hypertable_create_internal(PG_FUNCTION_ARGS, bool is_dist_call)
data_nodes);

ht = ts_hypertable_cache_get_cache_and_entry(table_relid, CACHE_FLAG_NONE, &hcache);

if (NULL != space_dim_info)
ts_hypertable_check_partitioning(ht, space_dim_info->dimension_id);

retval = create_hypertable_datum(fcinfo, ht, created);
ts_cache_release(hcache);

Expand Down
6 changes: 6 additions & 0 deletions src/hypertable.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@

#define INVALID_HYPERTABLE_ID 0

/* We cannot make use of more data nodes than we have slices in closed (space)
* dimensions, and the value for number of slices is an int16. */
#define MAX_NUM_HYPERTABLE_DATA_NODES PG_INT16_MAX

typedef struct SubspaceStore SubspaceStore;
typedef struct Chunk Chunk;
typedef struct Hypercube Hypercube;
Expand Down Expand Up @@ -122,6 +126,8 @@ extern TSDLLEXPORT ObjectAddress ts_hypertable_create_trigger(Hypertable *ht, Cr
extern TSDLLEXPORT void ts_hypertable_drop_trigger(Hypertable *ht, const char *trigger_name);
extern TSDLLEXPORT void ts_hypertable_drop(Hypertable *hypertable, DropBehavior behavior);

extern TSDLLEXPORT void ts_hypertable_check_partitioning(Hypertable *ht,
int32 id_of_updated_dimension);
extern int ts_hypertable_reset_associated_schema_name(const char *associated_schema);
extern TSDLLEXPORT Oid ts_hypertable_id_to_relid(int32 hypertable_id);
extern TSDLLEXPORT int32 ts_hypertable_relid_to_id(Oid relid);
Expand Down
2 changes: 1 addition & 1 deletion test/expected/ddl_errors.out
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,5 @@ ERROR: invalid tablespace name
\set ON_ERROR_STOP 1
\set ON_ERROR_STOP 0
select set_number_partitions(NULL,NULL);
ERROR: invalid main_table: cannot be NULL
ERROR: invalid hypertable name: cannot be NULL
\set ON_ERROR_STOP 1
Loading

0 comments on commit 5309cd6

Please sign in to comment.