-- Due to a race condition that happens in TransferShards() when the same shard id -- is used to create the same shard on a different worker node, need to call -- citus_cleanup_orphaned_resources() to clean up any orphaned resources before -- running the tests. -- -- See https://github.com/citusdata/citus/pull/7180#issuecomment-1706786615. SET client_min_messages TO WARNING; CALL citus_cleanup_orphaned_resources(); RESET client_min_messages; -- Returns true if all placement groups within given shard group are isolated. -- -- Not created in isolate_placement schema because it's dropped a few times during the test. CREATE OR REPLACE FUNCTION verify_placements_in_shard_group_isolated( qualified_table_name text, shard_group_index bigint) RETURNS boolean AS $func$ DECLARE v_result boolean; BEGIN SELECT bool_and(has_separate_node) INTO v_result FROM citus_shards JOIN ( SELECT shardids FROM public.get_enumerated_shard_groups(qualified_table_name) WHERE shardgroupindex = shard_group_index ) q ON (shardid = ANY(q.shardids)); RETURN v_result; END; $func$ LANGUAGE plpgsql; CREATE SCHEMA isolate_placement; SET search_path TO isolate_placement; -- test null input SELECT citus_internal_shard_group_set_needsseparatenode(0, NULL); SELECT citus_internal_shard_group_set_needsseparatenode(NULL, false); SET citus.shard_replication_factor TO 1; SET citus.next_shard_id TO 2000000; CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); -- test with user that doesn't have permission to execute the function SELECT citus_internal_shard_group_set_needsseparatenode(shardid, true) FROM pg_dist_shard WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass; DROP TABLE single_shard_1; CREATE ROLE test_user_isolate_placement WITH LOGIN; GRANT ALL ON SCHEMA isolate_placement TO test_user_isolate_placement; ALTER SYSTEM SET citus.enable_manual_metadata_changes_for_user TO 'test_user_isolate_placement'; SELECT pg_reload_conf(); SELECT pg_sleep(0.1); SET ROLE test_user_isolate_placement; -- test invalid shard id SELECT citus_internal_shard_group_set_needsseparatenode(0, true); -- test null needs_separate_node SELECT citus_internal_add_shard_metadata( relation_id=>0, shard_id=>0, storage_type=>'0', shard_min_value=>'0', shard_max_value=>'0', needs_separate_node=>null); RESET ROLE; REVOKE ALL ON SCHEMA isolate_placement FROM test_user_isolate_placement; DROP USER test_user_isolate_placement; ALTER SYSTEM RESET citus.enable_manual_metadata_changes_for_user; SELECT pg_reload_conf(); SELECT pg_sleep(0.1); SET search_path TO isolate_placement; SET citus.shard_replication_factor TO 1; SET citus.next_shard_id TO 2001000; CREATE USER mysuperuser superuser; SET ROLE mysuperuser; CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); CREATE USER regularuser; GRANT USAGE ON SCHEMA isolate_placement TO regularuser; ALTER SYSTEM SET citus.enable_manual_metadata_changes_for_user TO 'regularuser'; SELECT pg_reload_conf(); SELECT pg_sleep(0.1); SET ROLE regularuser; -- throws an error as the user is not the owner of the table SELECT citus_shard_property_set(shardid) FROM pg_dist_shard WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass; SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass; SELECT citus_internal_shard_group_set_needsseparatenode(shardid, true) FROM pg_dist_shard WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass; -- assign all tables to regularuser RESET ROLE; SELECT result FROM run_command_on_all_nodes($$ REASSIGN OWNED BY mysuperuser TO regularuser; $$); SET ROLE regularuser; SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass; SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.single_shard_1') $$) ORDER BY result; SELECT citus_internal_shard_group_set_needsseparatenode(shardid, false) FROM pg_dist_shard WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass; SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.single_shard_1') $$) ORDER BY result; SELECT citus_internal_shard_group_set_needsseparatenode(shardid, true) FROM pg_dist_shard WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass; DROP TABLE single_shard_1; RESET ROLE; REVOKE USAGE ON SCHEMA isolate_placement FROM regularuser; ALTER SYSTEM RESET citus.enable_manual_metadata_changes_for_user; SELECT pg_reload_conf(); SELECT pg_sleep(0.1); DROP ROLE regularuser, mysuperuser; SET search_path TO isolate_placement; SET citus.next_shard_id TO 2002000; SET citus.shard_count TO 32; SET citus.shard_replication_factor TO 1; SET client_min_messages TO WARNING; SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0); SET client_min_messages TO NOTICE; SET citus.shard_replication_factor TO 2; CREATE TABLE dist_1(a int); CREATE TABLE dist_2(a int); CREATE TABLE dist_3(a int); SELECT create_distributed_table('dist_1', 'a'); SELECT create_distributed_table('dist_2', 'a', colocate_with=>'dist_1'); SELECT create_distributed_table('dist_3', 'a', colocate_with=>'dist_1'); SET citus.shard_replication_factor TO 1; -- none of the placements have been marked as needsseparatenode yet SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; SELECT shardids[2] AS shardgroup_5_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 5 \gset SELECT citus_shard_property_set(:shardgroup_5_shardid, anti_affinity=>true); SELECT shardids[3] AS shardgroup_10_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 10 \gset SELECT citus_shard_property_set(:shardgroup_10_shardid, anti_affinity=>true); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; SELECT shardids[1] AS shardgroup_3_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 3 \gset SELECT citus_shard_property_set(:shardgroup_3_shardid, anti_affinity=>false); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; SELECT shardids[1] AS shardgroup_10_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 10 \gset SELECT citus_shard_property_set(:shardgroup_10_shardid, anti_affinity=>false); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; SELECT shardids[1] AS shardgroup_5_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 5 \gset SELECT citus_shard_property_set(:shardgroup_5_shardid, anti_affinity=>true); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; -- test metadata sync -- first, need to re-create them with shard_replication_factor = 1 because we will first remove worker_2 DROP TABLE dist_1, dist_2, dist_3; SELECT 1 FROM citus_remove_node('localhost', :worker_2_port); CREATE TABLE dist_1(a int); CREATE TABLE dist_2(a int); CREATE TABLE dist_3(a int); SELECT create_distributed_table('dist_1', 'a'); SELECT create_distributed_table('dist_2', 'a', colocate_with=>'dist_1'); SELECT create_distributed_table('dist_3', 'a', colocate_with=>'dist_1'); SELECT 1 FROM citus_add_node('localhost', :worker_2_port); SELECT shardids[1] AS shardgroup_5_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 5 \gset SELECT citus_shard_property_set(:shardgroup_5_shardid, anti_affinity=>true); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; CREATE TABLE dist_4(a int); SELECT create_distributed_table('dist_4', 'a', colocate_with=>'dist_1'); CREATE TABLE dist_4_concurrently(a int); SELECT create_distributed_table_concurrently('dist_4_concurrently', 'a', colocate_with=>'dist_1'); -- Placements of a new distributed table created within the same colocated -- group inherit needsseparatenode from the colocated placements too. SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; DROP TABLE dist_4, dist_4_concurrently; -- Returns source and target node ids that can be used to perform a -- shard transfer for one of the placements of given shard. CREATE OR REPLACE FUNCTION get_candidate_node_for_shard_transfer( p_shardid bigint) RETURNS TABLE (source_nodeid integer, target_nodeid integer) SET search_path TO 'pg_catalog, public' AS $func$ DECLARE v_source_nodeids integer[]; v_target_nodeid integer; BEGIN SELECT array_agg(nodeid) INTO v_source_nodeids FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) JOIN pg_dist_node USING (groupid) WHERE noderole = 'primary' AND shardid = p_shardid; IF v_source_nodeids IS NULL THEN RAISE EXCEPTION 'could not determine the source node of shard %', p_shardid; END IF; SELECT nodeid INTO v_target_nodeid FROM pg_dist_node WHERE isactive AND shouldhaveshards AND noderole='primary' AND nodeid NOT IN (SELECT unnest(v_source_nodeids)) LIMIT 1; IF v_target_nodeid IS NULL THEN RAISE EXCEPTION 'could not determine a node to transfer the placement to'; END IF; RETURN QUERY SELECT v_source_nodeids[1], v_target_nodeid; END; $func$ LANGUAGE plpgsql; SELECT shardids[1] AS shardgroup_15_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 15 \gset SELECT citus_move_shard_placement(:shardgroup_5_shardid, source_nodeid, target_nodeid, 'block_writes') FROM get_candidate_node_for_shard_transfer(:shardgroup_5_shardid); SELECT citus_move_shard_placement(:shardgroup_15_shardid, source_nodeid, target_nodeid, 'block_writes') FROM get_candidate_node_for_shard_transfer(:shardgroup_15_shardid); -- so that citus_copy_shard_placement works UPDATE pg_dist_partition SET repmodel = 'c' WHERE logicalrelid = 'isolate_placement.dist_1'::regclass; SELECT citus_copy_shard_placement(:shardgroup_5_shardid, source_nodeid, target_nodeid, 'block_writes') FROM get_candidate_node_for_shard_transfer(:shardgroup_5_shardid); SELECT citus_copy_shard_placement(:shardgroup_15_shardid, source_nodeid, target_nodeid, 'block_writes') FROM get_candidate_node_for_shard_transfer(:shardgroup_15_shardid); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; DROP TABLE dist_1, dist_2, dist_3; CREATE TABLE dist_1(a int); CREATE TABLE dist_2(a int); SELECT create_distributed_table('dist_1', 'a', shard_count=>3); SELECT create_distributed_table('dist_2', 'a', colocate_with=>'dist_1'); SELECT shardids[1] AS shardgroup_3_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 3 \gset SELECT citus_shard_property_set(:shardgroup_3_shardid, anti_affinity=>true); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; -- so that replicate_table_shards works UPDATE pg_dist_partition SET repmodel = 'c' WHERE logicalrelid = 'isolate_placement.dist_1'::regclass; SET client_min_messages TO WARNING; SELECT replicate_table_shards('isolate_placement.dist_1', shard_replication_factor=>2, shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; DROP TABLE dist_1, dist_2; CREATE TABLE dist_1(a int); CREATE TABLE dist_2(a int); SELECT create_distributed_table('dist_1', 'a'); SELECT create_distributed_table('dist_2', 'a', colocate_with=>'dist_1'); SELECT shardids[1] AS shardgroup_9_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 9 \gset SELECT citus_shard_property_set(:shardgroup_9_shardid, anti_affinity=>true); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; SELECT nodeid AS worker_1_node FROM pg_dist_node WHERE nodeport=:worker_1_port \gset SELECT nodeid AS worker_2_node FROM pg_dist_node WHERE nodeport=:worker_2_port \gset SELECT pg_catalog.citus_split_shard_by_split_points( :shardgroup_9_shardid, ARRAY[((shardminvalue::bigint + shardmaxvalue::bigint) / 2)::text], ARRAY[:worker_1_node, :worker_2_node], 'block_writes') FROM pg_dist_shard WHERE shardid = :shardgroup_9_shardid; -- We shouldn't see shard group 9 because shard-split operation doesn't -- preserve needsseparatenode flag when splitting the shard. SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; SELECT shardids[1] AS shardgroup_12_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 12 \gset SELECT citus_shard_property_set(:shardgroup_12_shardid, anti_affinity=>true); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; SELECT shardids[1] AS shardgroup_10_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 10 \gset SELECT pg_catalog.citus_split_shard_by_split_points( :shardgroup_10_shardid, ARRAY[((shardminvalue::bigint + shardmaxvalue::bigint) / 2)::text], ARRAY[:worker_1_node, :worker_2_node], 'block_writes') FROM pg_dist_shard WHERE shardid = :shardgroup_10_shardid; -- We should see old shard group 12 (now as 13 due to split -- of a prior shard) because it's not the one we splitted. SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; CREATE TABLE dist_3(a int); SELECT create_distributed_table('dist_3', 'a', colocate_with=>'none'); SELECT shardids[1] AS shardgroup_17_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_3') WHERE shardgroupindex = 17 \gset SELECT citus_shard_property_set(:shardgroup_17_shardid, anti_affinity=>true); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_3') $$) ORDER BY result; -- verify that shard key value 100 is stored on shard group 17 select get_shard_id_for_distribution_column('dist_3', 100) = :shardgroup_17_shardid; SELECT 1 FROM isolate_tenant_to_new_shard('dist_3', 100, shard_transfer_mode => 'block_writes'); -- We shouldn't see shard group 17 because isolate_tenant_to_new_shard doesn't -- preserve needsseparatenode flag when splitting the shard. SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_3') $$) ORDER BY result; SELECT shardids[1] AS shardgroup_18_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_3') WHERE shardgroupindex = 18 \gset SELECT citus_shard_property_set(:shardgroup_18_shardid, anti_affinity=>true); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_3') $$) ORDER BY result; -- verify that shard key value 1000 is _not_ stored on shard group 18 SELECT get_shard_id_for_distribution_column('dist_3', 1000) != :shardgroup_18_shardid; SELECT 1 FROM isolate_tenant_to_new_shard('dist_3', 1000, shard_transfer_mode => 'block_writes'); -- We should see shard group 18 (now as 20 due to split of a prior shard) -- because it's not the one we splitted. SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_3') $$) ORDER BY result; CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); SELECT shardids[1] AS shardgroup_1_shardid FROM public.get_enumerated_shard_groups('isolate_placement.single_shard_1') WHERE shardgroupindex = 1 \gset SELECT citus_shard_property_set(:shardgroup_1_shardid, anti_affinity=>true); -- noop SELECT citus_shard_property_set(:shardgroup_1_shardid, NULL); SELECT citus_shard_property_set(:shardgroup_1_shardid); CREATE TABLE single_shard_2(a int); SELECT create_distributed_table('single_shard_2', null, colocate_with=>'single_shard_1'); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.single_shard_1') $$) ORDER BY result; -- test invalid input SELECT citus_shard_property_set(NULL, anti_affinity=>true); SELECT citus_shard_property_set(0, anti_affinity=>true); SELECT citus_shard_property_set(NULL, anti_affinity=>false); SELECT citus_shard_property_set(0, anti_affinity=>false); -- we verify whether shard exists even if anti_affinity is not provided SELECT citus_shard_property_set(0, anti_affinity=>NULL); CREATE TABLE append_table (a int, b int); SELECT create_distributed_table('append_table', 'a', 'append'); SELECT 1 FROM master_create_empty_shard('append_table'); CREATE TYPE composite_key_type AS (f1 int, f2 text); CREATE TABLE range_table(key composite_key_type, value int); SELECT create_distributed_table('range_table', 'key', 'range'); CALL public.create_range_partitioned_shards('range_table', '{"(0,a)","(25,a)"}','{"(24,z)","(49,z)"}'); CREATE TABLE ref_table(a int); SELECT create_reference_table('ref_table'); CREATE TABLE local_table(a int); SELECT citus_add_local_table_to_metadata('local_table'); -- all should fail SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid = 'append_table'::regclass LIMIT 1; SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid = 'range_table'::regclass LIMIT 1; SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid = 'ref_table'::regclass LIMIT 1; SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid = 'local_table'::regclass LIMIT 1; SELECT citus_shard_property_set(shardid, anti_affinity=>false) FROM pg_dist_shard WHERE logicalrelid = 'append_table'::regclass LIMIT 1; SELECT citus_shard_property_set(shardid, anti_affinity=>false) FROM pg_dist_shard WHERE logicalrelid = 'range_table'::regclass LIMIT 1; SELECT citus_shard_property_set(shardid, anti_affinity=>false) FROM pg_dist_shard WHERE logicalrelid = 'ref_table'::regclass LIMIT 1; SELECT citus_shard_property_set(shardid, anti_affinity=>false) FROM pg_dist_shard WHERE logicalrelid = 'local_table'::regclass LIMIT 1; DROP TABLE range_table; DROP TYPE composite_key_type; SET client_min_messages TO WARNING; DROP SCHEMA isolate_placement CASCADE; CREATE SCHEMA isolate_placement; SET search_path TO isolate_placement; SET client_min_messages TO NOTICE; CREATE TABLE dist_1(a int); CREATE TABLE dist_2(a int); SELECT create_distributed_table('dist_1', 'a', shard_count=>4); SELECT create_distributed_table('dist_2', 'a', colocate_with=>'dist_1'); CREATE TABLE dist_non_colocated(a int); SELECT create_distributed_table('dist_non_colocated', 'a', shard_count=>4, colocate_with=>'none'); CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); CREATE TABLE single_shard_2(a int); SELECT create_distributed_table('single_shard_2', null, colocate_with=>'none'); CREATE TABLE append_table (a int, b int); SELECT create_distributed_table('append_table', 'a', 'append'); SELECT 1 FROM master_create_empty_shard('append_table'); CREATE TABLE range_table(a int, b int); SELECT create_distributed_table('range_table', 'a', 'range'); CALL public.create_range_partitioned_shards('range_table', '{"0","25"}','{"26","50"}'); CREATE TABLE reference_table_1(a int); SELECT create_reference_table('reference_table_1'); CREATE TABLE local_table_1(a int); SELECT citus_add_local_table_to_metadata('local_table_1'); SELECT shardids[1] AS shardgroup_1_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex = 1 \gset SELECT citus_shard_property_set(:shardgroup_1_shardid, anti_affinity=>true); SET client_min_messages TO WARNING; SELECT rebalance_table_shards(shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; CREATE TABLE reference_table_2(a int); SELECT create_reference_table('reference_table_2'); CREATE TABLE local_table_2(a int); SELECT citus_add_local_table_to_metadata('local_table_2'); -- make sure that we still have placements for both reference tables on all nodes SELECT COUNT(DISTINCT(groupid))=3 FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) WHERE logicalrelid = 'reference_table_1'::regclass; SELECT COUNT(DISTINCT(groupid))=3 FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) WHERE logicalrelid = 'reference_table_2'::regclass; -- sanity check for local tables SELECT groupid = 0 FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) WHERE logicalrelid = 'local_table_1'::regclass; SELECT groupid = 0 FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) WHERE logicalrelid = 'local_table_2'::regclass; CREATE TABLE dist_post_non_colocated(a int); SELECT create_distributed_table('dist_post_non_colocated', 'a', shard_count=>4, colocate_with=>'none'); CREATE TABLE dist_post_concurrently_non_colocated(a int); SELECT create_distributed_table_concurrently('dist_post_concurrently_non_colocated', 'a', shard_count=>4, colocate_with=>'none'); CREATE TABLE dist_post_colocated(a int); SELECT create_distributed_table('dist_post_colocated', 'a', colocate_with=>'dist_1'); CREATE TABLE dist_post_concurrently_colocated(a int); SELECT create_distributed_table_concurrently('dist_post_concurrently_colocated', 'a', colocate_with=>'dist_1'); CREATE TABLE single_shard_post(a int); SELECT create_distributed_table('single_shard_post', null, colocate_with=>'none'); CREATE TABLE append_table_post(a int, b int); SELECT create_distributed_table('append_table_post', 'a', 'append'); SELECT 1 FROM master_create_empty_shard('append_table_post'); CREATE TABLE range_table_post(a int, b int); SELECT create_distributed_table('range_table_post', 'a', 'range'); CALL public.create_range_partitioned_shards('range_table_post', '{"0","25"}','{"26","50"}'); SELECT result FROM run_command_on_all_nodes($$ SELECT * FROM public.get_colocated_shards_needisolatednode('isolate_placement.dist_1') $$) ORDER BY result; -- Make sure that the node that contains shard-group 1 of isolate_placement.dist_1 -- doesn't have any other placements. SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.dist_1', 1); SET client_min_messages TO ERROR; SELECT citus_drain_node('localhost', :worker_1_port, shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; SELECT citus_set_node_property('localhost', :worker_1_port, 'shouldhaveshards', true); -- drain node should have failed and the node should still have the same set of placements SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.dist_1', 1); SET client_min_messages TO ERROR; SELECT citus_drain_node('localhost', :worker_2_port, shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; SELECT citus_set_node_property('localhost', :worker_2_port, 'shouldhaveshards', true); -- drain node should have failed and the node should still have the same set of placements SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.dist_1', 1); CREATE TABLE dist_3(a int); SELECT create_distributed_table('dist_3', 'a', colocate_with=>'dist_1'); SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.dist_1', 1); SET citus.shard_replication_factor TO 2; CREATE TABLE dist_replicated(a int); -- fails as we only have one node that's not used to isolate a shard placement group SELECT create_distributed_table('dist_replicated', 'a', shard_count=>4, colocate_with=>'none'); SET citus.shard_replication_factor TO 1; CREATE TABLE dist_to_be_replicated(a int); SELECT create_distributed_table('dist_to_be_replicated', 'a', shard_count=>4, colocate_with=>'none'); UPDATE pg_dist_partition SET repmodel = 'c' WHERE logicalrelid = 'isolate_placement.dist_to_be_replicated'::regclass; SET client_min_messages TO WARNING; -- fails as we only have one node that's not used to isolate a shard placement group SELECT replicate_table_shards('isolate_placement.dist_to_be_replicated', shard_replication_factor=>2, shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; SELECT citus_set_node_property('localhost', :master_port, 'shouldhaveshards', true); SET client_min_messages TO WARNING; -- succeeds as now we have two nodes that are not used to isolate a shard placement group SELECT replicate_table_shards('isolate_placement.dist_to_be_replicated', shard_replication_factor=>2, shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.dist_1', 1); SELECT DISTINCT(table_name::regclass::text) FROM citus_shards JOIN pg_class ON (oid = table_name) WHERE relnamespace = 'isolate_placement'::regnamespace AND has_separate_node ORDER BY 1; SELECT bool_or(has_separate_node) = false FROM citus_shards JOIN ( SELECT unnest(shardids) shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_1') WHERE shardgroupindex != 1 ) shards_except_group_1 USING (shardid); DROP TABLE dist_to_be_replicated; SELECT citus_drain_node('localhost', :master_port, shard_transfer_mode=>'block_writes'); DROP TABLE dist_replicated; SET client_min_messages TO WARNING; DROP SCHEMA isolate_placement CASCADE; CREATE SCHEMA isolate_placement; SET search_path TO isolate_placement; SET client_min_messages TO NOTICE; CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); CREATE TABLE single_shard_2(a int); SELECT create_distributed_table('single_shard_2', null, colocate_with=>'none'); SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_1'::regclass, 'single_shard_2'::regclass); SET client_min_messages TO WARNING; SELECT rebalance_table_shards(shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; -- fails CREATE TABLE dist_1(a int); SELECT create_distributed_table('dist_1', 'a', shard_count=>4); CREATE TABLE single_shard_3(a int); SELECT create_distributed_table('single_shard_3', null, colocate_with=>'none'); CREATE TABLE append_table (a int, b int); SELECT create_distributed_table('append_table', 'a', 'append'); SELECT 1 FROM master_create_empty_shard('append_table'); CREATE TABLE range_table(a int, b int); SELECT create_distributed_table('range_table', 'a', 'range'); CALL public.create_range_partitioned_shards('range_table', '{"0","25"}','{"26","50"}'); -- succeeds CREATE TABLE reference_table_1(a int); SELECT create_reference_table('reference_table_1'); CREATE TABLE local_table_1(a int); SELECT citus_add_local_table_to_metadata('local_table_1'); CREATE TABLE single_shard_4(a int); SELECT create_distributed_table('single_shard_4', null, colocate_with=>'single_shard_1'); SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_1', 1); SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_2', 1); SET client_min_messages TO WARNING; DROP SCHEMA isolate_placement CASCADE; CREATE SCHEMA isolate_placement; SET search_path TO isolate_placement; SET client_min_messages TO NOTICE; CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); CREATE TABLE single_shard_2(a int); SELECT create_distributed_table('single_shard_2', null, colocate_with=>'none'); -- Make sure that we don't assume that a node is used to isolate a shard placement -- group just because it contains a single shard placement group. CREATE TABLE single_shard_3(a int); SELECT create_distributed_table('single_shard_3', null, colocate_with=>'none'); SET client_min_messages TO WARNING; DROP SCHEMA isolate_placement CASCADE; CREATE SCHEMA isolate_placement; SET search_path TO isolate_placement; SET client_min_messages TO NOTICE; SELECT citus_set_node_property('localhost', :master_port, 'shouldhaveshards', true); CREATE TABLE dist_1(a int); CREATE TABLE dist_2(a int); -- will replicate this CREATE TABLE dist_3(a int); SELECT create_distributed_table('dist_1', 'a', shard_count=>1); SELECT create_distributed_table('dist_2', 'a', shard_count=>1, colocate_with=>'none'); SELECT create_distributed_table('dist_3', 'a', shard_count=>1, colocate_with=>'none'); SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('dist_1'::regclass, 'dist_2'::regclass); SET client_min_messages TO WARNING; SELECT rebalance_table_shards(shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; -- so that replicate_table_shards works UPDATE pg_dist_partition SET repmodel = 'c' WHERE logicalrelid = 'isolate_placement.dist_2'::regclass; SET client_min_messages TO WARNING; -- succeeds but breaks the isolation requirement for either of dist_1 or dist_2 .. SELECT replicate_table_shards('isolate_placement.dist_2', shard_replication_factor=>2, shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; -- .. so check the xor of the isolation requirements for dist_1 and dist_2 SELECT (public.verify_placements_in_shard_group_isolated('isolate_placement.dist_1', 1) OR public.verify_placements_in_shard_group_isolated('isolate_placement.dist_2', 1)) = true AND (public.verify_placements_in_shard_group_isolated('isolate_placement.dist_1', 1) AND public.verify_placements_in_shard_group_isolated('isolate_placement.dist_2', 1)) = false; DROP TABLE dist_1, dist_2, dist_3; SELECT citus_drain_node('localhost', :master_port, shard_transfer_mode=>'block_writes'); CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); CREATE TABLE single_shard_2(a int); SELECT create_distributed_table('single_shard_2', null, colocate_with=>'none'); CREATE TABLE dist_1(a int); SELECT create_distributed_table('dist_1', 'a', shard_count=>4); SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_1'::regclass); SELECT groupid AS single_shard_1_group_id FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass \gset SET client_min_messages TO WARNING; SELECT rebalance_table_shards(shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_1', 1); -- show that we try to isolate placements where they were staying at the time rebalancer is invoked SELECT groupid = :single_shard_1_group_id FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass; DROP TABLE dist_1, single_shard_1, single_shard_2; SET citus.shard_replication_factor TO 2; CREATE TABLE dist_1(a int); CREATE TABLE dist_2(a int); SELECT citus_set_node_property('localhost', :master_port, 'shouldhaveshards', true); SELECT create_distributed_table('dist_1', 'a', shard_count=>1); SELECT create_distributed_table('dist_2', 'a', colocate_with=>'dist_1'); SET citus.shard_replication_factor TO 1; SELECT shardids[1] AS shardgroup_1_shardid FROM public.get_enumerated_shard_groups('isolate_placement.dist_2') WHERE shardgroupindex = 1 \gset SELECT citus_shard_property_set(:shardgroup_1_shardid, anti_affinity=>true); SET client_min_messages TO WARNING; SELECT rebalance_table_shards(shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.dist_1', 1) = true; DROP TABLE dist_1, dist_2; SELECT citus_drain_node('localhost', :master_port, shard_transfer_mode=>'block_writes'); CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); CREATE TABLE single_shard_2(a int); SELECT create_distributed_table('single_shard_2', null, colocate_with=>'none'); CREATE TABLE single_shard_3(a int); SELECT create_distributed_table('single_shard_3', null, colocate_with=>'none'); CREATE TABLE single_shard_4(a int); SELECT create_distributed_table('single_shard_4', null, colocate_with=>'none'); SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_1'::regclass); SET client_min_messages TO WARNING; SELECT rebalance_table_shards(shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_1', 1); SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_2'::regclass); SELECT citus_shard_property_set(shardid, anti_affinity=>false) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_1'::regclass); SET client_min_messages TO WARNING; SELECT rebalance_table_shards(shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_2', 1); SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_1', 1) = false; DROP TABLE single_shard_1, single_shard_2, single_shard_3, single_shard_4; CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); CREATE TABLE single_shard_2(a int); SELECT create_distributed_table('single_shard_2', null, colocate_with=>'none'); -- this would be placed on the same node as single_shard_1 CREATE TABLE single_shard_3(a int); SELECT create_distributed_table('single_shard_3', null, colocate_with=>'none'); DROP TABLE single_shard_2; SELECT shardid, nodeid INTO single_shard_3_shardid_nodeid FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) JOIN pg_dist_node USING (groupid) WHERE logicalrelid = 'isolate_placement.single_shard_3'::regclass AND noderole = 'primary'; SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_1'::regclass); SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_3'::regclass); -- tell rebalancer that single_shard_3 cannot be placed on the node where it is currently placed CREATE OR REPLACE FUNCTION test_shard_allowed_on_node(p_shardid bigint, p_nodeid int) RETURNS boolean AS $$ SELECT CASE WHEN (p_shardid = shardid and p_nodeid = nodeid) THEN false ELSE true END FROM single_shard_3_shardid_nodeid; $$ LANGUAGE sql; INSERT INTO pg_catalog.pg_dist_rebalance_strategy( name, default_strategy, shard_cost_function, node_capacity_function, shard_allowed_on_node_function, default_threshold, minimum_threshold, improvement_threshold ) VALUES ( 'test_isolate_placement', false, 'citus_shard_cost_1', 'citus_node_capacity_1', 'isolate_placement.test_shard_allowed_on_node', 0, 0, 0 ); SET client_min_messages TO WARNING; SELECT rebalance_table_shards(rebalance_strategy := 'test_isolate_placement', shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; -- test_shard_allowed_on_node() didn't cause rebalance_table_shards() to fail. -- -- Right now single_shard_1 & single_shard_3 are placed on the same node. And -- due to order we follow when assigning nodes to placement groups that need an -- isolated node, we will try placing single_shard_1 to the node where it is -- currently placed, and then we will try placing single_shard_3 to some other -- node (as its current node is already assigned to single_shard_1), not to the -- one we disallowed in test_shard_allowed_on_node(). SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_1', 1); SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_3', 1); DROP TABLE single_shard_3_shardid_nodeid; DELETE FROM pg_catalog.pg_dist_rebalance_strategy WHERE name='test_isolate_placement'; DROP TABLE single_shard_1, single_shard_3; CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); CREATE TABLE single_shard_2(a int); SELECT create_distributed_table('single_shard_2', null, colocate_with=>'none'); -- this would be placed on the same node as single_shard_1 CREATE TABLE single_shard_3(a int); SELECT create_distributed_table('single_shard_3', null, colocate_with=>'none'); DROP TABLE single_shard_2; SELECT shardid, nodeid INTO single_shard_3_shardid_nodeid FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) JOIN pg_dist_node USING (groupid) WHERE logicalrelid = 'isolate_placement.single_shard_3'::regclass AND noderole = 'primary'; SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_1'::regclass); SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_3'::regclass); -- Same test above but this time we tell rebalancer that single_shard_3 cannot be placed -- on any node except the one where it is currently placed. CREATE OR REPLACE FUNCTION test_shard_allowed_on_node(p_shardid bigint, p_nodeid int) RETURNS boolean AS $$ SELECT CASE WHEN (p_shardid = shardid and p_nodeid != nodeid) THEN false ELSE true END FROM single_shard_3_shardid_nodeid; $$ LANGUAGE sql; INSERT INTO pg_catalog.pg_dist_rebalance_strategy( name, default_strategy, shard_cost_function, node_capacity_function, shard_allowed_on_node_function, default_threshold, minimum_threshold, improvement_threshold ) VALUES ( 'test_isolate_placement', false, 'citus_shard_cost_1', 'citus_node_capacity_1', 'isolate_placement.test_shard_allowed_on_node', 0, 0, 0 ); SET client_min_messages TO ERROR; SELECT rebalance_table_shards(rebalance_strategy := 'test_isolate_placement', shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; -- This time, test_shard_allowed_on_node() caused rebalance_table_shards() to -- fail. -- -- Right now single_shard_1 & single_shard_3 are placed on the same node. And -- due to order we follow when assigning nodes to placement groups that need an -- isolated node, we will try placing single_shard_1 to the node where it is -- currently placed, and then we will try placing single_shard_3 to some other -- node (as its current node is already assigned to single_shard_1). However, -- test_shard_allowed_on_node() doesn't allow that. SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_1', 1) = false; SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_3', 1) = false; DROP TABLE single_shard_3_shardid_nodeid; DELETE FROM pg_catalog.pg_dist_rebalance_strategy WHERE name='test_isolate_placement'; DROP TABLE single_shard_1, single_shard_3; CREATE TABLE single_shard_1(a int); SELECT create_distributed_table('single_shard_1', null, colocate_with=>'none'); CREATE TABLE single_shard_2(a int); SELECT create_distributed_table('single_shard_2', null, colocate_with=>'none'); -- this would be placed on the same node as single_shard_1 CREATE TABLE single_shard_3(a int); SELECT create_distributed_table('single_shard_3', null, colocate_with=>'none'); DROP TABLE single_shard_2; SELECT shardid, nodeid INTO single_shard_1_shardid_nodeid FROM pg_dist_shard JOIN pg_dist_placement USING (shardid) JOIN pg_dist_node USING (groupid) WHERE logicalrelid = 'isolate_placement.single_shard_1'::regclass AND noderole = 'primary'; SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_1'::regclass); SELECT citus_shard_property_set(shardid, anti_affinity=>true) FROM pg_dist_shard WHERE logicalrelid IN ('single_shard_3'::regclass); -- tell rebalancer that single_shard_1 cannot be placed on the node where it is currently placed CREATE OR REPLACE FUNCTION test_shard_allowed_on_node(p_shardid bigint, p_nodeid int) RETURNS boolean AS $$ SELECT CASE WHEN (p_shardid = shardid and p_nodeid = nodeid) THEN false ELSE true END FROM single_shard_1_shardid_nodeid; $$ LANGUAGE sql; INSERT INTO pg_catalog.pg_dist_rebalance_strategy( name, default_strategy, shard_cost_function, node_capacity_function, shard_allowed_on_node_function, default_threshold, minimum_threshold, improvement_threshold ) VALUES ( 'test_isolate_placement', false, 'citus_shard_cost_1', 'citus_node_capacity_1', 'isolate_placement.test_shard_allowed_on_node', 0, 0, 0 ); SET client_min_messages TO WARNING; SELECT rebalance_table_shards(rebalance_strategy := 'test_isolate_placement', shard_transfer_mode=>'block_writes'); SET client_min_messages TO NOTICE; -- This time, test_shard_allowed_on_node() didn't cause rebalance_table_shards() to -- emit a warning. -- -- Right now single_shard_1 & single_shard_3 are placed on the same node. And -- due to order we follow when assigning nodes to placement groups that need an -- isolated node, we will try placing single_shard_1 to the node where it is -- currently placed but this is not possible due to test_shard_allowed_on_node(). -- But this is not a problem because we will take the specified rebalancer strategy -- into the account when assigning nodes to placements that need separate nodes and -- will try to place it to a different node. Then we will try placing single_shard_3 -- to the node where it is currently placed, and this is ok. SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_1', 1) = true; SELECT public.verify_placements_in_shard_group_isolated('isolate_placement.single_shard_3', 1) = true; DROP TABLE single_shard_1_shardid_nodeid; DELETE FROM pg_catalog.pg_dist_rebalance_strategy WHERE name='test_isolate_placement'; DROP TABLE single_shard_1, single_shard_3; SET client_min_messages TO WARNING; DROP SCHEMA isolate_placement CASCADE; DROP FUNCTION public.verify_placements_in_shard_group_isolated(text, bigint); SELECT citus_remove_node('localhost', :master_port);