-- This test verfies a behavioir of maintenance daemon in multi-database environment -- It checks that distributed deadlock detection and 2PC transaction recovery should respect the citus.shared_pool_size_maintenance_quota. -- To do that, it created 100 databases and syntactically generates distributed transactions in various states there. SELECT $definition$ ALTER SYSTEM SET citus.recover_2pc_interval TO '-1'; ALTER SYSTEM SET citus.distributed_deadlock_detection_factor = '-1'; SELECT pg_reload_conf(); $definition$ AS turn_off_maintenance \gset SELECT $deinition$ ALTER SYSTEM SET citus.recover_2pc_interval TO '5s'; ALTER SYSTEM RESET citus.distributed_deadlock_detection_factor; SELECT pg_reload_conf(); $deinition$ AS turn_on_maintenance \gset SELECT $definition$ DO $do$ DECLARE index int; db_name text; current_port int; db_create_statement text; BEGIN SELECT setting::int FROM pg_settings WHERE name = 'port' INTO current_port; FOR index IN 1..100 LOOP SELECT format('db%s', index) INTO db_name; SELECT format('CREATE DATABASE %I', db_name) INTO db_create_statement; PERFORM dblink(format('dbname=regression host=localhost port=%s user=postgres', current_port), db_create_statement); PERFORM dblink(format('dbname=%s host=localhost port=%s user=postgres', db_name, current_port), 'CREATE EXTENSION citus;'); IF (SELECT groupid = 0 FROM pg_dist_node WHERE nodeport = current_port) THEN PERFORM dblink_exec(format('dbname=%s host=localhost port=%s user=postgres', db_name, current_port), format($add_workers$SELECT citus_add_node('localhost', %s);COMMIT;$add_workers$, nodeport)) FROM pg_dist_node WHERE groupid != 0 AND isactive AND noderole = 'primary'; END IF; END LOOP; END; $do$; $definition$ AS create_databases \gset -- Code reiles heavily on dblink for cross-db and cross-node queries CREATE EXTENSION IF NOT EXISTS dblink; -- Disable maintenance operations to prepare the environment :turn_off_maintenance \c - - - :worker_1_port :turn_off_maintenance \c - - - :worker_2_port :turn_off_maintenance -- Create databases \c - - - :worker_1_port :create_databases SELECT count(*) FROM pg_database WHERE datname LIKE 'db%'; \c - - - :worker_2_port :create_databases SELECT count(*) FROM pg_database WHERE datname LIKE 'db%'; \c - - - :master_port :create_databases SELECT count(*) FROM pg_database WHERE datname LIKE 'db%'; -- Generate distributed transactions \c - - - :master_port DO $do$ DECLARE index int; db_name text; transaction_to_abort_name text; transaction_to_commit_name text; transaction_to_be_forgotten text; coordinator_port int; BEGIN FOR index IN 1..100 LOOP SELECT format('db%s', index) INTO db_name; SELECT format('citus_0_1234_3_0_%s', oid) FROM pg_database WHERE datname = db_name INTO transaction_to_abort_name; SELECT format('citus_0_1234_4_0_%s', oid) FROM pg_database WHERE datname = db_name INTO transaction_to_commit_name; SELECT format('citus_0_should_be_forgotten_%s', oid) FROM pg_database WHERE datname = db_name INTO transaction_to_be_forgotten; SELECT setting::int FROM pg_settings WHERE name = 'port' INTO coordinator_port; -- Prepare transactions on workers PERFORM dblink_exec(format('dbname=%s host=localhost port=%s user=postgres', db_name, nodeport), format($worker_cmd$ BEGIN; CREATE TABLE should_abort (value int); PREPARE TRANSACTION '%s'; BEGIN; CREATE TABLE should_commit (value int); PREPARE TRANSACTION '%s'; $worker_cmd$, transaction_to_abort_name, transaction_to_commit_name)) FROM pg_dist_node WHERE groupid != 0 AND isactive AND noderole = 'primary'; -- Fill the pg_dist_transaction PERFORM dblink_exec(format('dbname=%s host=localhost port=%s user=postgres', db_name, coordinator_port), format($coordinator_cmd$ INSERT INTO pg_dist_transaction SELECT groupid, '%s' FROM pg_dist_node UNION ALL SELECT groupid, '%s' FROM pg_dist_node; $coordinator_cmd$, transaction_to_commit_name, transaction_to_be_forgotten)); END LOOP; END; $do$; -- Verify state before enabling maintenance \c - - - :master_port SELECT count(*) = 400 AS pg_dist_transaction_before_recovery_coordinator_test FROM pg_database, dblink(format('dbname=%s host=localhost port=%s user=postgres', datname, (SELECT setting::int FROM pg_settings WHERE name = 'port')), $statement$ SELECT groupid, gid FROM pg_dist_transaction WHERE gid LIKE 'citus_0_1234_4_0_%' OR gid LIKE 'citus_0_should_be_forgotten_%' $statement$) AS t(groupid integer, gid text) WHERE datname LIKE 'db%'; SELECT count(*) = 0 AS cached_connections_before_recovery_coordinator_test FROM pg_stat_activity WHERE state = 'idle' AND now() - backend_start > '5 seconds'::interval; \c - - - :worker_1_port SELECT count(*) = 100 AS pg_prepared_xacts_before_recover_worker_1_test FROM pg_prepared_xacts WHERE gid LIKE 'citus_0_1234_4_0_%' OR gid LIKE 'citus_0_should_be_forgotten_%'; SELECT count(*) = 0 AS cached_connections_before_recovery_worker_1_test FROM pg_stat_activity WHERE state = 'idle' AND now() - backend_start > '5 seconds'::interval; \c - - - :worker_2_port SELECT count(*) = 100 AS pg_prepared_xacts_before_recover_worker_2_test FROM pg_prepared_xacts WHERE gid LIKE 'citus_0_1234_4_0_%' OR gid LIKE 'citus_0_should_be_forgotten_%'; SELECT count(*) = 0 AS cached_connections_before_recovery_worker_2_test FROM pg_stat_activity WHERE state = 'idle' AND now() - backend_start > '5 seconds'::interval; -- Turn on the maintenance \c - - - :master_port :turn_on_maintenance \c - - - :worker_1_port :turn_on_maintenance \c - - - :worker_2_port :turn_on_maintenance \c - - - :master_port -- Let maintenance do it's work... DO $$ BEGIN FOR i IN 0 .. 300 LOOP IF i = 300 THEN RAISE 'Waited too long'; END IF; PERFORM pg_stat_clear_snapshot(); PERFORM * FROM pg_stat_activity WHERE application_name = 'Citus Maintenance Daemon'; IF (SELECT count(*) = 0 AS pg_dist_transaction_after_recovery_coordinator_test FROM pg_database, dblink(format('dbname=%s host=localhost port=%s user=postgres', datname, (SELECT setting::int FROM pg_settings WHERE name = 'port')), $statement$ SELECT groupid, gid FROM pg_dist_transaction WHERE gid LIKE 'citus_0_1234_4_0_%' OR gid LIKE 'citus_0_should_be_forgotten_%' $statement$) AS t(groupid integer, gid text) WHERE datname LIKE 'db%') THEN EXIT; END IF; PERFORM pg_sleep_for('1 SECOND'::interval); END LOOP; END $$; -- Verify maintenance result SELECT count(*) = 0 AS too_many_clients_test FROM regexp_split_to_table(pg_read_file('../log/postmaster.log'), E'\n') AS t(log_line) WHERE log_line LIKE '%sorry, too many clients already%'; SELECT count(*) = 0 AS cached_connections_after_recovery_coordinator_test FROM pg_stat_activity WHERE state = 'idle' AND now() - backend_start > '5 seconds'::interval; \c - - - :worker_1_port SELECT count(*) = 0 AS too_many_clients_test FROM regexp_split_to_table(pg_read_file('../log/postmaster.log'), E'\n') AS t(log_line) WHERE log_line LIKE '%sorry, too many clients already%'; SELECT count(*) = 0 AS pg_prepared_xacts_after_recover_worker_1_test FROM pg_prepared_xacts WHERE gid LIKE 'citus_0_1234_4_0_%' OR gid LIKE 'citus_0_should_be_forgotten_%'; SELECT count(*) = 0 AS cached_connections_after_recovery_worker_1_test FROM pg_stat_activity WHERE state = 'idle' AND now() - backend_start > '5 seconds'::interval; \c - - - :worker_2_port SELECT count(*) = 0 AS too_many_clients_test FROM regexp_split_to_table(pg_read_file('../log/postmaster.log'), E'\n') AS t(log_line) WHERE log_line LIKE '%sorry, too many clients already%'; SELECT count(*) = 0 AS pg_prepared_xacts_after_recover_worker_2_test FROM pg_prepared_xacts WHERE gid LIKE 'citus_0_1234_4_0_%' OR gid LIKE 'citus_0_should_be_forgotten_%'; SELECT count(*) = 0 AS cached_connections_after_recovery_worker_2_test FROM pg_stat_activity WHERE state = 'idle' AND now() - backend_start > '5 seconds'::interval; -- Cleanup \c - - - :master_port SELECT $definition$ ALTER SYSTEM RESET citus.recover_2pc_interval; ALTER SYSTEM RESET citus.distributed_deadlock_detection_factor; SELECT pg_reload_conf(); DO $do$ DECLARE index int; db_name text; current_port int; BEGIN SELECT setting::int FROM pg_settings WHERE name = 'port' INTO current_port; FOR index IN 1..100 LOOP SELECT format('db%s', index) INTO db_name; PERFORM dblink(format('dbname=%s host=localhost port=%s user=postgres', db_name, current_port), 'DROP EXTENSION citus;'); END LOOP; END; $do$; -- Dropping tables explicitly because ProcSignalBarrier prevents from using dblink DROP DATABASE db1 WITH (FORCE); DROP DATABASE db2 WITH (FORCE); DROP DATABASE db3 WITH (FORCE); DROP DATABASE db4 WITH (FORCE); DROP DATABASE db5 WITH (FORCE); DROP DATABASE db6 WITH (FORCE); DROP DATABASE db7 WITH (FORCE); DROP DATABASE db8 WITH (FORCE); DROP DATABASE db9 WITH (FORCE); DROP DATABASE db10 WITH (FORCE); DROP DATABASE db11 WITH (FORCE); DROP DATABASE db12 WITH (FORCE); DROP DATABASE db13 WITH (FORCE); DROP DATABASE db14 WITH (FORCE); DROP DATABASE db15 WITH (FORCE); DROP DATABASE db16 WITH (FORCE); DROP DATABASE db17 WITH (FORCE); DROP DATABASE db18 WITH (FORCE); DROP DATABASE db19 WITH (FORCE); DROP DATABASE db20 WITH (FORCE); DROP DATABASE db21 WITH (FORCE); DROP DATABASE db22 WITH (FORCE); DROP DATABASE db23 WITH (FORCE); DROP DATABASE db24 WITH (FORCE); DROP DATABASE db25 WITH (FORCE); DROP DATABASE db26 WITH (FORCE); DROP DATABASE db27 WITH (FORCE); DROP DATABASE db28 WITH (FORCE); DROP DATABASE db29 WITH (FORCE); DROP DATABASE db30 WITH (FORCE); DROP DATABASE db31 WITH (FORCE); DROP DATABASE db32 WITH (FORCE); DROP DATABASE db33 WITH (FORCE); DROP DATABASE db34 WITH (FORCE); DROP DATABASE db35 WITH (FORCE); DROP DATABASE db36 WITH (FORCE); DROP DATABASE db37 WITH (FORCE); DROP DATABASE db38 WITH (FORCE); DROP DATABASE db39 WITH (FORCE); DROP DATABASE db40 WITH (FORCE); DROP DATABASE db41 WITH (FORCE); DROP DATABASE db42 WITH (FORCE); DROP DATABASE db43 WITH (FORCE); DROP DATABASE db44 WITH (FORCE); DROP DATABASE db45 WITH (FORCE); DROP DATABASE db46 WITH (FORCE); DROP DATABASE db47 WITH (FORCE); DROP DATABASE db48 WITH (FORCE); DROP DATABASE db49 WITH (FORCE); DROP DATABASE db50 WITH (FORCE); DROP DATABASE db51 WITH (FORCE); DROP DATABASE db52 WITH (FORCE); DROP DATABASE db53 WITH (FORCE); DROP DATABASE db54 WITH (FORCE); DROP DATABASE db55 WITH (FORCE); DROP DATABASE db56 WITH (FORCE); DROP DATABASE db57 WITH (FORCE); DROP DATABASE db58 WITH (FORCE); DROP DATABASE db59 WITH (FORCE); DROP DATABASE db60 WITH (FORCE); DROP DATABASE db61 WITH (FORCE); DROP DATABASE db62 WITH (FORCE); DROP DATABASE db63 WITH (FORCE); DROP DATABASE db64 WITH (FORCE); DROP DATABASE db65 WITH (FORCE); DROP DATABASE db66 WITH (FORCE); DROP DATABASE db67 WITH (FORCE); DROP DATABASE db68 WITH (FORCE); DROP DATABASE db69 WITH (FORCE); DROP DATABASE db70 WITH (FORCE); DROP DATABASE db71 WITH (FORCE); DROP DATABASE db72 WITH (FORCE); DROP DATABASE db73 WITH (FORCE); DROP DATABASE db74 WITH (FORCE); DROP DATABASE db75 WITH (FORCE); DROP DATABASE db76 WITH (FORCE); DROP DATABASE db77 WITH (FORCE); DROP DATABASE db78 WITH (FORCE); DROP DATABASE db79 WITH (FORCE); DROP DATABASE db80 WITH (FORCE); DROP DATABASE db81 WITH (FORCE); DROP DATABASE db82 WITH (FORCE); DROP DATABASE db83 WITH (FORCE); DROP DATABASE db84 WITH (FORCE); DROP DATABASE db85 WITH (FORCE); DROP DATABASE db86 WITH (FORCE); DROP DATABASE db87 WITH (FORCE); DROP DATABASE db88 WITH (FORCE); DROP DATABASE db89 WITH (FORCE); DROP DATABASE db90 WITH (FORCE); DROP DATABASE db91 WITH (FORCE); DROP DATABASE db92 WITH (FORCE); DROP DATABASE db93 WITH (FORCE); DROP DATABASE db94 WITH (FORCE); DROP DATABASE db95 WITH (FORCE); DROP DATABASE db96 WITH (FORCE); DROP DATABASE db97 WITH (FORCE); DROP DATABASE db98 WITH (FORCE); DROP DATABASE db99 WITH (FORCE); DROP DATABASE db100 WITH (FORCE); SELECT count(*) FROM pg_database WHERE datname LIKE 'db%'; $definition$ AS cleanup \gset :cleanup \c - - - :worker_1_port :cleanup \c - - - :worker_2_port :cleanup \c - - - :master_port DROP EXTENSION IF EXISTS dblink;