From ce5e529c5bd9f19e3a23f92f7832f361ae361c48 Mon Sep 17 00:00:00 2001 From: Muhammad Usama Date: Wed, 9 Nov 2022 12:43:25 +0500 Subject: [PATCH] [New-Feature] Dynamic spare process management This feature allows selecting between static and dynamic process management modes. Static process management is the same as the existing behavior of Pgpool-II, where it spawns all child processes at startup. The new Dynamic mode keeps track of idle processes and forks or kills processes to keep this number within the specified boundaries. Four new settings, process_management_mode, process_management_strategy, min_spare_children, and max_spare_children are added to configure the process management behavior, while process_management_strategy allows selecting between three possible scaling-down strategies. The first version of the patch was shared by "zhoujianshen at highgo.com" and reworked by me Discussion: https://www.pgpool.net/pipermail/pgpool-hackers/2022-September/004189.html Reviewed by: Bo Peng and Tatsuo Ishii --- doc/src/sgml/connection-settings.sgml | 159 +++++++++++++ doc/src/sgml/runtime.sgml | 33 +++ src/auth/pool_auth.c | 3 +- src/config/pool_config_variables.c | 68 +++++- src/context/pool_process_context.c | 25 ++- src/include/main/pool_internal_comms.h | 1 - src/include/pcp/libpcp_ext.h | 8 +- src/include/pool.h | 1 + src/include/pool_config.h | 20 +- src/include/protocol/pool_connection_pool.h | 1 + src/main/pgpool_main.c | 236 +++++++++++++++++++- src/protocol/child.c | 31 ++- src/protocol/pool_connection_pool.c | 15 +- src/sample/pgpool.conf.sample-stream | 38 +++- src/tools/pcp/pcp_frontend_client.c | 4 +- src/utils/pool_process_reporting.c | 25 ++- src/watchdog/wd_json_data.c | 18 +- 17 files changed, 648 insertions(+), 38 deletions(-) diff --git a/doc/src/sgml/connection-settings.sgml b/doc/src/sgml/connection-settings.sgml index 78a2540b5..189c86de3 100644 --- a/doc/src/sgml/connection-settings.sgml +++ b/doc/src/sgml/connection-settings.sgml @@ -457,6 +457,165 @@ + + Process Management + + + + + + process_management_mode (enum) + + process_management_mode configuration parameter + + + + + Specify the idle process management method for + Pgpool-II child processes. + Valid options: + + Possible Process Management Modes + + + + + Static + + All children are pre-forked at startup. + + + + + Dynamic + child processes are spawned on demand. + number of idle child processes at any time depends on + min_spare_children and max_spare_children + + + + + +
+
+ + Default is Static, that is compatible with pre V4.4. + + + + process_management_mode is not available prior to + Pgpool-II V4.4. + + +
+
+ + + process_management_strategy (enum) + + process_management_strategy configuration parameter + + + + + Specify the process management strategy to satisfy spare (idle) processes count + Valid options: + + Possible Process Management Strategies + + + + + Lazy + With this strategy the scale-down is performed gradually + and only gets triggered when excessive spare processes count + remains high for more than 5 mins + + + + + Gentle + With this strategy the scale-down is performed gradually + and only gets triggered when excessive spare processes count + remains high for more than 2 mins + + + + + Aggressive + With this strategy the scale-down is performed aggressively + and gets triggered more frequently in case of higher spare processes. + This mode uses faster and slightly less smart process selection criteria + to identify the child processes that can be serviced to satisfy + max_spare_children + + + + + +
+
+ + Default is Gentle. + + + process_management_mode is not available prior to + Pgpool-II V4.4. + + +
+
+ + + min_spare_children (integer) + + min_spare_children configuration parameter + + + + + Specify the minimum number of spare (idle) child processes to keep. + If the idle process count falls below min_spare_children, + Pgpool-II will spawn new child processes unless it hits the total allowed child process ceiling (num_init_children) + Default value is 5. + + + This parameter can be changed by reloading + the Pgpool-II configurations. + + + This parameter is only applicable for dynamic process management mode. + + + + + + max_spare_children (integer) + + max_spare_children configuration parameter + + + + + Specify the maximum number of spare (idle) child processes to keep. + If the idle process count increases from max_spare_children, Pgpool-II will kill the excessive child processes. + Default value is 10. + + + This parameter can be changed by reloading + the Pgpool-II configurations. + + + This parameter is only applicable for dynamic process management mode. + + + + +
+ +
+
+ Clustering mode diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 08027361e..e05c76473 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -297,6 +297,39 @@ default_transaction_isolation = 'repeatable read' is not possible in the mode. + + + Process management modes + + + dynamic process management + + + + static process management + + + Pgpool-II implements a multi-process architecture where + each child process can handle exactly one client connection at any time. + The total number of concurrent client connections Pgpool-II + can handle is configured by the + num_init_children config parameter. + Pgpool-II supports two child process management modes. + Dynamic and Static. + In static process management mode, Pgpool-II pre-forks the + num_init_children number of child + process at startup, and each child process keeps listening for incoming + client connection. While with dynamic process management mode, + Pgpool-II keeps track of idle processes and forks or kills + processes to keep this number within the specified boundaries. + + + process_management_mode is not available prior to + Pgpool-II V4.4. + + + + diff --git a/src/auth/pool_auth.c b/src/auth/pool_auth.c index 9425a0f2c..0286f8f93 100644 --- a/src/auth/pool_auth.c +++ b/src/auth/pool_auth.c @@ -21,6 +21,7 @@ */ #include "pool.h" +#include #include "context/pool_session_context.h" #include "protocol/pool_process_query.h" #include "protocol/pool_proto_modules.h" @@ -34,11 +35,11 @@ #include "utils/palloc.h" #include "utils/memutils.h" #include "auth/md5.h" +#include #ifdef HAVE_CRYPT_H #include #endif - #ifdef HAVE_SYS_TYPES_H #include #endif diff --git a/src/config/pool_config_variables.c b/src/config/pool_config_variables.c index 25d7ac29e..8896cbc91 100644 --- a/src/config/pool_config_variables.c +++ b/src/config/pool_config_variables.c @@ -236,6 +236,21 @@ static const struct config_enum_entry backend_clustering_mode_options[] = { {NULL, 0, false} }; +static const struct config_enum_entry process_management_mode_options[] = { + {"static", PM_STATIC, false}, + {"dynamic", PM_DYNAMIC, false}, + + {NULL, 0, false} +}; + +static const struct config_enum_entry process_management_strategy_options[] = { + {"aggressive", PM_STRATEGY_AGGRESSIVE, false}, + {"gentle", PM_STRATEGY_GENTLE, false}, + {"lazy", PM_STRATEGY_LAZY, false}, + + {NULL, 0, false} +}; + static const struct config_enum_entry log_standby_delay_options[] = { {"always", LSD_ALWAYS, false}, {"if_over_threshold", LSD_OVER_THRESHOLD, false}, @@ -1896,7 +1911,7 @@ static struct config_int ConfigureNamesInt[] = { {"num_init_children", CFGCXT_INIT, CONNECTION_POOL_CONFIG, - "Number of children pre-forked for client connections.", + "Maximim number of child processs to handle client connections.", CONFIG_VAR_TYPE_INT, false, 0 }, &g_pool_config.num_init_children, @@ -1905,6 +1920,28 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, NULL }, + { + {"min_spare_children", CFGCXT_RELOAD, CONNECTION_POOL_CONFIG, + "Minimum number of spare child processes.", + CONFIG_VAR_TYPE_INT, false, 0 + }, + &g_pool_config.min_spare_children, + 5, + 1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"max_spare_children", CFGCXT_RELOAD, CONNECTION_POOL_CONFIG, + "Maximum number of spare child processes.", + CONFIG_VAR_TYPE_INT, false, 0 + }, + &g_pool_config.max_spare_children, + 10, + 1, INT_MAX, + NULL, NULL, NULL + }, + { {"reserved_connections", CFGCXT_INIT, CONNECTION_POOL_CONFIG, "Number of reserved connections.", @@ -2246,6 +2283,27 @@ static struct config_enum ConfigureNamesEnum[] = NULL, NULL, NULL, NULL }, + { + {"process_management_mode", CFGCXT_RELOAD, CONNECTION_POOL_CONFIG, + "child process management mode.", + CONFIG_VAR_TYPE_ENUM, false, 0 + }, + (int *) &g_pool_config.process_management, + PM_STATIC, + process_management_mode_options, + NULL, NULL, NULL, NULL + }, + + { + {"process_management_strategy", CFGCXT_RELOAD, CONNECTION_POOL_CONFIG, + "child process management strategy.", + CONFIG_VAR_TYPE_ENUM, false, 0 + }, + (int *) &g_pool_config.process_management_strategy, + PM_STRATEGY_GENTLE, + process_management_strategy_options, + NULL, NULL, NULL, NULL + }, { {"syslog_facility", CFGCXT_RELOAD, LOGGING_CONFIG, @@ -4863,6 +4921,14 @@ config_post_processor(ConfigContext context, int elevel) (errmsg("invalid configuration, failover_when_quorum_exists is not allowed in native replication mode"))); return false; } + + if (pool_config->min_spare_children >= pool_config->max_spare_children) + { + ereport(elevel, + (errmsg("invalid configuration, max_spare_children:%d must be greater than max_spare_children:%d", + pool_config->max_spare_children,pool_config->min_spare_children))); + return false; + } return true; } diff --git a/src/context/pool_process_context.c b/src/context/pool_process_context.c index 51b08bed2..00a04ff8d 100644 --- a/src/context/pool_process_context.c +++ b/src/context/pool_process_context.c @@ -230,25 +230,30 @@ pool_coninfo_backend_pid(int backend_pid, int *backend_node_id) for (child = 0; child < pool_config->num_init_children; child++) { int pool; - ProcessInfo *pi = pool_get_process_info(process_info[child].pid); - for (pool = 0; pool < pool_config->max_pool; pool++) + if (process_info[child].pid) { - int backend_id; + ProcessInfo *pi = pool_get_process_info(process_info[child].pid); - for (backend_id = 0; backend_id < NUM_BACKENDS; backend_id++) + for (pool = 0; pool < pool_config->max_pool; pool++) { - int poolBE = pool * MAX_NUM_BACKENDS + backend_id; + int backend_id; - if (ntohl(pi->connection_info[poolBE].pid) == backend_pid) + for (backend_id = 0; backend_id < NUM_BACKENDS; backend_id++) { - ereport(DEBUG1, - (errmsg("found for the connection with backend pid:%d on backend node %d", backend_pid, backend_id))); - *backend_node_id = backend_id; - return &pi->connection_info[poolBE]; + int poolBE = pool * MAX_NUM_BACKENDS + backend_id; + + if (ntohl(pi->connection_info[poolBE].pid) == backend_pid) + { + ereport(DEBUG1, + (errmsg("found the connection with backend pid:%d on backend node %d", backend_pid, backend_id))); + *backend_node_id = backend_id; + return &pi->connection_info[poolBE]; + } } } } + } return NULL; } diff --git a/src/include/main/pool_internal_comms.h b/src/include/main/pool_internal_comms.h index 1709d1ad9..6b4dc60d8 100644 --- a/src/include/main/pool_internal_comms.h +++ b/src/include/main/pool_internal_comms.h @@ -42,5 +42,4 @@ extern void register_backend_state_sync_req_interrupt(void); extern void register_inform_quarantine_nodes_req(void); extern bool register_node_operation_request(POOL_REQUEST_KIND kind, int *node_id_set, int count, unsigned char flags); - #endif /* pool_internal_comms_h */ diff --git a/src/include/pcp/libpcp_ext.h b/src/include/pcp/libpcp_ext.h index 9d2c0cfd4..2d86dd304 100644 --- a/src/include/pcp/libpcp_ext.h +++ b/src/include/pcp/libpcp_ext.h @@ -124,7 +124,8 @@ typedef enum WAIT_FOR_CONNECT, COMMAND_EXECUTE, IDLE, - IDLE_IN_TRANS + IDLE_IN_TRANS, + CONNECTING } ProcessStatus; /* @@ -176,10 +177,13 @@ typedef struct * this process */ int client_connection_count; /* how many times clients used this process */ ProcessStatus status; - char need_to_restart; /* If non 0, exit this child process as + bool need_to_restart; /* If non 0, exit this child process as * soon as current session ends. Typical * case this flag being set is failback a * node in streaming replication mode. */ + bool exit_if_idle; + int pooled_connections; /* Total number of pooled connections + * by this child */ } ProcessInfo; /* diff --git a/src/include/pool.h b/src/include/pool.h index af0706973..f3de14543 100644 --- a/src/include/pool.h +++ b/src/include/pool.h @@ -595,6 +595,7 @@ extern BACKEND_STATUS private_backend_status[MAX_NUM_BACKENDS]; extern char remote_host[]; /* client host */ extern char remote_port[]; /* client port */ + /* * public functions */ diff --git a/src/include/pool_config.h b/src/include/pool_config.h index 142c6fab1..3bf1663ec 100644 --- a/src/include/pool_config.h +++ b/src/include/pool_config.h @@ -57,6 +57,19 @@ typedef struct regex_t regexv; } RegPattern; +typedef enum ProcessManagementModes +{ + PM_STATIC = 1, + PM_DYNAMIC +} ProcessManagementModes; + +typedef enum ProcessManagementSstrategies +{ + PM_STRATEGY_AGGRESSIVE = 1, + PM_STRATEGY_GENTLE, + PM_STRATEGY_LAZY +} ProcessManagementSstrategies; + typedef enum NativeReplicationSubModes { SLONY_MODE = 1, @@ -203,6 +216,8 @@ typedef struct typedef struct { ClusteringModes backend_clustering_mode; /* Backend clustering mode */ + ProcessManagementModes process_management; + ProcessManagementSstrategies process_management_strategy; char **listen_addresses; /* hostnames/IP addresses to listen on */ int port; /* port # to bind */ char **pcp_listen_addresses; /* PCP listen address to listen on */ @@ -212,7 +227,10 @@ typedef struct int unix_socket_permissions; /* pgpool sockets permissions */ char *wd_ipc_socket_dir; /* watchdog command IPC socket directory */ char *pcp_socket_dir; /* PCP socket directory */ - int num_init_children; /* # of children initially pre-forked */ + int num_init_children; /* Maximum number of child to + * accept connections */ + int min_spare_children; /* Minimum number of idle children */ + int max_spare_children; /* Minimum number of idle children */ int listen_backlog_multiplier; /* determines the size of the * connection queue */ int reserved_connections; /* # of reserved connections */ diff --git a/src/include/protocol/pool_connection_pool.h b/src/include/protocol/pool_connection_pool.h index 19b8f72af..aee976d7c 100644 --- a/src/include/protocol/pool_connection_pool.h +++ b/src/include/protocol/pool_connection_pool.h @@ -37,4 +37,5 @@ extern int connect_inet_domain_socket_by_port(char *host, int port, bool retry); extern int connect_unix_domain_socket_by_port(int port, char *socket_dir, bool retry); extern int pool_pool_index(void); extern void close_all_backend_connections(void); +extern void update_pooled_connection_count(void); #endif /* pool_connection_pool_h */ diff --git a/src/main/pgpool_main.c b/src/main/pgpool_main.c index 5dde03646..1fc25bd5f 100644 --- a/src/main/pgpool_main.c +++ b/src/main/pgpool_main.c @@ -117,6 +117,8 @@ typedef struct User1SignalSlot #endif #define PGPOOLMAXLITSENQUEUELENGTH 10000 +#define MAX_ONE_SHOT_KILLS 8 + #define UNIXSOCK_PATH_BUFLEN sizeof(((struct sockaddr_un *) NULL)->sun_path) @@ -198,12 +200,16 @@ static void exec_notice_pcp_child(FAILOVER_CONTEXT *failover_context); static void check_requests(void); static void print_signal_member(sigset_t *sig); +static void service_child_processes(void); +static int select_victim_processes(int *process_info_idxs, int count); static struct sockaddr_un *un_addrs; /* unix domain socket path */ static struct sockaddr_un pcp_un_addr; /* unix domain socket path for PCP */ ProcessInfo *process_info = NULL; /* Per child info table on shmem */ volatile User1SignalSlot *user1SignalSlot = NULL; /* User 1 signal slot on * shmem */ +int current_child_process_count; + struct timeval random_start_time; /* @@ -515,15 +521,24 @@ PgpoolMain(bool discard_status, bool clear_memcache_oidmaps) * is harmless. */ POOL_SETMASK(&BlockSig); + + if (pool_config->process_management == PM_DYNAMIC) + current_child_process_count = pool_config->max_spare_children; + else + current_child_process_count = pool_config->num_init_children; + /* fork the children */ - for (i = 0; i < pool_config->num_init_children; i++) + for (i = 0; i < current_child_process_count; i++) { - process_info[i].pid = fork_a_child(fds, i); process_info[i].start_time = time(NULL); process_info[i].client_connection_count = 0; process_info[i].status = WAIT_FOR_CONNECT; process_info[i].connected = 0; process_info[i].wait_for_connect = 0; + process_info[i].pooled_connections = 0; + process_info[i].need_to_restart = false; + process_info[i].exit_if_idle = false; + process_info[i].pid = fork_a_child(fds, i); } /* create pipe for delivering event */ @@ -656,11 +671,15 @@ PgpoolMain(bool discard_status, bool clear_memcache_oidmaps) for (;;) { int r; - struct timeval t = {3, 0}; + struct timeval t = {2, 0}; POOL_SETMASK(&UnBlockSig); r = pool_pause(&t); POOL_SETMASK(&BlockSig); + + if (pool_config->process_management == PM_DYNAMIC) + service_child_processes(); + if (r > 0) break; } @@ -1958,7 +1977,8 @@ reaper(void) { found = true; /* if found, fork a new child */ - if (!switching && !exiting && restart_child) + if (!switching && !exiting && restart_child && + pool_config->process_management != PM_DYNAMIC) { process_info[i].pid = fork_a_child(fds, i); process_info[i].start_time = time(NULL); @@ -1969,7 +1989,10 @@ reaper(void) process_info[i].wait_for_connect = 0; } else + { + current_child_process_count--; process_info[i].pid = 0; + } break; } } @@ -2050,12 +2073,25 @@ int * pool_get_process_list(int *array_size) { int *array; + int cnt = 0; int i; - *array_size = pool_config->num_init_children; + for (i=0;i < pool_config->num_init_children;i++) + { + if (process_info[i].pid != 0) + cnt++; + } + *array_size = cnt; + cnt = 0; array = palloc0(*array_size * sizeof(int)); - for (i = 0; i < *array_size; i++) - array[i] = process_info[i].pid; + for (i = 0; i < pool_config->num_init_children || cnt < *array_size; i++) + { + if (process_info[i].pid != 0) + { + array[cnt] = process_info[i].pid; + cnt++; + } + } return array; } @@ -2977,7 +3013,10 @@ initialize_shared_mem_objects(bool clear_memcache_oidmaps) process_info = (ProcessInfo *)pool_shared_memory_segment_get_chunk(pool_config->num_init_children * (sizeof(ProcessInfo))); for (i = 0; i < pool_config->num_init_children; i++) + { process_info[i].connection_info = pool_coninfo(i, 0, 0); + process_info[i].pid = 0; + } user1SignalSlot = (User1SignalSlot *)pool_shared_memory_segment_get_chunk(sizeof(User1SignalSlot)); @@ -3734,6 +3773,7 @@ sync_backend_from_watchdog(void) process_info[i].status = WAIT_FOR_CONNECT; process_info[i].connected = 0; process_info[i].wait_for_connect = 0; + process_info[i].pooled_connections = 0; } } else @@ -3749,7 +3789,8 @@ sync_backend_from_watchdog(void) */ for (i = 0; i < pool_config->num_init_children; i++) { - process_info[i].need_to_restart = 1; + if (process_info[i].pid) + process_info[i].need_to_restart = 1; } } @@ -4598,6 +4639,8 @@ exec_child_restart(FAILOVER_CONTEXT *failover_context, int node_id) process_info[i].status = WAIT_FOR_CONNECT; process_info[i].connected = 0; process_info[i].wait_for_connect = 0; + process_info[i].pooled_connections = 0; + } } else @@ -4890,3 +4933,180 @@ void print_signal_member(sigset_t *sig) ereport(LOG, (errmsg("SIGTERM is member"))); } + +/* +* Function does the house keeping of spare child processes +*/ +static void +service_child_processes(void) +{ + int connected_children = Req_info->conn_counter; + int idle_children = current_child_process_count - connected_children; + static int high_load_counter = 0; + ereport(DEBUG2, + (errmsg("current_children_count = %d idle_children = %d connected_children = %d high_load_counter = %d", + current_child_process_count, idle_children, connected_children, high_load_counter))); + if (idle_children > pool_config->max_spare_children) + { + int ki; + int victim_count; + int kill_process_info_idxs[MAX_ONE_SHOT_KILLS]; + int kill_count = idle_children - pool_config->max_spare_children; + int cycle_skip_count_before_scale_down; + int cycle_skip_between_scale_down; + int one_shot_kill_count; + + switch (pool_config->process_management_strategy) + { + case PM_STRATEGY_AGGRESSIVE: + cycle_skip_count_before_scale_down = 25; /* roughly 50 seconds */ + cycle_skip_between_scale_down = 2; + one_shot_kill_count = MAX_ONE_SHOT_KILLS; + break; + + case PM_STRATEGY_LAZY: + cycle_skip_count_before_scale_down = 150; /* roughly 300 seconds */ + cycle_skip_between_scale_down = 10; + one_shot_kill_count = 3; + break; + + case PM_STRATEGY_GENTLE: + cycle_skip_count_before_scale_down = 60; /* roughly 120 seconds */ + cycle_skip_between_scale_down = 5; + one_shot_kill_count = 3; + break; + + default: + /* should never come here, but if we do use gentle counts*/ + cycle_skip_count_before_scale_down = 60; /* roughly 120 seconds */ + cycle_skip_between_scale_down = 5; + one_shot_kill_count = 3; + break; + } + + /* Do not scale down too quickly */ + if (++high_load_counter < cycle_skip_count_before_scale_down || high_load_counter % cycle_skip_between_scale_down) + return; + + memset(kill_process_info_idxs, -1 ,sizeof(kill_process_info_idxs)); + + if (kill_count > one_shot_kill_count) + kill_count = one_shot_kill_count; + + victim_count = select_victim_processes(kill_process_info_idxs, kill_count); + + for (ki = 0; ki < victim_count; ki++) + { + int index = kill_process_info_idxs[ki]; + if (index >=0) + { + if (process_info[index].pid && process_info[index].status == WAIT_FOR_CONNECT) + { + ereport(DEBUG1, + (errmsg("asking child process with pid:%d to kill itself to satisfy max_spare_children", + process_info[index].pid), + errdetail("child process has %d pooled connections",process_info[index].pooled_connections))); + process_info[index].exit_if_idle = true; + kill(process_info[index].pid, SIGUSR2); + } + } + } + } + else + { + /* Reset the high load counter */ + high_load_counter = 0; + /*See if we need to spawn new children */ + if (idle_children < pool_config->min_spare_children) + { + int i; + int spawned = 0; + int new_spawn_no = pool_config->min_spare_children - idle_children; + /* Add 25% of max_spare_children */ + new_spawn_no += pool_config->max_spare_children / 4; + if (new_spawn_no + current_child_process_count > pool_config->num_init_children) + { + ereport(LOG, + (errmsg("we have hit the ceiling, spawning %d child(ren)", + pool_config->num_init_children - current_child_process_count))); + new_spawn_no = pool_config->num_init_children - current_child_process_count; + } + if (new_spawn_no <= 0) + return; + for (i = 0; i < pool_config->num_init_children; i++) + { + if (process_info[i].pid == 0) + { + process_info[i].start_time = time(NULL); + process_info[i].client_connection_count = 0; + process_info[i].status = WAIT_FOR_CONNECT; + process_info[i].connected = 0; + process_info[i].wait_for_connect = 0; + process_info[i].pooled_connections = 0; + process_info[i].need_to_restart = 0; + process_info[i].exit_if_idle = false; + process_info[i].pid = fork_a_child(fds, i); + + current_child_process_count++; + if (++spawned >= new_spawn_no) + break; + } + } + } + } +} + +/* + * Function selects the child processes that can be killed based. + * selection criteria is to select the processes with minimum number of + * pooled connections. + * Returns the total number of identified process and fills the proc_info_arr + * with the victim children process_info index + */ +static int +select_victim_processes(int *process_info_idxs, int count) +{ + int i, ki; + bool found_enough = false; + int selected_count = 0; + + if (count <= 0) + return 0; + + for (i = 0; i < pool_config->num_init_children; i++) + { + /* Only the child process in waiting for connect can be terminated */ + if (process_info[i].pid && process_info[i].status == WAIT_FOR_CONNECT) + { + if (selected_count < count) + { + process_info_idxs[selected_count++] = i; + } + else + { + found_enough = true; + /* we don't bother selecting the child having least pooled connection with + * aggressive strategy + */ + if (pool_config->process_management_strategy != PM_STRATEGY_AGGRESSIVE) + { + for (ki = 0; ki < count; ki++) + { + int old_index = process_info_idxs[ki]; + if (old_index < 0 || process_info[old_index].pooled_connections > process_info[i].pooled_connections) + { + process_info_idxs[ki] = i; + found_enough = false; + break; + } + if (process_info[old_index].pooled_connections) + found_enough = false; + } + } + } + } + if (found_enough) + break; + } + return selected_count; +} diff --git a/src/protocol/child.c b/src/protocol/child.c index 9056a29a9..f68c9eda8 100644 --- a/src/protocol/child.c +++ b/src/protocol/child.c @@ -82,6 +82,7 @@ static bool connect_using_existing_connection(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * backend, StartupPacket *sp); static void check_restart_request(void); +static void check_exit_request(void); static void enable_authentication_timeout(void); static void disable_authentication_timeout(void); static int wait_for_new_connections(int *fds, SockAddr *saddr); @@ -300,7 +301,7 @@ do_child(int *fds) pool_close(child_frontend); child_frontend = NULL; } - + update_pooled_connection_count(); MemoryContextSwitchTo(TopMemoryContext); FlushErrorState(); } @@ -325,6 +326,7 @@ do_child(int *fds) /* pgpool stop request already sent? */ check_stop_request(); check_restart_request(); + check_exit_request(); accepted = 0; /* Destroy session context for just in case... */ pool_session_context_destroy(); @@ -345,6 +347,10 @@ do_child(int *fds) if (front_end_fd == RETRY) continue; + set_process_status(CONNECTING); + /* Reset any exit if idle request even if it's pending */ + pool_get_my_process_info()->exit_if_idle = false; + con_count = connection_count_up(); if (pool_config->reserved_connections > 0) @@ -459,7 +465,7 @@ do_child(int *fds) /* Mark this connection pool is not connected from frontend */ pool_coninfo_unset_frontend_connected(pool_get_process_context()->proc_id, pool_pool_index()); - + update_pooled_connection_count(); accepted = 0; connection_count_down(); if (pool_config->log_disconnections) @@ -483,6 +489,8 @@ do_child(int *fds) child_exit(POOL_EXIT_NO_RESTART); } + + /* ------------------------------------------------------------------- * private functions * ------------------------------------------------------------------- @@ -1432,6 +1440,23 @@ pool_initialize_private_backend_status(void) my_main_node_id = REAL_MAIN_NODE_ID; } +static void +check_exit_request(void) +{ + /* + * Check if exit request is set because of spare children management. + */ + if (pool_get_my_process_info()->exit_if_idle) + { + ereport(LOG, + (errmsg("Exit flag set"), + errdetail("Exiting myself"))); + + pool_get_my_process_info()->exit_if_idle = 0; + child_exit(POOL_EXIT_NO_RESTART); + } +} + static void check_restart_request(void) { @@ -1445,7 +1470,7 @@ check_restart_request(void) (errmsg("failover or failback event detected"), errdetail("restarting myself"))); - pool_get_my_process_info()->need_to_restart = 0; + pool_get_my_process_info()->need_to_restart = false; child_exit(POOL_EXIT_AND_RESTART); } } diff --git a/src/protocol/pool_connection_pool.c b/src/protocol/pool_connection_pool.c index ed28d8700..7f3c44ce5 100644 --- a/src/protocol/pool_connection_pool.c +++ b/src/protocol/pool_connection_pool.c @@ -471,7 +471,7 @@ pool_backend_timer(void) nearest = 1; pool_alarm(pool_backend_timer_handler, nearest); } - + update_pooled_connection_count(); POOL_SETMASK(&UnBlockSig); } @@ -1066,3 +1066,16 @@ close_all_backend_connections(void) POOL_SETMASK(&oldmask); } + +void update_pooled_connection_count(void) +{ + int i; + int count = 0; + POOL_CONNECTION_POOL *p = pool_connection_pool; + for (i = 0; i < pool_config->max_pool; i++) + { + if (MAIN_CONNECTION(p)) + count++; + } + pool_get_my_process_info()->pooled_connections = count; +} diff --git a/src/sample/pgpool.conf.sample-stream b/src/sample/pgpool.conf.sample-stream index a0b02e8bb..daee798df 100644 --- a/src/sample/pgpool.conf.sample-stream +++ b/src/sample/pgpool.conf.sample-stream @@ -170,9 +170,45 @@ backend_clustering_mode = 'streaming_replication' # - Concurrent session and pool size - +#process_management_mode = static + # process management mode for child processes + # Valid options: + # static: all children are pre-forked at startup + # dynamic: child processes are spawned on demand. + # number of idle child processes at any time are + # configured by min_spare_children and max_spare_children + +#process_management_strategy = gentle + # process management strategy to satisfy spare processes + # Valid options: + # + # lazy: In this mode, the scale-down is performed gradually + # and only gets triggered when excessive spare processes count + # remains high for more than 5 mins + # + # gentle: In this mode, the scale-down is performed gradually + # and only gets triggered when excessive spare processes count + # remains high for more than 2 mins + # + # aggressive: In this mode, the scale-down is performed aggressively + # and gets triggered more frequently in case of higher spare processes. + # This mode uses faster and slightly less smart process selection criteria + # to identify the child processes that can be serviced to satisfy + # max_spare_children + # + # (Only applicable for dynamic process management mode) + #num_init_children = 32 - # Number of concurrent sessions allowed + # Maximum Number of concurrent sessions allowed # (change requires restart) +#min_spare_children = 5 + # Minimum number of spare child processes waiting for connection + # (Only applicable for dynamic process management mode) + +#max_spare_children = 10 + # Maximum number of idle child processes waiting for connection + # (Only applicable for dynamic process management mode) + #max_pool = 4 # Number of connection pool caches per connection # (change requires restart) diff --git a/src/tools/pcp/pcp_frontend_client.c b/src/tools/pcp/pcp_frontend_client.c index 4ae47ffa3..ef068e298 100644 --- a/src/tools/pcp/pcp_frontend_client.c +++ b/src/tools/pcp/pcp_frontend_client.c @@ -4,7 +4,7 @@ * pgpool: a language independent connection pool server for PostgreSQL * written by Tatsuo Ishii * - * Copyright (c) 2003-2021 PgPool Global Development Group + * Copyright (c) 2003-2022 PgPool Global Development Group * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby @@ -745,7 +745,7 @@ output_procinfo_result(PCPResultInfo * pcpResInfo, bool all, bool verbose) if (pools == NULL) break; - if ((!all) && (pools->database[0] == '\0')) + if (((!all) && (pools->database[0] == '\0')) || (pools->pool_pid[0] == '\0')) continue; printed = true; printf(format, diff --git a/src/utils/pool_process_reporting.c b/src/utils/pool_process_reporting.c index acb32c726..9263b4522 100644 --- a/src/utils/pool_process_reporting.c +++ b/src/utils/pool_process_reporting.c @@ -335,10 +335,6 @@ get_config(int *nrows) /* POOLS */ /* - Pool size - */ - StrNCpy(status[i].name, "num_init_children", POOLCONFIG_MAXNAMELEN); - snprintf(status[i].value, POOLCONFIG_MAXVALLEN, "%d", pool_config->num_init_children); - StrNCpy(status[i].desc, "# of children initially pre-forked", POOLCONFIG_MAXDESCLEN); - i++; StrNCpy(status[i].name, "listen_backlog_multiplier", POOLCONFIG_MAXNAMELEN); snprintf(status[i].value, POOLCONFIG_MAXVALLEN, "%d", pool_config->listen_backlog_multiplier); @@ -360,6 +356,21 @@ get_config(int *nrows) StrNCpy(status[i].desc, "max # of connection pool per child", POOLCONFIG_MAXDESCLEN); i++; + StrNCpy(status[i].name, "num_init_children", POOLCONFIG_MAXNAMELEN); + snprintf(status[i].value, POOLCONFIG_MAXVALLEN, "%d", pool_config->num_init_children); + StrNCpy(status[i].desc, "# of children initially pre-forked", POOLCONFIG_MAXDESCLEN); + i++; + + StrNCpy(status[i].name, "min_spare_children", POOLCONFIG_MAXNAMELEN); + snprintf(status[i].value, POOLCONFIG_MAXVALLEN, "%d", pool_config->min_spare_children); + StrNCpy(status[i].desc, "min # of spare children waitting for connection", POOLCONFIG_MAXDESCLEN); + i++; + + StrNCpy(status[i].name, "max_spare_children", POOLCONFIG_MAXNAMELEN); + snprintf(status[i].value, POOLCONFIG_MAXVALLEN, "%d", pool_config->max_spare_children); + StrNCpy(status[i].desc, "max # of spare children waitting for connection", POOLCONFIG_MAXDESCLEN); + i++; + /* - Life time - */ StrNCpy(status[i].name, "child_life_time", POOLCONFIG_MAXNAMELEN); snprintf(status[i].value, POOLCONFIG_MAXVALLEN, "%d", pool_config->child_life_time); @@ -1622,6 +1633,9 @@ get_pools(int *nrows) case IDLE_IN_TRANS: StrNCpy(pools[lines].status, "Idle in transaction", POOLCONFIG_MAXPROCESSSTATUSLEN); break; + case CONNECTING: + StrNCpy(pools[lines].status, "Connecting", POOLCONFIG_MAXPROCESSSTATUSLEN); + break; default: *(pools[lines].status) = '\0'; } @@ -1746,6 +1760,9 @@ get_processes(int *nrows) case IDLE_IN_TRANS: StrNCpy(processes[child].status, "Idle in transaction", POOLCONFIG_MAXPROCESSSTATUSLEN); break; + case CONNECTING: + StrNCpy(processes[child].status, "Connecting", POOLCONFIG_MAXPROCESSSTATUSLEN); + break; default: *(processes[child].status) = '\0'; } diff --git a/src/watchdog/wd_json_data.c b/src/watchdog/wd_json_data.c index e1dd04f45..83842a75b 100644 --- a/src/watchdog/wd_json_data.c +++ b/src/watchdog/wd_json_data.c @@ -48,8 +48,6 @@ get_pool_config_from_json(char *json_data, int data_len) if (root == NULL || root->type != json_object) goto ERROR_EXIT; - if (json_get_int_value_for_key(root, "num_init_children", &config->num_init_children)) - goto ERROR_EXIT; if (json_get_int_value_for_key(root, "listen_backlog_multiplier", &config->listen_backlog_multiplier)) goto ERROR_EXIT; if (json_get_int_value_for_key(root, "child_life_time", &config->child_life_time)) @@ -62,6 +60,16 @@ get_pool_config_from_json(char *json_data, int data_len) goto ERROR_EXIT; if (json_get_int_value_for_key(root, "max_pool", &config->max_pool)) goto ERROR_EXIT; + if (json_get_int_value_for_key(root, "num_init_children", &config->num_init_children)) + goto ERROR_EXIT; + if (json_get_int_value_for_key(root, "min_spare_children", &config->min_spare_children)) + goto ERROR_EXIT; + if (json_get_int_value_for_key(root, "max_spare_children", &config->max_spare_children)) + goto ERROR_EXIT; + if (json_get_int_value_for_key(root, "process_management_mode", (int*)&config->process_management)) + goto ERROR_EXIT; + if (json_get_int_value_for_key(root, "process_management_strategy", (int*)&config->process_management_strategy)) + goto ERROR_EXIT; if (json_get_bool_value_for_key(root, "replication_mode", &config->replication_mode)) goto ERROR_EXIT; if (json_get_bool_value_for_key(root, "enable_pool_hba", &config->enable_pool_hba)) @@ -174,13 +182,17 @@ get_pool_config_json(void) JsonNode *jNode = jw_create_with_object(true); - jw_put_int(jNode, "num_init_children", pool_config->num_init_children); jw_put_int(jNode, "listen_backlog_multiplier", pool_config->listen_backlog_multiplier); jw_put_int(jNode, "child_life_time", pool_config->child_life_time); jw_put_int(jNode, "connection_life_time", pool_config->connection_life_time); jw_put_int(jNode, "child_max_connections", pool_config->child_max_connections); jw_put_int(jNode, "client_idle_limit", pool_config->client_idle_limit); jw_put_int(jNode, "max_pool", pool_config->max_pool); + jw_put_int(jNode, "num_init_children", pool_config->num_init_children); + jw_put_int(jNode, "min_spare_children", pool_config->min_spare_children); + jw_put_int(jNode, "max_spare_children", pool_config->max_spare_children); + jw_put_int(jNode, "process_management_mode", pool_config->process_management); + jw_put_int(jNode, "process_management_strategy", pool_config->process_management_strategy); jw_put_bool(jNode, "replication_mode", pool_config->replication_mode); jw_put_bool(jNode, "enable_pool_hba", pool_config->enable_pool_hba); jw_put_bool(jNode, "load_balance_mode", pool_config->load_balance_mode); -- 2.39.5