diff --git a/sql/ddl_internal.sql b/sql/ddl_internal.sql index 135d919638a..ee990fea34f 100644 --- a/sql/ddl_internal.sql +++ b/sql/ddl_internal.sql @@ -54,3 +54,9 @@ $BODY$ SET search_path TO pg_catalog, pg_temp; CREATE OR REPLACE FUNCTION _timescaledb_internal.health() RETURNS TABLE (node_name NAME, healthy BOOL, in_recovery BOOL, error TEXT) AS '@MODULE_PATHNAME@', 'ts_health_check' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION _timescaledb_internal.drop_stale_chunks( + node_name NAME, + chunks integer[] = NULL +) RETURNS VOID +AS '@MODULE_PATHNAME@', 'ts_chunks_drop_stale' LANGUAGE C VOLATILE; diff --git a/sql/updates/latest-dev.sql b/sql/updates/latest-dev.sql index 54dafbf1a20..018d0e3c283 100644 --- a/sql/updates/latest-dev.sql +++ b/sql/updates/latest-dev.sql @@ -496,3 +496,10 @@ ALTER TABLE _timescaledb_catalog.continuous_agg_migrate_plan REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id); ANALYZE _timescaledb_catalog.continuous_agg; + +-- changes related to drop_stale_chunks() +CREATE FUNCTION _timescaledb_internal.drop_stale_chunks( + node_name NAME, + chunks integer[] = NULL +) RETURNS VOID +AS '@MODULE_PATHNAME@', 'ts_chunks_drop_stale' LANGUAGE C VOLATILE; diff --git a/sql/updates/reverse-dev.sql b/sql/updates/reverse-dev.sql index 559820ff85c..12b1980481f 100644 --- a/sql/updates/reverse-dev.sql +++ b/sql/updates/reverse-dev.sql @@ -429,3 +429,6 @@ ALTER TABLE _timescaledb_catalog.continuous_agg_migrate_plan REFERENCES _timescaledb_catalog.continuous_agg (mat_hypertable_id); ANALYZE _timescaledb_catalog.continuous_agg; + +-- changes related to drop_stale_chunks() +DROP FUNCTION _timescaledb_internal.drop_stale_chunks; diff --git a/src/chunk.c b/src/chunk.c index 22157555a22..3b1079508f9 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -3261,6 +3261,28 @@ ts_chunk_get_chunk_ids_by_hypertable_id(int32 hypertable_id) return chunkids; } +List * +ts_chunk_get_all_chunk_ids(LOCKMODE lockmode) +{ + List *chunkids = NIL; + ScanIterator iterator = ts_scan_iterator_create(CHUNK, lockmode, CurrentMemoryContext); + ts_scan_iterator_set_index(&iterator, CHUNK, CHUNK_ID_INDEX); + ts_scan_iterator_scan_key_init(&iterator, + Anum_chunk_idx_id, + BTEqualStrategyNumber, + F_INT4GE, + Int32GetDatum(0)); + ts_scanner_foreach(&iterator) + { + bool isnull; + Datum id = slot_getattr(ts_scan_iterator_slot(&iterator), Anum_chunk_id, &isnull); + if (!isnull) + chunkids = lappend_int(chunkids, DatumGetInt32(id)); + } + + return chunkids; +} + static ChunkResult chunk_recreate_constraint(ChunkScanCtx *ctx, ChunkStub *stub) { diff --git a/src/chunk.h b/src/chunk.h index b6b3f2b8092..517444b342d 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -215,7 +215,9 @@ extern TSDLLEXPORT bool ts_chunk_contains_compressed_data(const Chunk *chunk); extern TSDLLEXPORT ChunkCompressionStatus ts_chunk_get_compression_status(int32 chunk_id); extern TSDLLEXPORT Datum ts_chunk_id_from_relid(PG_FUNCTION_ARGS); extern TSDLLEXPORT List *ts_chunk_get_chunk_ids_by_hypertable_id(int32 hypertable_id); +extern TSDLLEXPORT List *ts_chunk_get_all_chunk_ids(LOCKMODE lockmode); extern TSDLLEXPORT List *ts_chunk_get_data_node_name_list(const Chunk *chunk); + extern bool TSDLLEXPORT ts_chunk_has_data_node(const Chunk *chunk, const char *node_name); extern List *ts_chunk_data_nodes_copy(const Chunk *chunk); extern TSDLLEXPORT Chunk *ts_chunk_create_only_table(Hypertable *ht, Hypercube *cube, diff --git a/src/cross_module_fn.c b/src/cross_module_fn.c index 06af2fac474..f585ce3fc05 100644 --- a/src/cross_module_fn.c +++ b/src/cross_module_fn.c @@ -106,6 +106,7 @@ CROSSMODULE_WRAPPER(data_node_alter); CROSSMODULE_WRAPPER(chunk_drop_replica); CROSSMODULE_WRAPPER(chunk_freeze_chunk); CROSSMODULE_WRAPPER(chunk_unfreeze_chunk); +CROSSMODULE_WRAPPER(chunks_drop_stale); CROSSMODULE_WRAPPER(chunk_set_default_data_node); CROSSMODULE_WRAPPER(chunk_get_relstats); @@ -520,6 +521,7 @@ TSDLLEXPORT CrossModuleFunctions ts_cm_functions_default = { .chunk_drop_replica = error_no_default_fn_pg_community, .chunk_freeze_chunk = error_no_default_fn_pg_community, .chunk_unfreeze_chunk = error_no_default_fn_pg_community, + .chunks_drop_stale = error_no_default_fn_pg_community, .hypertable_make_distributed = hypertable_make_distributed_default_fn, .get_and_validate_data_node_list = get_and_validate_data_node_list_default_fn, .timescaledb_fdw_handler = error_no_default_fn_pg_community, diff --git a/src/cross_module_fn.h b/src/cross_module_fn.h index 8c479f5e546..f05808301de 100644 --- a/src/cross_module_fn.h +++ b/src/cross_module_fn.h @@ -200,6 +200,7 @@ typedef struct CrossModuleFunctions PGFunction chunk_drop_replica; PGFunction chunk_freeze_chunk; PGFunction chunk_unfreeze_chunk; + PGFunction chunks_drop_stale; void (*update_compressed_chunk_relstats)(Oid uncompressed_relid, Oid compressed_relid); CompressSingleRowState *(*compress_row_init)(int srcht_id, Relation in_rel, Relation out_rel); TupleTableSlot *(*compress_row_exec)(CompressSingleRowState *cr, TupleTableSlot *slot); diff --git a/src/hypertable.h b/src/hypertable.h index a5449787386..8d7b11e2e87 100644 --- a/src/hypertable.h +++ b/src/hypertable.h @@ -143,7 +143,7 @@ extern Tablespace *ts_hypertable_select_tablespace(const Hypertable *ht, const C extern const char *ts_hypertable_select_tablespace_name(const Hypertable *ht, const Chunk *chunk); extern Tablespace *ts_hypertable_get_tablespace_at_offset_from(int32 hypertable_id, Oid tablespace_oid, int16 offset); -extern bool ts_hypertable_has_chunks(Oid table_relid, LOCKMODE lockmode); +extern TSDLLEXPORT bool ts_hypertable_has_chunks(Oid table_relid, LOCKMODE lockmode); extern void ts_hypertables_rename_schema_name(const char *old_name, const char *new_name); extern bool ts_is_partitioning_column(const Hypertable *ht, AttrNumber column_attno); extern TSDLLEXPORT bool ts_hypertable_set_compressed(Hypertable *ht, diff --git a/tsl/src/chunk.c b/tsl/src/chunk.c index 9d4f2b7e8a0..ff4950823a5 100644 --- a/tsl/src/chunk.c +++ b/tsl/src/chunk.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -561,3 +562,200 @@ chunk_unfreeze_chunk(PG_FUNCTION_ARGS) bool ret = ts_chunk_unset_frozen(chunk); PG_RETURN_BOOL(ret); } + +static List * +chunk_id_list_create(ArrayType *array) +{ + /* create a sorted list of chunk ids from array */ + ArrayIterator it; + Datum id_datum; + List *id_list = NIL; + bool isnull; + + it = array_create_iterator(array, 0, NULL); + while (array_iterate(it, &id_datum, &isnull)) + { + if (isnull) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("chunks array arguments cannot be NULL"))); + id_list = lappend_int(id_list, DatumGetInt32(id_datum)); + } + array_free_iterator(it); + + (void) list_sort_compat(id_list, list_int_cmp_compat); + return id_list; +} + +static List * +chunk_id_list_exclusive_right_merge_join(const List *an_list, const List *dn_list) +{ + /* + * merge join two sorted list and return only values which exclusively + * exists in the right target (dn_list list) + */ + List *result = NIL; + const ListCell *l = list_head(an_list); + const ListCell *r = list_head(dn_list); + for (;;) + { + if (l && r) + { + int compare = list_int_cmp_compat(l, r); + if (compare == 0) + { + /* l = r */ + l = lnext_compat(an_list, l); + r = lnext_compat(dn_list, r); + } + else if (compare < 0) + { + /* l < r */ + /* chunk exists only on the access node */ + l = lnext_compat(an_list, l); + } + else + { + /* l > r */ + /* chunk exists only on the data node */ + result = lappend_int(result, lfirst_int(r)); + r = lnext_compat(dn_list, r); + } + } + else if (l) + { + /* chunk exists only on the access node */ + l = lnext_compat(an_list, l); + } + else if (r) + { + /* chunk exists only on the data node */ + result = lappend_int(result, lfirst_int(r)); + r = lnext_compat(dn_list, r); + } + else + { + break; + } + } + return result; +} + +/* + * chunk_drop_stale_chunks: + * + * This function drops chunks on a specified data node if those chunks are + * not known by the access node (chunks array). + * + * This function is intended to be used on the access node and data node. + */ +Datum +chunk_drop_stale_chunks(PG_FUNCTION_ARGS) +{ + char *node_name = PG_ARGISNULL(0) ? NULL : NameStr(*PG_GETARG_NAME(0)); + ArrayType *chunks_array = PG_ARGISNULL(1) ? NULL : PG_GETARG_ARRAYTYPE_P(1); + DistUtilMembershipStatus membership; + + TS_PREVENT_FUNC_IF_READ_ONLY(); + + /* execute according to the node membership */ + membership = dist_util_membership(); + if (membership == DIST_MEMBER_ACCESS_NODE) + { + StringInfo cmd = makeStringInfo(); + bool first = true; + + if (node_name == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("node_name argument cannot be NULL"))); + if (chunks_array != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("chunks argument cannot be used on the access node"))); + + /* get an exclusive lock on the chunks catalog table to prevent new chunk + * creation during this operation */ + LockRelationOid(ts_catalog_get()->tables[CHUNK].id, AccessExclusiveLock); + + /* generate query to execute drop_stale_chunks() on the data node */ + appendStringInfo(cmd, "SELECT _timescaledb_internal.drop_stale_chunks(NULL, array["); + + /* scan for chunks that reference the given data node */ + ScanIterator it = ts_chunk_data_nodes_scan_iterator_create(CurrentMemoryContext); + ts_chunk_data_nodes_scan_iterator_set_node_name(&it, node_name); + ts_scanner_foreach(&it) + { + TupleTableSlot *slot = ts_scan_iterator_slot(&it); + bool PG_USED_FOR_ASSERTS_ONLY isnull = false; + int32_t node_chunk_id; + + node_chunk_id = + DatumGetInt32(slot_getattr(slot, Anum_chunk_data_node_node_chunk_id, &isnull)); + Assert(!isnull); + + appendStringInfo(cmd, "%s%d", first ? "" : ",", node_chunk_id); + first = false; + } + ts_scan_iterator_close(&it); + + appendStringInfo(cmd, "]::integer[])"); + + /* execute command on the data node */ + ts_dist_cmd_run_on_data_nodes(cmd->data, list_make1(node_name), true); + } + else if (membership == DIST_MEMBER_DATA_NODE) + { + List *an_chunk_id_list = NIL; + List *dn_chunk_id_list = NIL; + List *dn_chunk_id_list_stale = NIL; + ListCell *lc; + Cache *htcache; + + if (node_name != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("node_name argument cannot be used on the data node"))); + + if (chunks_array == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("chunks argument cannot be NULL"))); + + /* get a sorted list of chunk ids from the supplied chunks id argument array */ + an_chunk_id_list = chunk_id_list_create(chunks_array); + + /* get a local sorted list of chunk ids */ + dn_chunk_id_list = ts_chunk_get_all_chunk_ids(RowExclusiveLock); + + /* merge join two sorted list and get chunk ids which exists locally */ + dn_chunk_id_list_stale = + chunk_id_list_exclusive_right_merge_join(an_chunk_id_list, dn_chunk_id_list); + + /* drop stale chunks */ + htcache = ts_hypertable_cache_pin(); + foreach (lc, dn_chunk_id_list_stale) + { + const Chunk *chunk = ts_chunk_get_by_id(lfirst_int(lc), false); + Hypertable *ht; + + /* chunk might be already dropped by previous drop, if the chunk was compressed */ + if (chunk == NULL) + continue; + + /* ensure that we drop only chunks related to distributed hypertables */ + ht = ts_hypertable_cache_get_entry(htcache, chunk->hypertable_relid, CACHE_FLAG_NONE); + if (hypertable_is_distributed_member(ht)) + ts_chunk_drop(chunk, DROP_RESTRICT, DEBUG1); + } + ts_cache_release(htcache); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("current server is not an access node or data node"))); + } + + PG_RETURN_VOID(); +} diff --git a/tsl/src/chunk.h b/tsl/src/chunk.h index 9d0dcfaaff5..6187814c623 100644 --- a/tsl/src/chunk.h +++ b/tsl/src/chunk.h @@ -16,6 +16,7 @@ extern Datum chunk_set_default_data_node(PG_FUNCTION_ARGS); extern Datum chunk_drop_replica(PG_FUNCTION_ARGS); extern Datum chunk_freeze_chunk(PG_FUNCTION_ARGS); extern Datum chunk_unfreeze_chunk(PG_FUNCTION_ARGS); +extern Datum chunk_drop_stale_chunks(PG_FUNCTION_ARGS); extern int chunk_invoke_drop_chunks(Oid relid, Datum older_than, Datum older_than_type); extern Datum chunk_create_replica_table(PG_FUNCTION_ARGS); diff --git a/tsl/src/init.c b/tsl/src/init.c index f30a93ca409..1bd7f16251a 100644 --- a/tsl/src/init.c +++ b/tsl/src/init.c @@ -201,6 +201,7 @@ CrossModuleFunctions tsl_cm_functions = { .chunk_drop_replica = chunk_drop_replica, .chunk_freeze_chunk = chunk_freeze_chunk, .chunk_unfreeze_chunk = chunk_unfreeze_chunk, + .chunks_drop_stale = chunk_drop_stale_chunks, .hypertable_make_distributed = hypertable_make_distributed, .get_and_validate_data_node_list = hypertable_get_and_validate_data_nodes, .timescaledb_fdw_handler = timescaledb_fdw_handler, diff --git a/tsl/test/expected/dist_ddl.out b/tsl/test/expected/dist_ddl.out index 2ac203e33c6..bd248d14b77 100644 --- a/tsl/test/expected/dist_ddl.out +++ b/tsl/test/expected/dist_ddl.out @@ -2540,6 +2540,7 @@ NOTICE: adding not-null constraint to column "time" INSERT INTO hyper SELECT t, ceil((random() * 5))::int, random() * 80 FROM generate_series('2019-01-01'::timestamptz, '2019-01-05'::timestamptz, '1 minute') as t; ANALYZE hyper; +DROP TABLE hyper; -- -- Ensure single query multi-statement command is blocked -- @@ -2805,6 +2806,465 @@ SELECT is_distributed, replication_factor FROM timescaledb_information.hypertabl (1 row) DROP TABLE drf_test; +-- test drop_stale_chunks() +-- +-- test directly on a data node first +CREATE TABLE dist_test(time timestamptz NOT NULL, device int, temp float); +SELECT create_distributed_hypertable('dist_test', 'time', 'device', 3, replication_factor => 3); + create_distributed_hypertable +------------------------------- + (34,public,dist_test,t) +(1 row) + +INSERT INTO dist_test SELECT t, (abs(timestamp_hash(t::timestamp)) % 10) + 1, 0.10 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-08 1:00', '1 hour') t; +SELECT * from show_chunks('dist_test'); + show_chunks +----------------------------------------------- + _timescaledb_internal._dist_hyper_34_31_chunk + _timescaledb_internal._dist_hyper_34_32_chunk + _timescaledb_internal._dist_hyper_34_33_chunk + _timescaledb_internal._dist_hyper_34_34_chunk + _timescaledb_internal._dist_hyper_34_35_chunk +(5 rows) + +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); +NOTICE: [db_dist_ddl_1]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_1]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_34_31_chunk +_timescaledb_internal._dist_hyper_34_32_chunk +_timescaledb_internal._dist_hyper_34_33_chunk +_timescaledb_internal._dist_hyper_34_34_chunk +_timescaledb_internal._dist_hyper_34_35_chunk +(5 rows) + + +NOTICE: [db_dist_ddl_2]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_2]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_34_31_chunk +_timescaledb_internal._dist_hyper_34_32_chunk +_timescaledb_internal._dist_hyper_34_33_chunk +_timescaledb_internal._dist_hyper_34_34_chunk +_timescaledb_internal._dist_hyper_34_35_chunk +(5 rows) + + +NOTICE: [db_dist_ddl_3]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_3]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_34_31_chunk +_timescaledb_internal._dist_hyper_34_32_chunk +_timescaledb_internal._dist_hyper_34_33_chunk +_timescaledb_internal._dist_hyper_34_34_chunk +_timescaledb_internal._dist_hyper_34_35_chunk +(5 rows) + + + remote_exec +------------- + +(1 row) + +\c :DATA_NODE_1 +-- check call arguments when executed on data node +\set ON_ERROR_STOP 0 +SELECT _timescaledb_internal.drop_stale_chunks(NULL, NULL); +ERROR: chunks argument cannot be NULL +SELECT _timescaledb_internal.drop_stale_chunks('dn1', NULL); +ERROR: node_name argument cannot be used on the data node +\set ON_ERROR_STOP 1 +-- direct call to all chunks other then 19, 21 +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; + id | table_name +----+------------------------- + 19 | _dist_hyper_34_31_chunk + 20 | _dist_hyper_34_32_chunk + 21 | _dist_hyper_34_33_chunk + 22 | _dist_hyper_34_34_chunk + 23 | _dist_hyper_34_35_chunk +(5 rows) + +SET client_min_messages TO DEBUG1; +SELECT _timescaledb_internal.drop_stale_chunks(NULL, array[19, 21]::integer[]); +LOG: statement: SELECT _timescaledb_internal.drop_stale_chunks(NULL, array[19, 21]::integer[]); +DEBUG: dropping chunk _timescaledb_internal._dist_hyper_34_32_chunk +DEBUG: dropping chunk _timescaledb_internal._dist_hyper_34_34_chunk +DEBUG: dropping chunk _timescaledb_internal._dist_hyper_34_35_chunk + drop_stale_chunks +------------------- + +(1 row) + +RESET client_min_messages; +LOG: statement: RESET client_min_messages; +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; + id | table_name +----+------------------------- + 19 | _dist_hyper_34_31_chunk + 21 | _dist_hyper_34_33_chunk +(2 rows) + +-- ensure that drop_stale_chunks() does not affect local chunks +CREATE TABLE local_test(time timestamptz NOT NULL, device int, temp float); +SELECT create_hypertable('local_test', 'time', 'device', 3); + create_hypertable +-------------------------- + (31,public,local_test,t) +(1 row) + +INSERT INTO local_test SELECT t, (abs(timestamp_hash(t::timestamp)) % 10) + 1, 0.10 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-08 1:00', '1 hour') t; +SELECT * from show_chunks('local_test'); + show_chunks +------------------------------------------ + _timescaledb_internal._hyper_31_24_chunk + _timescaledb_internal._hyper_31_25_chunk + _timescaledb_internal._hyper_31_26_chunk + _timescaledb_internal._hyper_31_27_chunk + _timescaledb_internal._hyper_31_28_chunk +(5 rows) + +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; + id | table_name +----+------------------------- + 19 | _dist_hyper_34_31_chunk + 21 | _dist_hyper_34_33_chunk + 24 | _hyper_31_24_chunk + 25 | _hyper_31_25_chunk + 26 | _hyper_31_26_chunk + 27 | _hyper_31_27_chunk + 28 | _hyper_31_28_chunk +(7 rows) + +SET client_min_messages TO DEBUG1; +SELECT _timescaledb_internal.drop_stale_chunks(NULL, array[19]::integer[]); +LOG: statement: SELECT _timescaledb_internal.drop_stale_chunks(NULL, array[19]::integer[]); +DEBUG: dropping chunk _timescaledb_internal._dist_hyper_34_33_chunk + drop_stale_chunks +------------------- + +(1 row) + +RESET client_min_messages; +LOG: statement: RESET client_min_messages; +SELECT * from show_chunks('local_test'); + show_chunks +------------------------------------------ + _timescaledb_internal._hyper_31_24_chunk + _timescaledb_internal._hyper_31_25_chunk + _timescaledb_internal._hyper_31_26_chunk + _timescaledb_internal._hyper_31_27_chunk + _timescaledb_internal._hyper_31_28_chunk +(5 rows) + +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; + id | table_name +----+------------------------- + 19 | _dist_hyper_34_31_chunk + 24 | _hyper_31_24_chunk + 25 | _hyper_31_25_chunk + 26 | _hyper_31_26_chunk + 27 | _hyper_31_27_chunk + 28 | _hyper_31_28_chunk +(6 rows) + +DROP TABLE local_test; +\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER; +DROP TABLE dist_test; +-- test from access node +CREATE TABLE dist_test(time timestamptz NOT NULL, device int, temp float); +SELECT create_distributed_hypertable('dist_test', 'time', 'device', 3, replication_factor => 3); + create_distributed_hypertable +------------------------------- + (35,public,dist_test,t) +(1 row) + +INSERT INTO dist_test SELECT t, (abs(timestamp_hash(t::timestamp)) % 10) + 1, 0.10 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-08 1:00', '1 hour') t; +SELECT * from show_chunks('dist_test'); + show_chunks +----------------------------------------------- + _timescaledb_internal._dist_hyper_35_36_chunk + _timescaledb_internal._dist_hyper_35_37_chunk + _timescaledb_internal._dist_hyper_35_38_chunk + _timescaledb_internal._dist_hyper_35_39_chunk + _timescaledb_internal._dist_hyper_35_40_chunk +(5 rows) + +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); +NOTICE: [db_dist_ddl_1]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_1]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_36_chunk +_timescaledb_internal._dist_hyper_35_37_chunk +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(5 rows) + + +NOTICE: [db_dist_ddl_2]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_2]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_36_chunk +_timescaledb_internal._dist_hyper_35_37_chunk +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(5 rows) + + +NOTICE: [db_dist_ddl_3]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_3]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_36_chunk +_timescaledb_internal._dist_hyper_35_37_chunk +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(5 rows) + + + remote_exec +------------- + +(1 row) + +-- check call arguments when executed on access node +\set ON_ERROR_STOP 0 +SELECT _timescaledb_internal.drop_stale_chunks( NULL, NULL); +ERROR: node_name argument cannot be NULL +SELECT _timescaledb_internal.drop_stale_chunks(NULL, array[1,2,3]); +ERROR: node_name argument cannot be NULL +\set ON_ERROR_STOP 1 +-- create stale chunk by dropping them from access node +DROP FOREIGN TABLE _timescaledb_internal._dist_hyper_35_36_chunk; +DROP FOREIGN TABLE _timescaledb_internal._dist_hyper_35_37_chunk; +SELECT * from show_chunks('dist_test'); + show_chunks +----------------------------------------------- + _timescaledb_internal._dist_hyper_35_38_chunk + _timescaledb_internal._dist_hyper_35_39_chunk + _timescaledb_internal._dist_hyper_35_40_chunk +(3 rows) + +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); +NOTICE: [db_dist_ddl_1]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_1]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_36_chunk +_timescaledb_internal._dist_hyper_35_37_chunk +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(5 rows) + + +NOTICE: [db_dist_ddl_2]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_2]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_36_chunk +_timescaledb_internal._dist_hyper_35_37_chunk +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(5 rows) + + +NOTICE: [db_dist_ddl_3]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_3]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_36_chunk +_timescaledb_internal._dist_hyper_35_37_chunk +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(5 rows) + + + remote_exec +------------- + +(1 row) + +---- drop stale chunks 36, 37 on data nodes +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_1'); + drop_stale_chunks +------------------- + +(1 row) + +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_2'); + drop_stale_chunks +------------------- + +(1 row) + +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_3'); + drop_stale_chunks +------------------- + +(1 row) + +SELECT * from show_chunks('dist_test'); + show_chunks +----------------------------------------------- + _timescaledb_internal._dist_hyper_35_38_chunk + _timescaledb_internal._dist_hyper_35_39_chunk + _timescaledb_internal._dist_hyper_35_40_chunk +(3 rows) + +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); +NOTICE: [db_dist_ddl_1]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_1]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(3 rows) + + +NOTICE: [db_dist_ddl_2]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_2]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(3 rows) + + +NOTICE: [db_dist_ddl_3]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_3]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(3 rows) + + + remote_exec +------------- + +(1 row) + +-- test drop_stale_chunks() with compressed chunk +ALTER TABLE dist_test set (timescaledb.compress, timescaledb.compress_segmentby = 'device', timescaledb.compress_orderby = 'time'); +SELECT compress_chunk('_timescaledb_internal._dist_hyper_35_38_chunk'); + compress_chunk +----------------------------------------------- + _timescaledb_internal._dist_hyper_35_38_chunk +(1 row) + +\c :DATA_NODE_1 +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; + id | table_name +----+---------------------------- + 31 | _dist_hyper_35_38_chunk + 32 | _dist_hyper_35_39_chunk + 33 | _dist_hyper_35_40_chunk + 34 | compress_hyper_33_34_chunk +(4 rows) + +SELECT * from show_chunks('dist_test'); + show_chunks +----------------------------------------------- + _timescaledb_internal._dist_hyper_35_38_chunk + _timescaledb_internal._dist_hyper_35_39_chunk + _timescaledb_internal._dist_hyper_35_40_chunk +(3 rows) + +\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER; +DROP FOREIGN TABLE _timescaledb_internal._dist_hyper_35_38_chunk; +SELECT * from show_chunks('dist_test'); + show_chunks +----------------------------------------------- + _timescaledb_internal._dist_hyper_35_39_chunk + _timescaledb_internal._dist_hyper_35_40_chunk +(2 rows) + +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); +NOTICE: [db_dist_ddl_1]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_1]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(3 rows) + + +NOTICE: [db_dist_ddl_2]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_2]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(3 rows) + + +NOTICE: [db_dist_ddl_3]: SELECT * from show_chunks('dist_test') +NOTICE: [db_dist_ddl_3]: +show_chunks +--------------------------------------------- +_timescaledb_internal._dist_hyper_35_38_chunk +_timescaledb_internal._dist_hyper_35_39_chunk +_timescaledb_internal._dist_hyper_35_40_chunk +(3 rows) + + + remote_exec +------------- + +(1 row) + +-- drop stale chunk 38 +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_1'); + drop_stale_chunks +------------------- + +(1 row) + +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_2'); + drop_stale_chunks +------------------- + +(1 row) + +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_3'); + drop_stale_chunks +------------------- + +(1 row) + +\c :DATA_NODE_1 +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; + id | table_name +----+------------------------- + 32 | _dist_hyper_35_39_chunk + 33 | _dist_hyper_35_40_chunk +(2 rows) + +SELECT * from show_chunks('dist_test'); + show_chunks +----------------------------------------------- + _timescaledb_internal._dist_hyper_35_39_chunk + _timescaledb_internal._dist_hyper_35_40_chunk +(2 rows) + +\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER; +DROP TABLE dist_test; -- cleanup \c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER; DROP DATABASE :DATA_NODE_1; diff --git a/tsl/test/shared/expected/extension.out b/tsl/test/shared/expected/extension.out index a0040d52fef..dbc5985fb13 100644 --- a/tsl/test/shared/expected/extension.out +++ b/tsl/test/shared/expected/extension.out @@ -66,6 +66,7 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text _timescaledb_internal.dimension_slice_get_constraint_sql(integer) _timescaledb_internal.drop_chunk(regclass) _timescaledb_internal.drop_dist_ht_invalidation_trigger(integer) + _timescaledb_internal.drop_stale_chunks(name,integer[]) _timescaledb_internal.finalize_agg(text,name,name,name[],bytea,anyelement) _timescaledb_internal.finalize_agg_ffunc(internal,text,name,name,name[],bytea,anyelement) _timescaledb_internal.finalize_agg_sfunc(internal,text,name,name,name[],bytea,anyelement) diff --git a/tsl/test/sql/dist_ddl.sql b/tsl/test/sql/dist_ddl.sql index 726d7fe9d2e..82f1fc14abf 100644 --- a/tsl/test/sql/dist_ddl.sql +++ b/tsl/test/sql/dist_ddl.sql @@ -731,6 +731,7 @@ SELECT create_distributed_hypertable('hyper', 'time', 'device', 4, chunk_time_in INSERT INTO hyper SELECT t, ceil((random() * 5))::int, random() * 80 FROM generate_series('2019-01-01'::timestamptz, '2019-01-05'::timestamptz, '1 minute') as t; ANALYZE hyper; +DROP TABLE hyper; -- -- Ensure single query multi-statement command is blocked @@ -896,6 +897,107 @@ SELECT create_distributed_hypertable('drf_test', 'time'); SELECT is_distributed, replication_factor FROM timescaledb_information.hypertables WHERE hypertable_name = 'drf_test'; DROP TABLE drf_test; +-- test drop_stale_chunks() +-- + +-- test directly on a data node first +CREATE TABLE dist_test(time timestamptz NOT NULL, device int, temp float); +SELECT create_distributed_hypertable('dist_test', 'time', 'device', 3, replication_factor => 3); +INSERT INTO dist_test SELECT t, (abs(timestamp_hash(t::timestamp)) % 10) + 1, 0.10 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-08 1:00', '1 hour') t; +SELECT * from show_chunks('dist_test'); +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); + +\c :DATA_NODE_1 + +-- check call arguments when executed on data node +\set ON_ERROR_STOP 0 +SELECT _timescaledb_internal.drop_stale_chunks(NULL, NULL); +SELECT _timescaledb_internal.drop_stale_chunks('dn1', NULL); +\set ON_ERROR_STOP 1 + +-- direct call to all chunks other then 19, 21 +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; + +SET client_min_messages TO DEBUG1; +SELECT _timescaledb_internal.drop_stale_chunks(NULL, array[19, 21]::integer[]); +RESET client_min_messages; + +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; + +-- ensure that drop_stale_chunks() does not affect local chunks +CREATE TABLE local_test(time timestamptz NOT NULL, device int, temp float); +SELECT create_hypertable('local_test', 'time', 'device', 3); +INSERT INTO local_test SELECT t, (abs(timestamp_hash(t::timestamp)) % 10) + 1, 0.10 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-08 1:00', '1 hour') t; +SELECT * from show_chunks('local_test'); +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; + +SET client_min_messages TO DEBUG1; +SELECT _timescaledb_internal.drop_stale_chunks(NULL, array[19]::integer[]); +RESET client_min_messages; + +SELECT * from show_chunks('local_test'); +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; +DROP TABLE local_test; + +\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER; +DROP TABLE dist_test; + +-- test from access node +CREATE TABLE dist_test(time timestamptz NOT NULL, device int, temp float); +SELECT create_distributed_hypertable('dist_test', 'time', 'device', 3, replication_factor => 3); +INSERT INTO dist_test SELECT t, (abs(timestamp_hash(t::timestamp)) % 10) + 1, 0.10 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-08 1:00', '1 hour') t; +SELECT * from show_chunks('dist_test'); +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); + +-- check call arguments when executed on access node +\set ON_ERROR_STOP 0 +SELECT _timescaledb_internal.drop_stale_chunks( NULL, NULL); +SELECT _timescaledb_internal.drop_stale_chunks(NULL, array[1,2,3]); +\set ON_ERROR_STOP 1 + +-- create stale chunk by dropping them from access node +DROP FOREIGN TABLE _timescaledb_internal._dist_hyper_35_36_chunk; +DROP FOREIGN TABLE _timescaledb_internal._dist_hyper_35_37_chunk; + +SELECT * from show_chunks('dist_test'); +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); + +---- drop stale chunks 36, 37 on data nodes +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_1'); +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_2'); +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_3'); + +SELECT * from show_chunks('dist_test'); +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); + +-- test drop_stale_chunks() with compressed chunk +ALTER TABLE dist_test set (timescaledb.compress, timescaledb.compress_segmentby = 'device', timescaledb.compress_orderby = 'time'); +SELECT compress_chunk('_timescaledb_internal._dist_hyper_35_38_chunk'); + +\c :DATA_NODE_1 + +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; +SELECT * from show_chunks('dist_test'); + +\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER; + +DROP FOREIGN TABLE _timescaledb_internal._dist_hyper_35_38_chunk; + +SELECT * from show_chunks('dist_test'); +SELECT * FROM test.remote_exec(NULL, $$ SELECT * from show_chunks('dist_test'); $$); + +-- drop stale chunk 38 +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_1'); +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_2'); +SELECT _timescaledb_internal.drop_stale_chunks(:'DATA_NODE_3'); + +\c :DATA_NODE_1 +SELECT id, table_name FROM _timescaledb_catalog.chunk ORDER BY id, table_name; +SELECT * from show_chunks('dist_test'); +\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER; + +DROP TABLE dist_test; + -- cleanup \c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER; DROP DATABASE :DATA_NODE_1;