]> granicus.if.org Git - pgbouncer/commitdiff
max_user_connections implementation - a limit of connected users.
authorPavel Stehule <pavel.stehule@gmail.com>
Tue, 4 Mar 2014 17:32:04 +0000 (18:32 +0100)
committerMarko Kreen <markokr@gmail.com>
Sat, 17 May 2014 16:03:58 +0000 (19:03 +0300)
It is similar to max_db_connections. It is designed for a usage, where databases on server are "unlimited"

doc/config.txt
etc/pgbouncer.ini
include/bouncer.h
include/objects.h
include/server.h
src/loader.c
src/main.c
src/objects.c
src/server.c

index b3af81446ad113a08b6d9bd2a15857f86aa0368f..70759c70bbdd3d9df66c15b6c69f9e6394fa7ebc 100644 (file)
@@ -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, 
index bdcaef4154da85b7e0e96d21de0f7796f05c9eb5..b57f9866aaaff7e8535f8671defaa982c7e13be8 100644 (file)
@@ -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
index 75c06e47d6ab5f568347c4a169367bed1d1a6882..ae78a79eccc02b6393e2fac0c7c382f8b5dd560a 100644 (file)
@@ -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;
index b18148b48e9c31c9ad112a11434dd147abcd143c..23a5df8fd183bfc6c56b3215a6a60e8977a712c8 100644 (file)
@@ -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;
index 3f40eb1010683ca74cd60bea3aefeeae71162326..174cd31eff7bc2ff30bc1e04290223a1a019dac2 100644 (file)
@@ -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;
index 94960845618224398c883c845a76ace1ec17cd19..ac8ad75207972fc41448b7e6f4adbb5e61daeca5 100644 (file)
@@ -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);
index 691d4af5f8ca52484f2aa48ad8e91c9ecf7cd26e..2f365845afe2e50fdac0958299ce1e64e68ae87f 100644 (file)
@@ -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"),
index ec80d109dc10da78f0d72b7adab4f28cf56e9f08..45162797bae5c642b3c561a796a2136039ededcb 100644 (file)
@@ -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);
 }
index b8f63e467bb4f883692abfed1d2d01c3024e2a5d..2ee266e25742922dc1980f22f0a4439321fbab14 100644 (file)
@@ -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)
 {