It is similar to max_db_connections. It is designed for a usage, where databases on server are "unlimited"
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,
; 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
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 */
};
/*
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;
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;
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;
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;
" 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);
}
user->pool_mode = pool_mode;
+ user->max_user_connections = max_user_connections;
fail:
free(tmp_connstr);
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;
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"),
}
server->pool->db->connection_count--;
+ server->pool->user->connection_count--;
change_server_state(server, SV_JUSTFREE);
if (!sbuf_close(&server->sbuf))
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)
{
}
}
+ 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) {
pool->last_connect_time = get_cached_time();
change_server_state(server, SV_LOGIN);
pool->db->connection_count++;
+ pool->user->connection_count++;
dns_connect(server);
}
}
}
+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)
{