From: Pavel Stehule Date: Tue, 4 Mar 2014 17:32:04 +0000 (+0100) Subject: max_user_connections implementation - a limit of connected users. X-Git-Tag: pgbouncer_1_6_rc1~10 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=700fb804c2dfe0e945c1539276e9a133f4969ae2;p=pgbouncer max_user_connections implementation - a limit of connected users. It is similar to max_db_connections. It is designed for a usage, where databases on server are "unlimited" --- diff --git a/doc/config.txt b/doc/config.txt index b3af814..70759c7 100644 --- a/doc/config.txt +++ b/doc/config.txt @@ -198,6 +198,15 @@ will immediately be opened for the waiting pool. Default: unlimited +==== max_user_connection ==== + +Do not allow more than this many connections per-user (regardless of pool - i.e. +user). It should be noted that when you hit the limit, closing a client connection +to one pool will not immediately allow a server connection to be established for +another pool, because the server connection for the first pool is still open. +Once the server connection closes (due to idle timeout), a new server connection +will immediately be opened for the waiting pool. + ==== server_round_robin ==== By default, pgbouncer reuses server connections in LIFO (last-in, first-out) manner, diff --git a/etc/pgbouncer.ini b/etc/pgbouncer.ini index bdcaef4..b57f986 100644 --- a/etc/pgbouncer.ini +++ b/etc/pgbouncer.ini @@ -126,6 +126,7 @@ default_pool_size = 20 ; how many total connections to a single database to allow from all pools ;max_db_connections = 50 +;max_user_connections = 50 ; log if client connects or server connection is made ;log_connections = 1 diff --git a/include/bouncer.h b/include/bouncer.h index 75c06e4..ae78a79 100644 --- a/include/bouncer.h +++ b/include/bouncer.h @@ -254,6 +254,8 @@ struct PgUser { char name[MAX_USERNAME]; char passwd[MAX_PASSWORD]; int pool_mode; + int max_user_connections; /* how much server connections are allowed */ + int connection_count; /* how much connections are used by user now */ }; /* @@ -376,6 +378,7 @@ extern int cf_min_pool_size; extern int cf_res_pool_size; extern usec_t cf_res_pool_timeout; extern int cf_max_db_connections; +extern int cf_max_user_connections; extern char * cf_autodb_connstr; extern usec_t cf_autodb_idle_timeout; diff --git a/include/objects.h b/include/objects.h index b18148b..23a5df8 100644 --- a/include/objects.h +++ b/include/objects.h @@ -34,6 +34,7 @@ PgUser *find_user(const char *name); PgPool *get_pool(PgDatabase *, PgUser *); PgSocket *compare_connections_by_time(PgSocket *lhs, PgSocket *rhs); bool evict_connection(PgDatabase *db) _MUSTCHECK; +bool evict_user_connection(PgUser *user) _MUSTCHECK; bool find_server(PgSocket *client) _MUSTCHECK; bool release_server(PgSocket *server) /* _MUSTCHECK */; bool finish_client_login(PgSocket *client) _MUSTCHECK; diff --git a/include/server.h b/include/server.h index 3f40eb1..174cd31 100644 --- a/include/server.h +++ b/include/server.h @@ -19,3 +19,4 @@ bool server_proto(SBuf *sbuf, SBufEvent evtype, struct MBuf *pkt) _MUSTCHECK; int pool_pool_mode(PgPool *pool) _MUSTCHECK; int database_max_connections(PgDatabase *db) _MUSTCHECK; +int user_max_connections(PgUser *user) _MUSTCHECK; diff --git a/src/loader.c b/src/loader.c index 9496084..ac8ad75 100644 --- a/src/loader.c +++ b/src/loader.c @@ -399,6 +399,8 @@ bool parse_user(void *base, const char *name, const char *connstr) PgUser *user; struct CfValue cv; int pool_mode = POOL_INHERIT; + int max_user_connections = -1; + cv.value_p = &pool_mode; cv.extra = (const void *)pool_mode_map; @@ -422,6 +424,9 @@ bool parse_user(void *base, const char *name, const char *connstr) " of invalid pool mode: %s", name, val); goto fail; } + + } else if (strcmp("max_user_connections", key) == 0) { + max_user_connections = atoi(val); } else { log_error("skipping user %s because" " of unknown parameter in settings: %s", name, key); @@ -439,6 +444,7 @@ bool parse_user(void *base, const char *name, const char *connstr) } user->pool_mode = pool_mode; + user->max_user_connections = max_user_connections; fail: free(tmp_connstr); diff --git a/src/main.c b/src/main.c index 691d4af..2f36584 100644 --- a/src/main.c +++ b/src/main.c @@ -94,6 +94,7 @@ int cf_min_pool_size; int cf_res_pool_size; usec_t cf_res_pool_timeout; int cf_max_db_connections; +int cf_max_user_connections; char *cf_server_reset_query; char *cf_server_check_query; @@ -187,6 +188,7 @@ CF_ABS("min_pool_size", CF_INT, cf_min_pool_size, 0, "0"), CF_ABS("reserve_pool_size", CF_INT, cf_res_pool_size, 0, "0"), CF_ABS("reserve_pool_timeout", CF_TIME_USEC, cf_res_pool_timeout, 0, "5"), CF_ABS("max_db_connections", CF_INT, cf_max_db_connections, 0, "0"), +CF_ABS("max_user_connections", CF_INT, cf_max_user_connections, 0, "0"), CF_ABS("syslog", CF_INT, cf_syslog, 0, "0"), CF_ABS("syslog_facility", CF_STR, cf_syslog_facility, 0, "daemon"), CF_ABS("syslog_ident", CF_STR, cf_syslog_ident, 0, "pgbouncer"), diff --git a/src/objects.c b/src/objects.c index ec80d10..4516279 100644 --- a/src/objects.c +++ b/src/objects.c @@ -802,6 +802,7 @@ void disconnect_server(PgSocket *server, bool notify, const char *reason, ...) } server->pool->db->connection_count--; + server->pool->user->connection_count--; change_server_state(server, SV_JUSTFREE); if (!sbuf_close(&server->sbuf)) @@ -1016,6 +1017,32 @@ bool evict_connection(PgDatabase *db) return false; } +/* evict the single most idle connection from among all pools to make room in the user */ +bool evict_user_connection(PgUser *user) +{ + struct List *item; + PgPool *pool; + PgSocket *oldest_connection = NULL; + + statlist_for_each(item, &pool_list) { + pool = container_of(item, PgPool, head); + if (pool->user != user) + continue; + oldest_connection = compare_connections_by_time(oldest_connection, last_socket(&pool->idle_server_list)); + /* only evict testing connections if nobody's waiting */ + if (statlist_empty(&pool->waiting_client_list)) { + oldest_connection = compare_connections_by_time(oldest_connection, last_socket(&pool->used_server_list)); + oldest_connection = compare_connections_by_time(oldest_connection, last_socket(&pool->tested_server_list)); + } + } + + if (oldest_connection) { + disconnect_server(oldest_connection, true, "evicted"); + return true; + } + return false; +} + /* the pool needs new connection, if possible */ void launch_new_connection(PgPool *pool) { @@ -1072,6 +1099,21 @@ allow_new: } } + total = user_max_connections(pool->user); + if (total > 0) { + /* try to evict unused connection first */ + while (pool->user->connection_count >= total) { + if (!evict_user_connection(pool->user)) { + break; + } + } + if (pool->user->connection_count >= total) { + log_debug("launch_new_connection: user full (%d >= %d)", + pool->user->connection_count, total); + return; + } + } + /* get free conn object */ server = slab_alloc(server_cache); if (!server) { @@ -1086,6 +1128,7 @@ allow_new: pool->last_connect_time = get_cached_time(); change_server_state(server, SV_LOGIN); pool->db->connection_count++; + pool->user->connection_count++; dns_connect(server); } diff --git a/src/server.c b/src/server.c index b8f63e4..2ee266e 100644 --- a/src/server.c +++ b/src/server.c @@ -204,6 +204,15 @@ int database_max_connections(PgDatabase *db) } } +int user_max_connections(PgUser *user) +{ + if (user->max_user_connections <= 0) { + return cf_max_user_connections; + } else { + return user->max_user_connections; + } +} + /* process packets on logged in connection */ static bool handle_server_work(PgSocket *server, PktHdr *pkt) {