timescaledb/tsl/test/expected/data_node.out
Dmitry Simonenko f12a361ef7 Add timeout argument to the ping_data_node()
This PR introduces a timeout argument and a new logic to the
timescale_internal.ping_data_node() function which allows
to handle io timeouts for nodes being unresponsive.

Fix #5312
2023-02-21 19:52:03 +02:00

2191 lines
93 KiB
Plaintext

-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER;
\unset ECHO
psql:include/remote_exec.sql:5: NOTICE: schema "test" already exists, skipping
\set DN_DBNAME_1 :TEST_DBNAME _1
\set DN_DBNAME_2 :TEST_DBNAME _2
\set DN_DBNAME_3 :TEST_DBNAME _3
\set DN_DBNAME_4 :TEST_DBNAME _4
\set DN_DBNAME_5 :TEST_DBNAME _5
\set DN_DBNAME_6 :TEST_DBNAME _6
-- View to see dimension partitions. Note RIGHT JOIN to see that
-- dimension partitions are cleaned up (deleted) properly.
CREATE VIEW hypertable_partitions AS
SELECT table_name, dimension_id, range_start, data_nodes
FROM _timescaledb_catalog.hypertable h
INNER JOIN _timescaledb_catalog.dimension d ON (d.hypertable_id = h.id)
RIGHT JOIN _timescaledb_catalog.dimension_partition dp ON (dp.dimension_id = d.id)
ORDER BY dimension_id, range_start;
GRANT SELECT ON hypertable_partitions TO :ROLE_1;
-- Add data nodes using TimescaleDB data_node management API.
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_1', host => 'localhost', database => :'DN_DBNAME_1');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_1 | db_data_node_1 | t | t | t
(1 row)
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_2', 'localhost', database => :'DN_DBNAME_2');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_2 | db_data_node_2 | t | t | t
(1 row)
\set ON_ERROR_STOP 0
-- Add again
SELECT * FROM add_data_node('data_node_2', host => 'localhost', database => :'DN_DBNAME_2');
ERROR: server "data_node_2" already exists
-- No host provided
SELECT * FROM add_data_node('data_node_99');
ERROR: function add_data_node(unknown) does not exist at character 15
SELECT * FROM add_data_node(NULL);
ERROR: function add_data_node(unknown) does not exist at character 15
-- Add NULL data_node
SELECT * FROM add_data_node(NULL, host => 'localhost');
ERROR: data node name cannot be NULL
SELECT * FROM add_data_node(NULL, NULL);
ERROR: a host needs to be specified
-- Test invalid port numbers
SELECT * FROM add_data_node('data_node_3', 'localhost',
port => 65536,
database => :'DN_DBNAME_3');
ERROR: invalid port number 65536
SELECT * FROM add_data_node('data_node_3', 'localhost',
port => 0,
database => :'DN_DBNAME_3');
ERROR: invalid port number 0
SELECT * FROM add_data_node('data_node_3', 'localhost',
port => -1,
database => :'DN_DBNAME_3');
ERROR: invalid port number -1
SELECT inet_server_port() as PGPORT \gset
-- Adding a data node via ADD SERVER is blocked
CREATE SERVER data_node_4 FOREIGN DATA WRAPPER timescaledb_fdw
OPTIONS (host 'localhost', port ':PGPORT', dbname :'DN_DBNAME_4');
ERROR: operation not supported for a TimescaleDB data node
-- Dropping a data node via DROP SERVER is also blocked
DROP SERVER data_node_1, data_node_2;
ERROR: operation not supported on a TimescaleDB data node
\set ON_ERROR_STOP 1
-- Should not generate error with if_not_exists option
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_2', host => 'localhost', database => :'DN_DBNAME_2',
if_not_exists => true);
NOTICE: data node "data_node_2" already exists, skipping
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_2 | db_data_node_2 | f | f | f
(1 row)
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_3', host => 'localhost', database => :'DN_DBNAME_3');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_3 | db_data_node_3 | t | t | t
(1 row)
-- Altering the host, dbname, and port should work via ALTER SERVER
BEGIN;
ALTER SERVER data_node_3 OPTIONS (SET host 'data_node_3', SET dbname 'new_db_name', SET port '9999');
SELECT srvname, srvoptions FROM pg_foreign_server WHERE srvname = 'data_node_3';
srvname | srvoptions
-------------+-------------------------------------------------
data_node_3 | {host=data_node_3,port=9999,dbname=new_db_name}
(1 row)
-- Altering the name should work
ALTER SERVER data_node_3 RENAME TO data_node_4;
SELECT srvname FROM pg_foreign_server WHERE srvname = 'data_node_4';
srvname
-------------
data_node_4
(1 row)
-- Revert name and options
ROLLBACK;
\set ON_ERROR_STOP 0
-- Should not be possible to set a version:
ALTER SERVER data_node_3 VERSION '2';
ERROR: version not supported
-- Should not be possible to set "available"
ALTER SERVER data_node_3 OPTIONS (SET available 'true');
ERROR: cannot set "available" using ALTER SERVER
\set ON_ERROR_STOP 1
-- Make sure changing server owner is allowed
ALTER SERVER data_node_1 OWNER TO CURRENT_USER;
-- List foreign data nodes
SELECT node_name FROM timescaledb_information.data_nodes ORDER BY node_name;
node_name
-------------
data_node_1
data_node_2
data_node_3
(3 rows)
SELECT * FROM delete_data_node('data_node_3');
delete_data_node
------------------
t
(1 row)
-- List data nodes
SELECT node_name FROM timescaledb_information.data_nodes ORDER BY node_name;
node_name
-------------
data_node_1
data_node_2
(2 rows)
\set ON_ERROR_STOP 0
-- Deleting a non-existing data node generates error
SELECT * FROM delete_data_node('data_node_3');
ERROR: server "data_node_3" does not exist
\set ON_ERROR_STOP 1
-- Deleting non-existing data node with "if_exists" set does not generate error
SELECT * FROM delete_data_node('data_node_3', if_exists => true);
NOTICE: data node "data_node_3" does not exist, skipping
delete_data_node
------------------
f
(1 row)
SELECT node_name FROM timescaledb_information.data_nodes ORDER BY node_name;
node_name
-------------
data_node_1
data_node_2
(2 rows)
SELECT * FROM delete_data_node('data_node_1');
delete_data_node
------------------
t
(1 row)
SELECT * FROM delete_data_node('data_node_2');
delete_data_node
------------------
t
(1 row)
-- No data nodes left
SELECT node_name FROM timescaledb_information.data_nodes ORDER BY node_name;
node_name
-----------
(0 rows)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
------------+--------------+-------------+------------
(0 rows)
-- Cleanup databases
RESET ROLE;
SET client_min_messages TO ERROR;
DROP DATABASE :DN_DBNAME_1;
DROP DATABASE :DN_DBNAME_2;
DROP DATABASE :DN_DBNAME_3;
SET client_min_messages TO INFO;
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_1', host => 'localhost', database => :'DN_DBNAME_1');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_1 | db_data_node_1 | t | t | t
(1 row)
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_2', host => 'localhost', database => :'DN_DBNAME_2');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_2 | db_data_node_2 | t | t | t
(1 row)
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_3', host => 'localhost', database => :'DN_DBNAME_3');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_3 | db_data_node_3 | t | t | t
(1 row)
-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes
GRANT CREATE ON SCHEMA public TO :ROLE_1;
SET ROLE :ROLE_1;
-- Create a distributed hypertable where no nodes can be selected
-- because there are no data nodes with the right permissions.
CREATE TABLE disttable(time timestamptz, device int, temp float);
\set ON_ERROR_STOP 0
\set VERBOSITY default
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device');
NOTICE: 3 of 3 data nodes not used by this hypertable due to lack of permissions
HINT: Grant USAGE on data nodes to attach them to a hypertable.
ERROR: no data nodes can be assigned to the hypertable
DETAIL: Data nodes exist, but none have USAGE privilege.
HINT: Grant USAGE on data nodes to attach them to the hypertable.
\set VERBOSITY terse
\set ON_ERROR_STOP 1
RESET ROLE;
-- Allow ROLE_1 to create distributed tables on these data nodes.
-- We'll test that data_node_3 is properly filtered when ROLE_1
-- creates a distributed hypertable without explicitly specifying
-- data_node_2.
GRANT USAGE
ON FOREIGN SERVER data_node_1, data_node_2
TO :ROLE_1;
-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes
GRANT CREATE ON SCHEMA public TO :ROLE_1;
SELECT node_name
FROM timescaledb_information.data_nodes
ORDER BY node_name;
node_name
-------------
data_node_1
data_node_2
data_node_3
(3 rows)
SELECT object_name, object_type, ARRAY_AGG(privilege_type)
FROM information_schema.role_usage_grants
WHERE object_schema NOT IN ('information_schema','pg_catalog')
AND object_type LIKE 'FOREIGN%'
GROUP BY object_schema, object_name, object_type
ORDER BY object_name, object_type;
object_name | object_type | array_agg
-----------------+----------------------+---------------
data_node_1 | FOREIGN SERVER | {USAGE,USAGE}
data_node_2 | FOREIGN SERVER | {USAGE,USAGE}
data_node_3 | FOREIGN SERVER | {USAGE}
timescaledb_fdw | FOREIGN DATA WRAPPER | {USAGE,USAGE}
(4 rows)
SET ROLE :ROLE_1;
-- Test that all data nodes are added to a hypertable and that the
-- slices in the device dimension equals the number of data nodes.
BEGIN;
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device');
NOTICE: 1 of 3 data nodes not used by this hypertable due to lack of permissions
NOTICE: adding not-null constraint to column "time"
hypertable_id | schema_name | table_name | created
---------------+-------------+------------+---------
1 | public | disttable | t
(1 row)
SELECT column_name, num_slices
FROM _timescaledb_catalog.dimension
WHERE column_name = 'device';
column_name | num_slices
-------------+------------
device | 2
(1 row)
-- Dimension partitions should be created
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
------------+--------------+----------------------+---------------
disttable | 2 | -9223372036854775808 | {data_node_1}
disttable | 2 | 1073741823 | {data_node_2}
(2 rows)
-- All data nodes with USAGE should be added.
SELECT hdn.node_name
FROM _timescaledb_catalog.hypertable_data_node hdn, _timescaledb_catalog.hypertable h
WHERE h.table_name = 'disttable' AND hdn.hypertable_id = h.id;
node_name
-------------
data_node_1
data_node_2
(2 rows)
ROLLBACK;
-- There should be an ERROR if we explicitly try to use a data node we
-- don't have permission to use.
\set ON_ERROR_STOP 0
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device',
data_nodes => '{ data_node_1, data_node_2, data_node_3 }');
ERROR: permission denied for foreign server data_node_3
\set ON_ERROR_STOP 1
RESET ROLE;
-- Now let ROLE_1 use data_node_3
GRANT USAGE
ON FOREIGN SERVER data_node_3
TO :ROLE_1;
-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes
GRANT CREATE ON SCHEMA public TO :ROLE_1;
SET ROLE :ROLE_1;
-- Now specify less slices than there are data nodes to generate a
-- warning
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device', 2);
NOTICE: adding not-null constraint to column "time"
WARNING: insufficient number of partitions for dimension "device"
hypertable_id | schema_name | table_name | created
---------------+-------------+------------+---------
2 | public | disttable | t
(1 row)
-- All data nodes should be added.
SELECT hdn.node_name
FROM _timescaledb_catalog.hypertable_data_node hdn, _timescaledb_catalog.hypertable h
WHERE h.table_name = 'disttable' AND hdn.hypertable_id = h.id;
node_name
-------------
data_node_1
data_node_2
data_node_3
(3 rows)
-- Ensure that replication factor allows to distinguish data node hypertables from regular hypertables
SELECT replication_factor FROM _timescaledb_catalog.hypertable WHERE table_name = 'disttable';
replication_factor
--------------------
1
(1 row)
SELECT * FROM test.remote_exec(NULL, $$ SELECT replication_factor
FROM _timescaledb_catalog.hypertable WHERE table_name = 'disttable'; $$);
NOTICE: [data_node_1]: SELECT replication_factor
FROM _timescaledb_catalog.hypertable WHERE table_name = 'disttable'
NOTICE: [data_node_1]:
replication_factor
------------------
-1
(1 row)
NOTICE: [data_node_2]: SELECT replication_factor
FROM _timescaledb_catalog.hypertable WHERE table_name = 'disttable'
NOTICE: [data_node_2]:
replication_factor
------------------
-1
(1 row)
NOTICE: [data_node_3]: SELECT replication_factor
FROM _timescaledb_catalog.hypertable WHERE table_name = 'disttable'
NOTICE: [data_node_3]:
replication_factor
------------------
-1
(1 row)
remote_exec
-------------
(1 row)
-- Create one chunk
INSERT INTO disttable VALUES ('2019-02-02 10:45', 1, 23.4);
-- Chunk mapping created
SELECT node_name FROM timescaledb_information.data_nodes ORDER BY node_name;
node_name
-------------
data_node_1
data_node_2
data_node_3
(3 rows)
DROP TABLE disttable;
-- data node mappings should be cleaned up
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-----------+--------------
(0 rows)
SELECT * FROM _timescaledb_catalog.chunk_data_node;
chunk_id | node_chunk_id | node_name
----------+---------------+-----------
(0 rows)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
------------+--------------+-------------+------------
(0 rows)
-- Now create tables as cluster user
CREATE TABLE disttable(time timestamptz, device int, temp float);
\set ON_ERROR_STOP 0
-- Attach data node should fail when called on a non-hypertable
SELECT * FROM attach_data_node('data_node_1', 'disttable');
ERROR: table "disttable" is not a hypertable
-- Test some bad create_hypertable() parameter values for distributed hypertables
-- Bad replication factor
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device', replication_factor => 0, data_nodes => '{ "data_node_2", "data_node_4" }');
ERROR: invalid replication factor
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device', replication_factor => 32768);
ERROR: replication factor too large for hypertable "disttable"
SELECT * FROM create_hypertable('disttable', 'time', replication_factor => -1);
ERROR: invalid replication factor
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device', replication_factor => -1);
ERROR: invalid replication factor
-- Non-existing data node
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device', replication_factor => 2, data_nodes => '{ "data_node_4" }');
ERROR: server "data_node_4" does not exist
\set ON_ERROR_STOP 1
-- Use a subset of data nodes and a replication factor of two so that
-- each chunk is associated with more than one data node. Set
-- number_partitions lower than number of servers to raise a warning
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device', number_partitions => 1, replication_factor => 2, data_nodes => '{ "data_node_2", "data_node_3" }');
NOTICE: adding not-null constraint to column "time"
WARNING: insufficient number of partitions for dimension "device"
hypertable_id | schema_name | table_name | created
---------------+-------------+------------+---------
3 | public | disttable | t
(1 row)
-- Create some chunks
INSERT INTO disttable VALUES
('2019-02-02 10:45', 1, 23.4),
('2019-05-23 10:45', 4, 14.9),
('2019-07-23 10:45', 8, 7.6);
SELECT * FROM test.show_subtables('disttable');
Child | Tablespace
---------------------------------------------+------------
_timescaledb_internal._dist_hyper_3_2_chunk |
_timescaledb_internal._dist_hyper_3_3_chunk |
_timescaledb_internal._dist_hyper_3_4_chunk |
(3 rows)
SELECT * FROM _timescaledb_catalog.chunk;
id | hypertable_id | schema_name | table_name | compressed_chunk_id | dropped | status | osm_chunk
----+---------------+-----------------------+-----------------------+---------------------+---------+--------+-----------
2 | 3 | _timescaledb_internal | _dist_hyper_3_2_chunk | | f | 0 | f
3 | 3 | _timescaledb_internal | _dist_hyper_3_3_chunk | | f | 0 | f
4 | 3 | _timescaledb_internal | _dist_hyper_3_4_chunk | | f | 0 | f
(3 rows)
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-------------+--------------
3 | 3 | data_node_2 | f
3 | 2 | data_node_3 | f
(2 rows)
SELECT * FROM _timescaledb_catalog.chunk_data_node;
chunk_id | node_chunk_id | node_name
----------+---------------+-------------
2 | 1 | data_node_2
2 | 1 | data_node_3
3 | 2 | data_node_2
3 | 2 | data_node_3
4 | 3 | data_node_2
4 | 3 | data_node_3
(6 rows)
-- Dropping a chunk should also clean up data node mappings
SELECT * FROM drop_chunks('disttable', older_than => '2019-05-22 17:18'::timestamptz);
drop_chunks
---------------------------------------------
_timescaledb_internal._dist_hyper_3_2_chunk
(1 row)
SELECT * FROM test.show_subtables('disttable');
Child | Tablespace
---------------------------------------------+------------
_timescaledb_internal._dist_hyper_3_3_chunk |
_timescaledb_internal._dist_hyper_3_4_chunk |
(2 rows)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
-----------------------+---------------------
_dist_hyper_3_3_chunk | data_node_2
_dist_hyper_3_4_chunk | data_node_2
(2 rows)
SELECT table_name, node_name
FROM _timescaledb_catalog.chunk c,
_timescaledb_catalog.chunk_data_node cdn
WHERE c.id = cdn.chunk_id;
table_name | node_name
-----------------------+-------------
_dist_hyper_3_3_chunk | data_node_2
_dist_hyper_3_3_chunk | data_node_3
_dist_hyper_3_4_chunk | data_node_2
_dist_hyper_3_4_chunk | data_node_3
(4 rows)
-- Setting the same data node should do nothing and return false
SELECT * FROM _timescaledb_internal.set_chunk_default_data_node('_timescaledb_internal._dist_hyper_3_3_chunk', 'data_node_3');
set_chunk_default_data_node
-----------------------------
t
(1 row)
-- Should update the default data node and return true
SELECT * FROM _timescaledb_internal.set_chunk_default_data_node('_timescaledb_internal._dist_hyper_3_3_chunk', 'data_node_2');
set_chunk_default_data_node
-----------------------------
t
(1 row)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
-----------------------+---------------------
_dist_hyper_3_3_chunk | data_node_2
_dist_hyper_3_4_chunk | data_node_2
(2 rows)
-- Reset the default data node
SELECT * FROM _timescaledb_internal.set_chunk_default_data_node('_timescaledb_internal._dist_hyper_3_3_chunk', 'data_node_3');
set_chunk_default_data_node
-----------------------------
t
(1 row)
\set ON_ERROR_STOP 0
-- Will fail because data_node_2 contains chunks
SET ROLE :ROLE_CLUSTER_SUPERUSER;
SELECT * FROM delete_data_node('data_node_2');
ERROR: data node "data_node_2" still holds data for distributed hypertable "disttable"
-- non-existing chunk
SELECT * FROM _timescaledb_internal.set_chunk_default_data_node('x_chunk', 'data_node_3');
ERROR: relation "x_chunk" does not exist at character 65
-- non-existing data node
SELECT * FROM _timescaledb_internal.set_chunk_default_data_node('_timescaledb_internal._dist_hyper_3_3_chunk', 'data_node_0000');
ERROR: server "data_node_0000" does not exist
-- data node exists but does not store the chunk
SELECT * FROM _timescaledb_internal.set_chunk_default_data_node('_timescaledb_internal._dist_hyper_3_3_chunk', 'data_node_1');
ERROR: chunk "_dist_hyper_3_3_chunk" does not exist on data node "data_node_1"
-- NULL try
SELECT * FROM _timescaledb_internal.set_chunk_default_data_node(NULL, 'data_node_3');
ERROR: invalid chunk: cannot be NULL
\set ON_ERROR_STOP 1
-- Deleting a data node removes the "foreign" chunk table(s) that
-- reference that data node as "primary" and should also remove the
-- hypertable_data_node and chunk_data_node mappings for that data
-- node. In the future we might want to fallback to a replica data
-- node for those chunks that have multiple data nodes so that the
-- chunk is not removed unnecessarily. We use force => true b/c
-- data_node_2 contains chunks. Dimension partitions should also be
-- updated to reflect the loss of the data node.
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
------------+--------------+----------------------+---------------------------
disttable | 6 | -9223372036854775808 | {data_node_2,data_node_3}
(1 row)
SELECT * FROM delete_data_node('data_node_2', force => true);
WARNING: distributed hypertable "disttable" is under-replicated
WARNING: insufficient number of data nodes for distributed hypertable "disttable"
delete_data_node
------------------
t
(1 row)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
------------+--------------+----------------------+---------------
disttable | 6 | -9223372036854775808 | {data_node_3}
(1 row)
SELECT * FROM test.show_subtables('disttable');
Child | Tablespace
---------------------------------------------+------------
_timescaledb_internal._dist_hyper_3_3_chunk |
_timescaledb_internal._dist_hyper_3_4_chunk |
(2 rows)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
-----------------------+---------------------
_dist_hyper_3_3_chunk | data_node_3
_dist_hyper_3_4_chunk | data_node_3
(2 rows)
SELECT * FROM _timescaledb_catalog.chunk;
id | hypertable_id | schema_name | table_name | compressed_chunk_id | dropped | status | osm_chunk
----+---------------+-----------------------+-----------------------+---------------------+---------+--------+-----------
3 | 3 | _timescaledb_internal | _dist_hyper_3_3_chunk | | f | 0 | f
4 | 3 | _timescaledb_internal | _dist_hyper_3_4_chunk | | f | 0 | f
(2 rows)
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-------------+--------------
3 | 2 | data_node_3 | f
(1 row)
SELECT * FROM _timescaledb_catalog.chunk_data_node;
chunk_id | node_chunk_id | node_name
----------+---------------+-------------
3 | 2 | data_node_3
4 | 3 | data_node_3
(2 rows)
\set ON_ERROR_STOP 0
-- can't delete b/c it's last data replica
SELECT * FROM delete_data_node('data_node_3', force => true);
ERROR: insufficient number of data nodes
\set ON_ERROR_STOP 1
-- Removing all data allows us to delete the data node by force, but
-- with WARNING that new data will be under-replicated
TRUNCATE disttable;
SELECT * FROM delete_data_node('data_node_3', force => true);
WARNING: insufficient number of data nodes for distributed hypertable "disttable"
delete_data_node
------------------
t
(1 row)
SELECT * FROM test.show_subtables('disttable');
Child | Tablespace
-------+------------
(0 rows)
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-----------+--------------
(0 rows)
SELECT * FROM _timescaledb_catalog.chunk_data_node;
chunk_id | node_chunk_id | node_name
----------+---------------+-----------
(0 rows)
SELECT * FROM _timescaledb_catalog.chunk;
id | hypertable_id | schema_name | table_name | compressed_chunk_id | dropped | status | osm_chunk
----+---------------+-------------+------------+---------------------+---------+--------+-----------
(0 rows)
-- Attach data node should now succeed
SET client_min_messages TO NOTICE;
SELECT * FROM attach_data_node('data_node_1', 'disttable');
hypertable_id | node_hypertable_id | node_name
---------------+--------------------+-------------
3 | 3 | data_node_1
(1 row)
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-------------+--------------
3 | 3 | data_node_1 | f
(1 row)
SELECT * FROM _timescaledb_catalog.chunk_data_node;
chunk_id | node_chunk_id | node_name
----------+---------------+-----------
(0 rows)
SELECT * FROM _timescaledb_internal.ping_data_node('data_node_1');
ping_data_node
----------------
t
(1 row)
-- Ensure timeout returned by argument
SELECT * FROM _timescaledb_internal.ping_data_node('data_node_1', interval '0s');
ping_data_node
----------------
f
(1 row)
SELECT * FROM _timescaledb_internal.ping_data_node('data_node_1', interval '3s');
ping_data_node
----------------
t
(1 row)
SELECT * FROM _timescaledb_internal.ping_data_node('data_node_1', interval '1 day');
ping_data_node
----------------
t
(1 row)
-- Create data node referencing postgres_fdw
RESET ROLE;
CREATE EXTENSION postgres_fdw;
CREATE SERVER pg_server_1 FOREIGN DATA WRAPPER postgres_fdw;
SET ROLE :ROLE_1;
CREATE TABLE standalone(time TIMESTAMPTZ, device INT, value FLOAT);
SELECT * FROM create_hypertable('standalone','time');
NOTICE: adding not-null constraint to column "time"
hypertable_id | schema_name | table_name | created
---------------+-------------+------------+---------
4 | public | standalone | t
(1 row)
\set ON_ERROR_STOP 0
-- Throw ERROR for non-existing data node
SELECT * FROM _timescaledb_internal.ping_data_node('data_node_123456789');
ERROR: server "data_node_123456789" does not exist
-- ERROR on NULL
SELECT * FROM _timescaledb_internal.ping_data_node(NULL);
ERROR: data node name cannot be NULL
-- ERROR when not passing TimescaleDB data node
SELECT * FROM _timescaledb_internal.ping_data_node('pg_data_node_1');
ERROR: server "pg_data_node_1" does not exist
-- ERROR on attaching to non-distributed hypertable
SELECT * FROM attach_data_node('data_node_1', 'standalone');
ERROR: hypertable "standalone" is not distributed
\set ON_ERROR_STOP 1
DROP TABLE standalone;
-- Some attach data node error cases
\set ON_ERROR_STOP 0
-- Invalid arguments
SELECT * FROM attach_data_node('data_node_1', NULL, true);
ERROR: hypertable cannot be NULL
SELECT * FROM attach_data_node(NULL, 'disttable', true);
ERROR: data node name cannot be NULL
-- Deleted data node
SELECT * FROM attach_data_node('data_node_2', 'disttable');
ERROR: server "data_node_2" does not exist
-- Attaching to an already attached data node without 'if_not_exists'
SELECT * FROM attach_data_node('data_node_1', 'disttable', false);
ERROR: data node "data_node_1" is already attached to hypertable "disttable"
-- Attaching a data node to another data node
\c :DN_DBNAME_1
SELECT * FROM attach_data_node('data_node_4', 'disttable');
ERROR: hypertable "disttable" is not distributed
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER;
SET ROLE :ROLE_1;
\set ON_ERROR_STOP 1
-- Attach if not exists
SELECT * FROM attach_data_node('data_node_1', 'disttable', true);
NOTICE: data node "data_node_1" is already attached to hypertable "disttable", skipping
hypertable_id | node_hypertable_id | node_name
---------------+--------------------+-------------
3 | 3 | data_node_1
(1 row)
-- Should repartition too. First show existing number of slices in
-- 'device' dimension
SELECT column_name, num_slices
FROM _timescaledb_catalog.dimension
WHERE num_slices IS NOT NULL
AND column_name = 'device';
column_name | num_slices
-------------+------------
device | 1
(1 row)
SET ROLE :ROLE_CLUSTER_SUPERUSER;
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_4', host => 'localhost', database => :'DN_DBNAME_4',
if_not_exists => true);
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_4 | db_data_node_4 | t | t | t
(1 row)
-- Now let ROLE_1 use data_node_4 since it owns this "disttable"
GRANT USAGE
ON FOREIGN SERVER data_node_4
TO :ROLE_1;
-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes
GRANT CREATE ON SCHEMA public TO :ROLE_1;
SELECT * FROM attach_data_node('data_node_4', 'disttable');
NOTICE: the number of partitions in dimension "device" was increased to 2
hypertable_id | node_hypertable_id | node_name
---------------+--------------------+-------------
3 | 1 | data_node_4
(1 row)
-- Recheck that ownership on data_node_4 is proper
SELECT * FROM test.remote_exec(NULL, $$ SELECT tablename, tableowner from pg_catalog.pg_tables where tablename = 'disttable'; $$);
NOTICE: [data_node_1]: SELECT tablename, tableowner from pg_catalog.pg_tables where tablename = 'disttable'
NOTICE: [data_node_1]:
tablename|tableowner
---------+-----------
disttable|test_role_1
(1 row)
NOTICE: [data_node_4]: SELECT tablename, tableowner from pg_catalog.pg_tables where tablename = 'disttable'
NOTICE: [data_node_4]:
tablename|tableowner
---------+-----------
disttable|test_role_1
(1 row)
remote_exec
-------------
(1 row)
-- Show updated number of slices in 'device' dimension.
SELECT column_name, num_slices
FROM _timescaledb_catalog.dimension
WHERE num_slices IS NOT NULL
AND column_name = 'device';
column_name | num_slices
-------------+------------
device | 2
(1 row)
-- Clean up
DROP TABLE disttable;
SELECT * FROM delete_data_node('data_node_4');
delete_data_node
------------------
t
(1 row)
SET ROLE :ROLE_1;
-- Creating a distributed hypertable without any servers should fail
CREATE TABLE disttable(time timestamptz, device int, temp float);
\set ON_ERROR_STOP 0
-- Creating a distributed hypertable without any data nodes should fail
SELECT * FROM create_distributed_hypertable('disttable', 'time', data_nodes => '{ }');
ERROR: no data nodes can be assigned to the hypertable
\set ON_ERROR_STOP 1
SET ROLE :ROLE_CLUSTER_SUPERUSER;
SELECT * FROM delete_data_node('data_node_1');
delete_data_node
------------------
t
(1 row)
SELECT node_name FROM timescaledb_information.data_nodes ORDER BY node_name;
node_name
-----------
(0 rows)
SELECT * FROM test.show_subtables('disttable');
Child | Tablespace
-------+------------
(0 rows)
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-----------+--------------
(0 rows)
SELECT * FROM _timescaledb_catalog.chunk_data_node;
chunk_id | node_chunk_id | node_name
----------+---------------+-----------
(0 rows)
SELECT * FROM _timescaledb_catalog.chunk;
id | hypertable_id | schema_name | table_name | compressed_chunk_id | dropped | status | osm_chunk
----+---------------+-------------+------------+---------------------+---------+--------+-----------
(0 rows)
\set ON_ERROR_STOP 0
-- No data nodes remain, so should fail
SELECT * FROM create_distributed_hypertable('disttable', 'time');
ERROR: no data nodes can be assigned to the hypertable
\set ON_ERROR_STOP 1
-- These data nodes have been deleted, so safe to remove their databases.
DROP DATABASE :DN_DBNAME_1;
DROP DATABASE :DN_DBNAME_2;
DROP DATABASE :DN_DBNAME_3;
DROP DATABASE :DN_DBNAME_4;
-- there should be no data nodes
SELECT node_name FROM timescaledb_information.data_nodes ORDER BY node_name;
node_name
-----------
(0 rows)
-- let's add some
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_1', host => 'localhost', database => :'DN_DBNAME_1');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_1 | db_data_node_1 | t | t | t
(1 row)
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_2', host => 'localhost', database => :'DN_DBNAME_2');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_2 | db_data_node_2 | t | t | t
(1 row)
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_3', host => 'localhost', database => :'DN_DBNAME_3');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_3 | db_data_node_3 | t | t | t
(1 row)
GRANT USAGE ON FOREIGN SERVER data_node_1, data_node_2, data_node_3 TO PUBLIC;
-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes
GRANT CREATE ON SCHEMA public TO :ROLE_1;
SET ROLE :ROLE_1;
DROP TABLE disttable;
CREATE TABLE disttable(time timestamptz, device int, temp float);
SELECT * FROM create_distributed_hypertable('disttable', 'time', 'device', 2,
replication_factor => 2,
data_nodes => '{"data_node_1", "data_node_2", "data_node_3"}');
NOTICE: adding not-null constraint to column "time"
WARNING: insufficient number of partitions for dimension "device"
hypertable_id | schema_name | table_name | created
---------------+-------------+------------+---------
5 | public | disttable | t
(1 row)
-- Create some chunks on all the data_nodes
INSERT INTO disttable VALUES
('2019-02-02 10:45', 1, 23.4),
('2019-05-23 10:45', 4, 14.9),
('2019-07-23 10:45', 8, 7.6);
SELECT * FROM test.show_subtables('disttable');
Child | Tablespace
---------------------------------------------+------------
_timescaledb_internal._dist_hyper_5_5_chunk |
_timescaledb_internal._dist_hyper_5_6_chunk |
_timescaledb_internal._dist_hyper_5_7_chunk |
(3 rows)
SELECT * FROM _timescaledb_catalog.chunk;
id | hypertable_id | schema_name | table_name | compressed_chunk_id | dropped | status | osm_chunk
----+---------------+-----------------------+-----------------------+---------------------+---------+--------+-----------
5 | 5 | _timescaledb_internal | _dist_hyper_5_5_chunk | | f | 0 | f
6 | 5 | _timescaledb_internal | _dist_hyper_5_6_chunk | | f | 0 | f
7 | 5 | _timescaledb_internal | _dist_hyper_5_7_chunk | | f | 0 | f
(3 rows)
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-------------+--------------
5 | 1 | data_node_1 | f
5 | 1 | data_node_2 | f
5 | 1 | data_node_3 | f
(3 rows)
SELECT * FROM _timescaledb_catalog.chunk_data_node;
chunk_id | node_chunk_id | node_name
----------+---------------+-------------
5 | 1 | data_node_1
5 | 1 | data_node_2
6 | 2 | data_node_2
6 | 1 | data_node_3
7 | 2 | data_node_1
7 | 3 | data_node_2
(6 rows)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
------------+--------------+----------------------+---------------------------
disttable | 9 | -9223372036854775808 | {data_node_1,data_node_2}
disttable | 9 | 1073741823 | {data_node_2,data_node_3}
(2 rows)
-- Add additional hypertable
CREATE TABLE disttable_2(time timestamptz, device int, temp float);
SELECT * FROM create_distributed_hypertable('disttable_2', 'time', 'device', 2, replication_factor => 2, data_nodes => '{"data_node_1", "data_node_2", "data_node_3"}');
NOTICE: adding not-null constraint to column "time"
WARNING: insufficient number of partitions for dimension "device"
hypertable_id | schema_name | table_name | created
---------------+-------------+-------------+---------
6 | public | disttable_2 | t
(1 row)
CREATE TABLE devices(device int, name text);
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-------------+--------------
5 | 1 | data_node_1 | f
5 | 1 | data_node_2 | f
5 | 1 | data_node_3 | f
6 | 2 | data_node_1 | f
6 | 2 | data_node_2 | f
6 | 2 | data_node_3 | f
(6 rows)
-- Block one data node for specific hypertable
SELECT * FROM timescaledb_experimental.block_new_chunks('data_node_1', 'disttable');
block_new_chunks
------------------
1
(1 row)
-- Block one data node for all hypertables
SELECT * FROM timescaledb_experimental.block_new_chunks('data_node_1');
NOTICE: new chunks already blocked on data node "data_node_1" for hypertable "disttable"
block_new_chunks
------------------
1
(1 row)
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-------------+--------------
5 | 1 | data_node_2 | f
5 | 1 | data_node_3 | f
6 | 2 | data_node_2 | f
6 | 2 | data_node_3 | f
5 | 1 | data_node_1 | t
6 | 2 | data_node_1 | t
(6 rows)
-- insert more data
INSERT INTO disttable VALUES
('2019-08-02 10:45', 1, 14.4),
('2019-08-15 10:45', 4, 14.9),
('2019-08-26 10:45', 8, 17.6);
-- no new chunks on data_node_1
SELECT * FROM _timescaledb_catalog.chunk_data_node;
chunk_id | node_chunk_id | node_name
----------+---------------+-------------
5 | 1 | data_node_1
5 | 1 | data_node_2
6 | 2 | data_node_2
6 | 1 | data_node_3
7 | 2 | data_node_1
7 | 3 | data_node_2
8 | 4 | data_node_2
8 | 2 | data_node_3
9 | 3 | data_node_3
9 | 5 | data_node_2
10 | 6 | data_node_2
10 | 4 | data_node_3
(12 rows)
-- some ERROR cases
\set ON_ERROR_STOP 0
-- Will error due to under-replication
SELECT * FROM timescaledb_experimental.block_new_chunks('data_node_2');
ERROR: insufficient number of data nodes for distributed hypertable "disttable"
-- can't block/allow non-existing data node
SELECT * FROM timescaledb_experimental.block_new_chunks('data_node_12345', 'disttable');
ERROR: server "data_node_12345" does not exist
SELECT * FROM timescaledb_experimental.allow_new_chunks('data_node_12345', 'disttable');
ERROR: server "data_node_12345" does not exist
-- NULL data node
SELECT * FROM timescaledb_experimental.block_new_chunks(NULL, 'disttable');
ERROR: data node name cannot be NULL
SELECT * FROM timescaledb_experimental.allow_new_chunks(NULL, 'disttable');
ERROR: data node name cannot be NULL
-- can't block/allow on non hypertable
SELECT * FROM timescaledb_experimental.block_new_chunks('data_node_1', 'devices');
ERROR: table "devices" is not a hypertable
SELECT * FROM timescaledb_experimental.allow_new_chunks('data_node_1', 'devices');
ERROR: table "devices" is not a hypertable
\set ON_ERROR_STOP 1
-- Force block all data nodes. Dimension partition information should
-- be updated to elide blocked data nodes
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
-------------+--------------+----------------------+---------------------------
disttable | 9 | -9223372036854775808 | {data_node_2,data_node_3}
disttable | 9 | 1073741823 | {data_node_3,data_node_2}
disttable_2 | 11 | -9223372036854775808 | {data_node_2,data_node_3}
disttable_2 | 11 | 1073741823 | {data_node_3,data_node_2}
(4 rows)
SELECT * FROM timescaledb_experimental.block_new_chunks('data_node_2', force => true);
WARNING: insufficient number of data nodes for distributed hypertable "disttable"
WARNING: insufficient number of data nodes for distributed hypertable "disttable_2"
block_new_chunks
------------------
2
(1 row)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
-------------+--------------+----------------------+---------------
disttable | 9 | -9223372036854775808 | {data_node_3}
disttable | 9 | 1073741823 | {data_node_3}
disttable_2 | 11 | -9223372036854775808 | {data_node_3}
disttable_2 | 11 | 1073741823 | {data_node_3}
(4 rows)
SELECT * FROM timescaledb_experimental.block_new_chunks('data_node_1', force => true);
NOTICE: new chunks already blocked on data node "data_node_1" for hypertable "disttable"
NOTICE: new chunks already blocked on data node "data_node_1" for hypertable "disttable_2"
block_new_chunks
------------------
0
(1 row)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
-------------+--------------+----------------------+---------------
disttable | 9 | -9223372036854775808 | {data_node_3}
disttable | 9 | 1073741823 | {data_node_3}
disttable_2 | 11 | -9223372036854775808 | {data_node_3}
disttable_2 | 11 | 1073741823 | {data_node_3}
(4 rows)
SELECT * FROM timescaledb_experimental.block_new_chunks('data_node_3', force => true);
WARNING: insufficient number of data nodes for distributed hypertable "disttable"
WARNING: insufficient number of data nodes for distributed hypertable "disttable_2"
block_new_chunks
------------------
2
(1 row)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
-------------+--------------+----------------------+------------
disttable | 9 | -9223372036854775808 |
disttable | 9 | 1073741823 |
disttable_2 | 11 | -9223372036854775808 |
disttable_2 | 11 | 1073741823 |
(4 rows)
-- All data nodes are blocked
SELECT * FROM _timescaledb_catalog.hypertable_data_node;
hypertable_id | node_hypertable_id | node_name | block_chunks
---------------+--------------------+-------------+--------------
5 | 1 | data_node_1 | t
6 | 2 | data_node_1 | t
5 | 1 | data_node_2 | t
6 | 2 | data_node_2 | t
5 | 1 | data_node_3 | t
6 | 2 | data_node_3 | t
(6 rows)
\set ON_ERROR_STOP 0
-- insert should fail b/c all data nodes are blocked
INSERT INTO disttable VALUES ('2019-11-02 02:45', 1, 13.3);
ERROR: insufficient number of data nodes
\set ON_ERROR_STOP 1
-- unblock data nodes for all hypertables
SELECT * FROM timescaledb_experimental.allow_new_chunks('data_node_1');
allow_new_chunks
------------------
2
(1 row)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
-------------+--------------+----------------------+---------------
disttable | 9 | -9223372036854775808 | {data_node_1}
disttable | 9 | 1073741823 | {data_node_1}
disttable_2 | 11 | -9223372036854775808 | {data_node_1}
disttable_2 | 11 | 1073741823 | {data_node_1}
(4 rows)
SELECT * FROM timescaledb_experimental.allow_new_chunks('data_node_2');
allow_new_chunks
------------------
2
(1 row)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
-------------+--------------+----------------------+---------------------------
disttable | 9 | -9223372036854775808 | {data_node_1,data_node_2}
disttable | 9 | 1073741823 | {data_node_2,data_node_1}
disttable_2 | 11 | -9223372036854775808 | {data_node_1,data_node_2}
disttable_2 | 11 | 1073741823 | {data_node_2,data_node_1}
(4 rows)
SELECT * FROM timescaledb_experimental.allow_new_chunks('data_node_3');
allow_new_chunks
------------------
2
(1 row)
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
-------------+--------------+----------------------+---------------------------
disttable | 9 | -9223372036854775808 | {data_node_1,data_node_2}
disttable | 9 | 1073741823 | {data_node_2,data_node_3}
disttable_2 | 11 | -9223372036854775808 | {data_node_1,data_node_2}
disttable_2 | 11 | 1073741823 | {data_node_2,data_node_3}
(4 rows)
SELECT table_name, node_name, block_chunks
FROM _timescaledb_catalog.hypertable_data_node dn,
_timescaledb_catalog.hypertable h
WHERE dn.hypertable_id = h.id
ORDER BY table_name;
table_name | node_name | block_chunks
-------------+-------------+--------------
disttable | data_node_1 | f
disttable | data_node_2 | f
disttable | data_node_3 | f
disttable_2 | data_node_1 | f
disttable_2 | data_node_2 | f
disttable_2 | data_node_3 | f
(6 rows)
-- Detach should work b/c disttable_2 has no data and more data nodes
-- than replication factor
SELECT * FROM detach_data_node('data_node_2', 'disttable_2');
detach_data_node
------------------
1
(1 row)
\set ON_ERROR_STOP 0
-- can't detach non-existing data node
SELECT * FROM detach_data_node('data_node_12345', 'disttable');
ERROR: server "data_node_12345" does not exist
-- NULL data node
SELECT * FROM detach_data_node(NULL, 'disttable');
ERROR: data node name cannot be NULL
-- Can't detach data node_1 b/c it contains data for disttable
SELECT * FROM detach_data_node('data_node_1');
ERROR: data node "data_node_1" still holds data for distributed hypertable "disttable"
-- can't detach already detached data node
SELECT * FROM detach_data_node('data_node_2', 'disttable_2');
ERROR: data node "data_node_2" is not attached to hypertable "disttable_2"
SELECT * FROM detach_data_node('data_node_2', 'disttable_2', if_attached => false);
ERROR: data node "data_node_2" is not attached to hypertable "disttable_2"
-- can't detach b/c of replication factor for disttable_2
SELECT * FROM detach_data_node('data_node_3', 'disttable_2');
ERROR: insufficient number of data nodes for distributed hypertable "disttable_2"
-- can't detach non hypertable
SELECT * FROM detach_data_node('data_node_3', 'devices');
ERROR: table "devices" is not a hypertable
\set ON_ERROR_STOP 1
-- do nothing if node is not attached
SELECT * FROM detach_data_node('data_node_2', 'disttable_2', if_attached => true);
NOTICE: data node "data_node_2" is not attached to hypertable "disttable_2", skipping
detach_data_node
------------------
0
(1 row)
-- force detach data node to become under-replicated for new data
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
-------------+--------------+----------------------+---------------------------
disttable | 9 | -9223372036854775808 | {data_node_1,data_node_2}
disttable | 9 | 1073741823 | {data_node_2,data_node_3}
disttable_2 | 11 | -9223372036854775808 | {data_node_1,data_node_3}
disttable_2 | 11 | 1073741823 | {data_node_3,data_node_1}
(4 rows)
SELECT * FROM detach_data_node('data_node_3', 'disttable_2', force => true);
WARNING: insufficient number of data nodes for distributed hypertable "disttable_2"
NOTICE: the number of partitions in dimension "device" of hypertable "disttable_2" was decreased to 1
detach_data_node
------------------
1
(1 row)
-- Dimension partitions should be updated to no longer list the data
-- node for the hypertable
SELECT * FROM hypertable_partitions;
table_name | dimension_id | range_start | data_nodes
-------------+--------------+----------------------+---------------------------
disttable | 9 | -9223372036854775808 | {data_node_1,data_node_2}
disttable | 9 | 1073741823 | {data_node_2,data_node_3}
disttable_2 | 11 | -9223372036854775808 | {data_node_1}
(3 rows)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
------------------------+---------------------
_dist_hyper_5_10_chunk | data_node_2
_dist_hyper_5_5_chunk | data_node_1
_dist_hyper_5_6_chunk | data_node_2
_dist_hyper_5_7_chunk | data_node_1
_dist_hyper_5_8_chunk | data_node_2
_dist_hyper_5_9_chunk | data_node_3
(6 rows)
-- force detach data node with data
SELECT * FROM detach_data_node('data_node_3', 'disttable', force => true);
WARNING: distributed hypertable "disttable" is under-replicated
detach_data_node
------------------
1
(1 row)
-- chunk and hypertable metadata should be deleted as well
SELECT * FROM _timescaledb_catalog.chunk_data_node;
chunk_id | node_chunk_id | node_name
----------+---------------+-------------
5 | 1 | data_node_1
5 | 1 | data_node_2
6 | 2 | data_node_2
7 | 2 | data_node_1
7 | 3 | data_node_2
8 | 4 | data_node_2
9 | 5 | data_node_2
10 | 6 | data_node_2
(8 rows)
SELECT table_name, node_name, block_chunks
FROM _timescaledb_catalog.hypertable_data_node dn,
_timescaledb_catalog.hypertable h
WHERE dn.hypertable_id = h.id
ORDER BY table_name;
table_name | node_name | block_chunks
-------------+-------------+--------------
disttable | data_node_1 | f
disttable | data_node_2 | f
disttable_2 | data_node_1 | f
(3 rows)
-- detached data_node_3 should not show up any more
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
------------------------+---------------------
_dist_hyper_5_10_chunk | data_node_2
_dist_hyper_5_5_chunk | data_node_1
_dist_hyper_5_6_chunk | data_node_2
_dist_hyper_5_7_chunk | data_node_1
_dist_hyper_5_8_chunk | data_node_2
_dist_hyper_5_9_chunk | data_node_2
(6 rows)
\set ON_ERROR_STOP 0
-- detaching data node with last data replica should ERROR even when forcing
SELECT * FROM detach_data_node('server_2', 'disttable', force => true);
ERROR: server "server_2" does not exist
\set ON_ERROR_STOP 1
-- drop all chunks
SELECT * FROM drop_chunks('disttable', older_than => '2200-01-01 00:00'::timestamptz);
drop_chunks
----------------------------------------------
_timescaledb_internal._dist_hyper_5_5_chunk
_timescaledb_internal._dist_hyper_5_6_chunk
_timescaledb_internal._dist_hyper_5_7_chunk
_timescaledb_internal._dist_hyper_5_8_chunk
_timescaledb_internal._dist_hyper_5_9_chunk
_timescaledb_internal._dist_hyper_5_10_chunk
(6 rows)
SELECT foreign_table_name, foreign_server_name
FROM information_schema.foreign_tables
ORDER BY foreign_table_name;
foreign_table_name | foreign_server_name
--------------------+---------------------
(0 rows)
SELECT * FROM detach_data_node('data_node_2', 'disttable', force => true);
WARNING: insufficient number of data nodes for distributed hypertable "disttable"
NOTICE: the number of partitions in dimension "device" of hypertable "disttable" was decreased to 1
detach_data_node
------------------
1
(1 row)
-- Let's add more data nodes
SET ROLE :ROLE_CLUSTER_SUPERUSER;
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_4', host => 'localhost', database => :'DN_DBNAME_4');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_4 | db_data_node_4 | t | t | t
(1 row)
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_5', host => 'localhost', database => :'DN_DBNAME_5');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_5 | db_data_node_5 | t | t | t
(1 row)
GRANT ALL ON FOREIGN SERVER data_node_4, data_node_5 TO PUBLIC;
-- though user on access node has required GRANTS, this will propagate GRANTS to the connected data nodes
GRANT CREATE ON SCHEMA public TO :ROLE_1;
-- Create table as super user
SET ROLE :ROLE_SUPERUSER;
CREATE TABLE disttable_3(time timestamptz, device int, temp float);
SELECT * FROM create_distributed_hypertable('disttable_3', 'time', replication_factor => 1, data_nodes => '{"data_node_4", "data_node_5"}');
NOTICE: adding not-null constraint to column "time"
hypertable_id | schema_name | table_name | created
---------------+-------------+-------------+---------
7 | public | disttable_3 | t
(1 row)
SET ROLE :ROLE_1;
CREATE TABLE disttable_4(time timestamptz, device int, temp float);
SELECT * FROM create_distributed_hypertable('disttable_4', 'time', replication_factor => 1, data_nodes => '{"data_node_4", "data_node_5"}');
NOTICE: adding not-null constraint to column "time"
hypertable_id | schema_name | table_name | created
---------------+-------------+-------------+---------
8 | public | disttable_4 | t
(1 row)
\set ON_ERROR_STOP 0
-- error due to missing permissions
SELECT * FROM detach_data_node('data_node_4', 'disttable_3');
ERROR: must be owner of hypertable "disttable_3"
SELECT * FROM timescaledb_experimental.block_new_chunks('data_node_4', 'disttable_3');
ERROR: must be owner of hypertable "disttable_3"
SELECT * FROM timescaledb_experimental.allow_new_chunks('data_node_4', 'disttable_3');
ERROR: must be owner of hypertable "disttable_3"
\set ON_ERROR_STOP 1
-- detach table(s) where user has permissions, otherwise show
-- NOTICE. Drop hypertables on data nodes in the process. Drop a
-- hypertable on a data node manually to ensure the command can handle
-- non-existing hypertables.
CREATE TABLE disttable_5(time timestamptz, device int, temp float);
SELECT * FROM create_distributed_hypertable('disttable_5', 'time', replication_factor => 1, data_nodes => '{"data_node_4", "data_node_5" }');
NOTICE: adding not-null constraint to column "time"
hypertable_id | schema_name | table_name | created
---------------+-------------+-------------+---------
9 | public | disttable_5 | t
(1 row)
SELECT * FROM test.remote_exec('{ data_node_4 }', $$ SELECT hypertable_name, owner FROM timescaledb_information.hypertables; $$);
NOTICE: [data_node_4]: SELECT hypertable_name, owner FROM timescaledb_information.hypertables
NOTICE: [data_node_4]:
hypertable_name|owner
---------------+-----------
disttable_3 |super_user
disttable_4 |test_role_1
disttable_5 |test_role_1
(3 rows)
remote_exec
-------------
(1 row)
CALL distributed_exec($$ DROP TABLE disttable_4 $$, '{ data_node_4 }');
RESET ROLE;
CALL distributed_exec(format('ALTER TABLE disttable_5 OWNER TO %s', :'ROLE_CLUSTER_SUPERUSER'), '{ data_node_4 }');
SET ROLE :ROLE_1;
-- test first detach without permission to drop the remote data
\set ON_ERROR_STOP 0
SELECT * FROM detach_data_node('data_node_4', drop_remote_data => true);
NOTICE: skipping hypertable "disttable_3" due to missing permissions
ERROR: [data_node_4]: must be owner of table disttable_5
\set ON_ERROR_STOP 1
-- detach should work with permissions
RESET ROLE;
CALL distributed_exec(format('ALTER TABLE disttable_5 OWNER TO %s', :'ROLE_1'), '{ data_node_4 }');
SET ROLE :ROLE_1;
SELECT * FROM detach_data_node('data_node_4', drop_remote_data => true);
NOTICE: skipping hypertable "disttable_3" due to missing permissions
detach_data_node
------------------
2
(1 row)
-- Hypertables user had permissions for should be dropped on data nodes
SELECT * FROM test.remote_exec('{ data_node_4 }', $$ SELECT hypertable_name, owner FROM timescaledb_information.hypertables; $$);
NOTICE: [data_node_4]: SELECT hypertable_name, owner FROM timescaledb_information.hypertables
NOTICE: [data_node_4]:
hypertable_name|owner
---------------+----------
disttable_3 |super_user
(1 row)
remote_exec
-------------
(1 row)
-- Cleanup
SET ROLE :ROLE_CLUSTER_SUPERUSER;
SELECT * FROM delete_data_node('data_node_1', force =>true);
WARNING: insufficient number of data nodes for distributed hypertable "disttable"
WARNING: insufficient number of data nodes for distributed hypertable "disttable_2"
delete_data_node
------------------
t
(1 row)
SELECT * FROM delete_data_node('data_node_2', force =>true);
delete_data_node
------------------
t
(1 row)
SELECT * FROM delete_data_node('data_node_3', force =>true);
delete_data_node
------------------
t
(1 row)
SET ROLE :ROLE_1;
\set ON_ERROR_STOP 0
-- Cannot delete a data node which is attached to a table that we don't
-- have owner permissions on
SELECT * FROM delete_data_node('data_node_4', force =>true);
ERROR: permission denied for hypertable "disttable_3"
SELECT * FROM delete_data_node('data_node_5', force =>true);
ERROR: permission denied for hypertable "disttable_3"
\set ON_ERROR_STOP 1
SET ROLE :ROLE_CLUSTER_SUPERUSER;
DROP TABLE disttable_3;
-- Now we should be able to delete the data nodes
SELECT * FROM delete_data_node('data_node_4', force =>true);
delete_data_node
------------------
t
(1 row)
SELECT * FROM delete_data_node('data_node_5', force =>true);
WARNING: insufficient number of data nodes for distributed hypertable "disttable_4"
WARNING: insufficient number of data nodes for distributed hypertable "disttable_5"
delete_data_node
------------------
t
(1 row)
\set ON_ERROR_STOP 0
-- Should fail because host has to be provided.
SELECT * FROM add_data_node('data_node_6');
ERROR: function add_data_node(unknown) does not exist at character 15
\set ON_ERROR_STOP 1
--
-- Test timescale extension version check during add_data_node()
-- and create_distributed_hypertable() calls.
--
-- Use mock extension and create basic function wrappers to
-- establish connection to a data node.
--
RESET ROLE;
DROP DATABASE :DN_DBNAME_1;
CREATE DATABASE :DN_DBNAME_1 OWNER :ROLE_1;
\c :DN_DBNAME_1
CREATE SCHEMA _timescaledb_internal;
GRANT ALL ON SCHEMA _timescaledb_internal TO :ROLE_1;
CREATE FUNCTION _timescaledb_internal.set_dist_id(uuid UUID)
RETURNS BOOL LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN true;
END
$BODY$;
CREATE FUNCTION _timescaledb_internal.set_peer_dist_id(uuid UUID)
RETURNS BOOL LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN true;
END
$BODY$;
CREATE FUNCTION _timescaledb_internal.validate_as_data_node()
RETURNS BOOL LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN true;
END
$BODY$;
CREATE EXTENSION timescaledb VERSION '0.0.0';
\c :TEST_DBNAME :ROLE_SUPERUSER;
\set ON_ERROR_STOP 0
SELECT * FROM add_data_node('data_node_1', 'localhost', database => :'DN_DBNAME_1',
bootstrap => false);
ERROR: remote PostgreSQL instance has an incompatible timescaledb extension version
-- Testing that it is not possible to use oneself as a data node. This
-- is not allowed since it would create a cycle.
--
-- We need to use the same owner for this connection as the extension
-- owner here to avoid triggering another error.
--
-- We cannot use default verbosity here for debugging since the
-- version number is printed in some of the notices.
SELECT * FROM add_data_node('data_node_99', host => 'localhost');
NOTICE: database "db_data_node" already exists on data node, skipping
NOTICE: extension "timescaledb" already exists on data node, skipping
ERROR: [data_node_99]: cannot add the current database as a data node to itself
\set ON_ERROR_STOP 1
-- Test adding bootstrapped data node where extension owner is different from user adding a data node
SET ROLE :ROLE_CLUSTER_SUPERUSER;
CREATE DATABASE :DN_DBNAME_6;
\c :DN_DBNAME_6
SET client_min_messages = ERROR;
-- Creating an extension as superuser
CREATE EXTENSION timescaledb;
RESET client_min_messages;
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER;
SET ROLE :ROLE_3;
-- Trying to add data node as non-superuser without GRANT on the
-- foreign data wrapper will fail.
\set ON_ERROR_STOP 0
SELECT * FROM add_data_node('data_node_6', host => 'localhost', database => :'DN_DBNAME_6');
ERROR: permission denied for foreign-data wrapper timescaledb_fdw
\set ON_ERROR_STOP 1
RESET ROLE;
GRANT USAGE ON FOREIGN DATA WRAPPER timescaledb_fdw TO :ROLE_3;
SET ROLE :ROLE_3;
\set ON_ERROR_STOP 0
-- ROLE_3 doesn't have a password in the passfile and has not way to
-- authenticate so adding a data node will still fail.
SELECT * FROM add_data_node('data_node_6', host => 'localhost', database => :'DN_DBNAME_6');
ERROR: could not connect to "data_node_6"
\set ON_ERROR_STOP 1
-- Providing the password on the command line should work
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_6', host => 'localhost', database => :'DN_DBNAME_6', password => :'ROLE_3_PASS');
NOTICE: database "db_data_node_6" already exists on data node, skipping
NOTICE: extension "timescaledb" already exists on data node, skipping
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_6 | db_data_node_6 | t | f | f
(1 row)
SELECT * FROM delete_data_node('data_node_6');
delete_data_node
------------------
t
(1 row)
RESET ROLE;
DROP DATABASE :DN_DBNAME_1;
DROP DATABASE :DN_DBNAME_2;
DROP DATABASE :DN_DBNAME_3;
DROP DATABASE :DN_DBNAME_4;
DROP DATABASE :DN_DBNAME_5;
DROP DATABASE :DN_DBNAME_6;
-----------------------------------------------
-- Test alter_data_node()
-----------------------------------------------
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_1', host => 'localhost', database => :'DN_DBNAME_1');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_1 | db_data_node_1 | t | t | t
(1 row)
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_2', host => 'localhost', database => :'DN_DBNAME_2');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_2 | db_data_node_2 | t | t | t
(1 row)
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('data_node_3', host => 'localhost', database => :'DN_DBNAME_3');
node_name | database | node_created | database_created | extension_created
-------------+----------------+--------------+------------------+-------------------
data_node_3 | db_data_node_3 | t | t | t
(1 row)
GRANT USAGE ON FOREIGN SERVER data_node_1, data_node_2, data_node_3 TO :ROLE_1;
GRANT CREATE ON SCHEMA public TO :ROLE_1;
SET ROLE :ROLE_1;
CREATE TABLE hyper1 (time timestamptz, location int, temp float);
CREATE TABLE hyper2 (LIKE hyper1);
CREATE TABLE hyper3 (LIKE hyper1);
CREATE TABLE hyper_1dim (LIKE hyper1);
SELECT create_distributed_hypertable('hyper1', 'time', 'location', replication_factor=>1);
NOTICE: adding not-null constraint to column "time"
create_distributed_hypertable
-------------------------------
(10,public,hyper1,t)
(1 row)
SELECT create_distributed_hypertable('hyper2', 'time', 'location', replication_factor=>2);
NOTICE: adding not-null constraint to column "time"
create_distributed_hypertable
-------------------------------
(11,public,hyper2,t)
(1 row)
SELECT create_distributed_hypertable('hyper3', 'time', 'location', replication_factor=>3);
NOTICE: adding not-null constraint to column "time"
create_distributed_hypertable
-------------------------------
(12,public,hyper3,t)
(1 row)
SELECT create_distributed_hypertable('hyper_1dim', 'time', chunk_time_interval=>interval '2 days', replication_factor=>3);
NOTICE: adding not-null constraint to column "time"
create_distributed_hypertable
-------------------------------
(13,public,hyper_1dim,t)
(1 row)
SELECT setseed(1);
setseed
---------
(1 row)
INSERT INTO hyper1
SELECT t, (abs(timestamp_hash(t::timestamp)) % 3) + 1, random() * 30
FROM generate_series('2022-01-01 00:00:00'::timestamptz, '2022-01-05 00:00:00', '1 h') t;
INSERT INTO hyper2 SELECT * FROM hyper1;
INSERT INTO hyper3 SELECT * FROM hyper1;
INSERT INTO hyper_1dim SELECT * FROM hyper1;
-- create view to see the data nodes and default data node of all
-- chunks
CREATE VIEW chunk_query_data_node AS
SELECT ch.hypertable_name, format('%I.%I', ch.chunk_schema, ch.chunk_name)::regclass AS chunk, ch.data_nodes, fs.srvname default_data_node
FROM timescaledb_information.chunks ch
INNER JOIN pg_foreign_table ft ON (format('%I.%I', ch.chunk_schema, ch.chunk_name)::regclass = ft.ftrelid)
INNER JOIN pg_foreign_server fs ON (ft.ftserver = fs.oid)
ORDER BY 1, 2;
SELECT * FROM chunk_query_data_node;
hypertable_name | chunk | data_nodes | default_data_node
-----------------+-----------------------------------------------+---------------------------------------+-------------------
hyper1 | _timescaledb_internal._dist_hyper_10_12_chunk | {data_node_1} | data_node_1
hyper1 | _timescaledb_internal._dist_hyper_10_13_chunk | {data_node_2} | data_node_2
hyper1 | _timescaledb_internal._dist_hyper_10_14_chunk | {data_node_3} | data_node_3
hyper2 | _timescaledb_internal._dist_hyper_11_15_chunk | {data_node_1,data_node_2} | data_node_1
hyper2 | _timescaledb_internal._dist_hyper_11_16_chunk | {data_node_2,data_node_3} | data_node_2
hyper2 | _timescaledb_internal._dist_hyper_11_17_chunk | {data_node_1,data_node_3} | data_node_3
hyper3 | _timescaledb_internal._dist_hyper_12_18_chunk | {data_node_1,data_node_2,data_node_3} | data_node_1
hyper3 | _timescaledb_internal._dist_hyper_12_19_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_20_chunk | {data_node_1,data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_21_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_22_chunk | {data_node_1,data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_23_chunk | {data_node_1,data_node_2,data_node_3} | data_node_1
(12 rows)
-- test alter_data_node permissions
\set ON_ERROR_STOP 0
-- must be owner to alter a data node
SELECT * FROM alter_data_node('data_node_1', available=>false);
ERROR: must be owner of foreign server data_node_1
SELECT * FROM alter_data_node('data_node_1', port=>8989);
ERROR: must be owner of foreign server data_node_1
\set ON_ERROR_STOP 1
-- query some data from all hypertables to show its working before
-- simulating the node being down
SELECT time, location FROM hyper1 ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
SELECT time, location FROM hyper2 ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
SELECT time, location FROM hyper3 ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
SELECT time, location FROM hyper_1dim ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
-- simulate a node being down by renaming the database for
-- data_node_1, but for that to work we need to reconnect the backend
-- to clear out the connection cache
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER;
ALTER DATABASE :DN_DBNAME_1 RENAME TO data_node_1_unavailable;
WARNING: you need to manually restart any running background workers after this command
\set ON_ERROR_STOP 0
SELECT time, location FROM hyper1 ORDER BY time LIMIT 1;
ERROR: could not connect to "data_node_1"
SELECT time, location FROM hyper2 ORDER BY time LIMIT 1;
ERROR: could not connect to "data_node_1"
SELECT time, location FROM hyper3 ORDER BY time LIMIT 1;
ERROR: could not connect to "data_node_1"
SELECT time, location FROM hyper_1dim ORDER BY time LIMIT 1;
ERROR: could not connect to "data_node_1"
\set ON_ERROR_STOP 1
-- alter the node as not available
SELECT * FROM alter_data_node('data_node_1', available=>false);
WARNING: could not switch data node on 1 chunks
node_name | host | port | database | available
-------------+-----------+-------+----------------+-----------
data_node_1 | localhost | 55432 | db_data_node_1 | f
(1 row)
-- the node that is not available for reads should no longer be
-- query data node for chunks, except for those that have no
-- alternative (i.e., the chunk only has one data node).
SELECT * FROM chunk_query_data_node;
hypertable_name | chunk | data_nodes | default_data_node
-----------------+-----------------------------------------------+---------------------------------------+-------------------
hyper1 | _timescaledb_internal._dist_hyper_10_12_chunk | {data_node_1} | data_node_1
hyper1 | _timescaledb_internal._dist_hyper_10_13_chunk | {data_node_2} | data_node_2
hyper1 | _timescaledb_internal._dist_hyper_10_14_chunk | {data_node_3} | data_node_3
hyper2 | _timescaledb_internal._dist_hyper_11_15_chunk | {data_node_1,data_node_2} | data_node_2
hyper2 | _timescaledb_internal._dist_hyper_11_16_chunk | {data_node_2,data_node_3} | data_node_2
hyper2 | _timescaledb_internal._dist_hyper_11_17_chunk | {data_node_1,data_node_3} | data_node_3
hyper3 | _timescaledb_internal._dist_hyper_12_18_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_19_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_20_chunk | {data_node_1,data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_21_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_22_chunk | {data_node_1,data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_23_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
(12 rows)
-- queries should work again, except on hyper1 which has no
-- replication
\set ON_ERROR_STOP 0
SELECT time, location FROM hyper1 ORDER BY time LIMIT 1;
ERROR: data node "data_node_1" is not available
\set ON_ERROR_STOP 1
SELECT time, location FROM hyper2 ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
SELECT time, location FROM hyper3 ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
SELECT time, location FROM hyper_1dim ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
-- inserts should continue to work and should go to the "live"
-- datanodes
INSERT INTO hyper3 VALUES ('2022-01-03 00:00:00', 1, 1);
INSERT INTO hyper3 VALUES ('2022-01-03 00:00:05', 1, 1);
INSERT INTO hyper_1dim VALUES ('2022-01-03 00:00:00', 1, 1);
INSERT INTO hyper_1dim VALUES ('2022-01-03 00:00:05', 1, 1);
-- Check that the metadata on the AN removes the association with
-- the "unavailable" DN for existing chunks that are being written into
-- above
SELECT * FROM chunk_query_data_node WHERE hypertable_name IN ('hyper3', 'hyper_1dim');
hypertable_name | chunk | data_nodes | default_data_node
-----------------+-----------------------------------------------+---------------------------------------+-------------------
hyper3 | _timescaledb_internal._dist_hyper_12_18_chunk | {data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_19_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_20_chunk | {data_node_1,data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_21_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_22_chunk | {data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_23_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
(6 rows)
-- Also, inserts should work if going to a new chunk
INSERT INTO hyper3 VALUES ('2022-01-10 00:00:00', 1, 1);
WARNING: insufficient number of data nodes
INSERT INTO hyper3 VALUES ('2022-01-10 00:00:05', 1, 1);
INSERT INTO hyper_1dim VALUES ('2022-01-10 00:00:00', 1, 1);
WARNING: insufficient number of data nodes
INSERT INTO hyper_1dim VALUES ('2022-01-10 00:00:05', 1, 1);
-- Also check that new chunks only use the "available" DNs
SELECT * FROM chunk_query_data_node WHERE hypertable_name IN ('hyper3', 'hyper_1dim');
hypertable_name | chunk | data_nodes | default_data_node
-----------------+-----------------------------------------------+---------------------------------------+-------------------
hyper3 | _timescaledb_internal._dist_hyper_12_18_chunk | {data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_19_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_20_chunk | {data_node_1,data_node_2,data_node_3} | data_node_3
hyper3 | _timescaledb_internal._dist_hyper_12_24_chunk | {data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_21_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_22_chunk | {data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_23_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_25_chunk | {data_node_2,data_node_3} | data_node_2
(8 rows)
-- Updates/Deletes should also work
UPDATE hyper3 SET temp = 10 WHERE time = '2022-01-03 00:00:00';
SELECT * FROM hyper3 WHERE time = '2022-01-03 00:00:00';
time | location | temp
------------------------------+----------+------
Mon Jan 03 00:00:00 2022 PST | 1 | 10
Mon Jan 03 00:00:00 2022 PST | 2 | 10
(2 rows)
UPDATE hyper3 SET temp = 10 WHERE time = '2022-01-03 00:00:05';
SELECT * FROM hyper3 WHERE time = '2022-01-03 00:00:05';
time | location | temp
------------------------------+----------+------
Mon Jan 03 00:00:05 2022 PST | 1 | 10
(1 row)
UPDATE hyper_1dim SET temp = 10 WHERE time = '2022-01-03 00:00:00';
SELECT * FROM hyper_1dim WHERE time = '2022-01-03 00:00:00';
time | location | temp
------------------------------+----------+------
Mon Jan 03 00:00:00 2022 PST | 2 | 10
Mon Jan 03 00:00:00 2022 PST | 1 | 10
(2 rows)
UPDATE hyper_1dim SET temp = 10 WHERE time = '2022-01-03 00:00:05';
SELECT * FROM hyper_1dim WHERE time = '2022-01-03 00:00:05';
time | location | temp
------------------------------+----------+------
Mon Jan 03 00:00:05 2022 PST | 1 | 10
(1 row)
DELETE FROM hyper3 WHERE time = '2022-01-03 00:00:00';
DELETE FROM hyper3 WHERE time = '2022-01-03 00:00:05';
SELECT * FROM hyper3 WHERE time = '2022-01-03 00:00:00';
time | location | temp
------+----------+------
(0 rows)
SELECT * FROM hyper3 WHERE time = '2022-01-03 00:00:05';
time | location | temp
------+----------+------
(0 rows)
DELETE FROM hyper_1dim WHERE time = '2022-01-03 00:00:00';
DELETE FROM hyper_1dim WHERE time = '2022-01-03 00:00:05';
SELECT * FROM hyper_1dim WHERE time = '2022-01-03 00:00:00';
time | location | temp
------+----------+------
(0 rows)
SELECT * FROM hyper_1dim WHERE time = '2022-01-03 00:00:05';
time | location | temp
------+----------+------
(0 rows)
-- Inserts directly into chunks using FDW should also work and should go to the
-- available DNs appropriately
INSERT INTO _timescaledb_internal._dist_hyper_12_24_chunk VALUES ('2022-01-11 00:00:00', 1, 1);
INSERT INTO _timescaledb_internal._dist_hyper_13_25_chunk VALUES ('2022-01-11 00:00:00', 1, 1);
SELECT * FROM test.remote_exec(ARRAY['data_node_2', 'data_node_3'], $$ SELECT * FROM _timescaledb_internal._dist_hyper_12_24_chunk WHERE time = '2022-01-11 00:00:00'; $$);
NOTICE: [data_node_2]: SELECT * FROM _timescaledb_internal._dist_hyper_12_24_chunk WHERE time = '2022-01-11 00:00:00'
NOTICE: [data_node_2]:
time |location|temp
----------------------------+--------+----
Tue Jan 11 00:00:00 2022 PST| 1| 1
(1 row)
NOTICE: [data_node_3]: SELECT * FROM _timescaledb_internal._dist_hyper_12_24_chunk WHERE time = '2022-01-11 00:00:00'
NOTICE: [data_node_3]:
time |location|temp
----------------------------+--------+----
Tue Jan 11 00:00:00 2022 PST| 1| 1
(1 row)
remote_exec
-------------
(1 row)
SELECT * FROM test.remote_exec(ARRAY['data_node_2', 'data_node_3'], $$ SELECT * FROM _timescaledb_internal._dist_hyper_13_25_chunk WHERE time = '2022-01-11 00:00:00'; $$);
NOTICE: [data_node_2]: SELECT * FROM _timescaledb_internal._dist_hyper_13_25_chunk WHERE time = '2022-01-11 00:00:00'
NOTICE: [data_node_2]:
time |location|temp
----------------------------+--------+----
Tue Jan 11 00:00:00 2022 PST| 1| 1
(1 row)
NOTICE: [data_node_3]: SELECT * FROM _timescaledb_internal._dist_hyper_13_25_chunk WHERE time = '2022-01-11 00:00:00'
NOTICE: [data_node_3]:
time |location|temp
----------------------------+--------+----
Tue Jan 11 00:00:00 2022 PST| 1| 1
(1 row)
remote_exec
-------------
(1 row)
SELECT * FROM chunk_query_data_node WHERE hypertable_name IN ('hyper3', 'hyper_1dim');
hypertable_name | chunk | data_nodes | default_data_node
-----------------+-----------------------------------------------+---------------------------------------+-------------------
hyper3 | _timescaledb_internal._dist_hyper_12_18_chunk | {data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_19_chunk | {data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_20_chunk | {data_node_1,data_node_2,data_node_3} | data_node_3
hyper3 | _timescaledb_internal._dist_hyper_12_24_chunk | {data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_21_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_22_chunk | {data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_23_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_25_chunk | {data_node_2,data_node_3} | data_node_2
(8 rows)
SELECT hypertable_name, chunk_name, data_nodes FROM timescaledb_information.chunks
WHERE hypertable_name IN ('hyper3', 'hyper_1dim')
AND range_start::timestamptz <= '2022-01-10 00:00:00'
AND range_end::timestamptz > '2022-01-10 00:00:00'
ORDER BY 1, 2;
hypertable_name | chunk_name | data_nodes
-----------------+-------------------------+---------------------------
hyper3 | _dist_hyper_12_24_chunk | {data_node_2,data_node_3}
hyper_1dim | _dist_hyper_13_25_chunk | {data_node_2,data_node_3}
(2 rows)
-- DDL should error out even if one DN is unavailable
\set ON_ERROR_STOP 0
ALTER TABLE hyper3 ADD COLUMN temp2 int;
ERROR: some data nodes are not available for DDL commands
ALTER TABLE hyper_1dim ADD COLUMN temp2 int;
ERROR: some data nodes are not available for DDL commands
\set ON_ERROR_STOP 1
-- Mark all DNs unavailable. Metadata should still retain last DN but all
-- activity should fail
SELECT * FROM alter_data_node('data_node_2', available=>false);
WARNING: could not switch data node on 2 chunks
node_name | host | port | database | available
-------------+-----------+-------+----------------+-----------
data_node_2 | localhost | 55432 | db_data_node_2 | f
(1 row)
SELECT * FROM alter_data_node('data_node_3', available=>false);
WARNING: could not switch data node on 11 chunks
node_name | host | port | database | available
-------------+-----------+-------+----------------+-----------
data_node_3 | localhost | 55432 | db_data_node_3 | f
(1 row)
\set ON_ERROR_STOP 0
INSERT INTO hyper3 VALUES ('2022-01-10 00:00:00', 1, 1);
ERROR: insufficient number of available data nodes
INSERT INTO hyper_1dim VALUES ('2022-01-10 00:00:00', 1, 1);
ERROR: insufficient number of available data nodes
UPDATE hyper3 SET temp = 10 WHERE time = '2022-01-03 00:00:00';
ERROR: insufficient number of available data nodes
UPDATE hyper_1dim SET temp = 10 WHERE time = '2022-01-03 00:00:00';
ERROR: insufficient number of available data nodes
DELETE FROM hyper3 WHERE time = '2022-01-03 00:00:00';
ERROR: insufficient number of available data nodes
DELETE FROM hyper_1dim WHERE time = '2022-01-03 00:00:00';
ERROR: insufficient number of available data nodes
SELECT count(*) FROM hyper3;
ERROR: data node "data_node_3" is not available
SELECT count(*) FROM hyper_1dim;
ERROR: data node "data_node_3" is not available
ALTER TABLE hyper3 ADD COLUMN temp2 int;
ERROR: some data nodes are not available for DDL commands
ALTER TABLE hyper_1dim ADD COLUMN temp2 int;
ERROR: some data nodes are not available for DDL commands
\set ON_ERROR_STOP 1
-- re-enable the data node and the chunks should "switch back" to
-- using the data node. However, only the chunks for which the node is
-- "primary" should switch to using the data node for queries
ALTER DATABASE data_node_1_unavailable RENAME TO :DN_DBNAME_1;
WARNING: you need to manually restart any running background workers after this command
SELECT * FROM alter_data_node('data_node_1', available=>true);
WARNING: insufficient number of data nodes
WARNING: insufficient number of data nodes
node_name | host | port | database | available
-------------+-----------+-------+----------------+-----------
data_node_1 | localhost | 55432 | db_data_node_1 | t
(1 row)
SELECT * FROM alter_data_node('data_node_2', available=>true);
WARNING: insufficient number of data nodes
WARNING: insufficient number of data nodes
WARNING: insufficient number of data nodes
WARNING: insufficient number of data nodes
node_name | host | port | database | available
-------------+-----------+-------+----------------+-----------
data_node_2 | localhost | 55432 | db_data_node_2 | t
(1 row)
SELECT * FROM alter_data_node('data_node_3', available=>true);
node_name | host | port | database | available
-------------+-----------+-------+----------------+-----------
data_node_3 | localhost | 55432 | db_data_node_3 | t
(1 row)
SELECT * FROM chunk_query_data_node;
hypertable_name | chunk | data_nodes | default_data_node
-----------------+-----------------------------------------------+---------------------------------------+-------------------
hyper1 | _timescaledb_internal._dist_hyper_10_12_chunk | {data_node_1} | data_node_1
hyper1 | _timescaledb_internal._dist_hyper_10_13_chunk | {data_node_2} | data_node_2
hyper1 | _timescaledb_internal._dist_hyper_10_14_chunk | {data_node_3} | data_node_3
hyper2 | _timescaledb_internal._dist_hyper_11_15_chunk | {data_node_1,data_node_2} | data_node_1
hyper2 | _timescaledb_internal._dist_hyper_11_16_chunk | {data_node_2,data_node_3} | data_node_2
hyper2 | _timescaledb_internal._dist_hyper_11_17_chunk | {data_node_1,data_node_3} | data_node_3
hyper3 | _timescaledb_internal._dist_hyper_12_18_chunk | {data_node_2,data_node_3} | data_node_3
hyper3 | _timescaledb_internal._dist_hyper_12_19_chunk | {data_node_2,data_node_3} | data_node_2
hyper3 | _timescaledb_internal._dist_hyper_12_20_chunk | {data_node_1,data_node_2,data_node_3} | data_node_3
hyper3 | _timescaledb_internal._dist_hyper_12_24_chunk | {data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_21_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_22_chunk | {data_node_2,data_node_3} | data_node_3
hyper_1dim | _timescaledb_internal._dist_hyper_13_23_chunk | {data_node_1,data_node_2,data_node_3} | data_node_2
hyper_1dim | _timescaledb_internal._dist_hyper_13_25_chunk | {data_node_2,data_node_3} | data_node_3
(14 rows)
--queries should work again on all tables
SELECT time, location FROM hyper1 ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
SELECT time, location FROM hyper2 ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
SELECT time, location FROM hyper3 ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
SELECT time, location FROM hyper_1dim ORDER BY time LIMIT 1;
time | location
------------------------------+----------
Sat Jan 01 00:00:00 2022 PST | 1
(1 row)
-- DDL should also work again
ALTER TABLE hyper3 ADD COLUMN temp2 int;
ALTER TABLE hyper_1dim ADD COLUMN temp2 int;
-- save old port so that we can restore connectivity after we test
-- changing the connection information for the data node
WITH options AS (
SELECT unnest(options) opt
FROM timescaledb_information.data_nodes
WHERE node_name = 'data_node_1'
)
SELECT split_part(opt, '=', 2) AS old_port
FROM options WHERE opt LIKE 'port%' \gset
-- also test altering host, port and database
SELECT node_name, options FROM timescaledb_information.data_nodes order by node_name;
node_name | options
-------------+------------------------------------------------------------------
data_node_1 | {host=localhost,port=55432,dbname=db_data_node_1,available=true}
data_node_2 | {host=localhost,port=55432,dbname=db_data_node_2,available=true}
data_node_3 | {host=localhost,port=55432,dbname=db_data_node_3,available=true}
(3 rows)
SELECT * FROM alter_data_node('data_node_1', available=>true, host=>'foo.bar', port=>8989, database=>'new_db');
node_name | host | port | database | available
-------------+---------+------+----------+-----------
data_node_1 | foo.bar | 8989 | new_db | t
(1 row)
SELECT node_name, options FROM timescaledb_information.data_nodes order by node_name;
node_name | options
-------------+------------------------------------------------------------------
data_node_1 | {host=foo.bar,port=8989,dbname=new_db,available=true}
data_node_2 | {host=localhost,port=55432,dbname=db_data_node_2,available=true}
data_node_3 | {host=localhost,port=55432,dbname=db_data_node_3,available=true}
(3 rows)
-- just show current options:
SELECT * FROM alter_data_node('data_node_1');
node_name | host | port | database | available
-------------+---------+------+----------+-----------
data_node_1 | foo.bar | 8989 | new_db | t
(1 row)
DROP TABLE hyper1;
DROP TABLE hyper2;
DROP TABLE hyper3;
DROP TABLE hyper_1dim;
\set ON_ERROR_STOP 0
-- test some error cases
SELECT * FROM alter_data_node(NULL);
ERROR: data node name cannot be NULL
SELECT * FROM alter_data_node('does_not_exist');
ERROR: server "does_not_exist" does not exist
SELECT * FROM alter_data_node('data_node_1', port=>89000);
ERROR: invalid port number 89000
-- cannot delete data node with "drop_database" since configuration is wrong
SELECT delete_data_node('data_node_1', drop_database=>true);
ERROR: could not connect to data node "data_node_1"
\set ON_ERROR_STOP 1
-- restore configuration for data_node_1
SELECT * FROM alter_data_node('data_node_1', host=>'localhost', port=>:old_port, database=>:'DN_DBNAME_1');
node_name | host | port | database | available
-------------+-----------+-------+----------------+-----------
data_node_1 | localhost | 55432 | db_data_node_1 | t
(1 row)
SELECT node_name, options FROM timescaledb_information.data_nodes order by node_name;
node_name | options
-------------+------------------------------------------------------------------
data_node_1 | {host=localhost,port=55432,dbname=db_data_node_1,available=true}
data_node_2 | {host=localhost,port=55432,dbname=db_data_node_2,available=true}
data_node_3 | {host=localhost,port=55432,dbname=db_data_node_3,available=true}
(3 rows)
DROP VIEW chunk_query_data_node;
-- create new session to clear out connection cache
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER;
SELECT delete_data_node('data_node_1', drop_database=>true);
delete_data_node
------------------
t
(1 row)
SELECT delete_data_node('data_node_2', drop_database=>true);
delete_data_node
------------------
t
(1 row)
SELECT delete_data_node('data_node_3', drop_database=>true);
delete_data_node
------------------
t
(1 row)