Erlo

PostgreSQL在线调整最大连接数方案设想

2021-06-24 16:00:21 发布   569 浏览  
页面报错/反馈
收藏 点赞

一、背景介绍

PostgreSQL的最大连接数在某些时候会成为一个让业务很苦恼的问题。业务连接数暴增,连接数打满,但又不能重启服务这种场景下,显得非常尴尬。最大连接数就只能重启调整,要想不停库,貌似“无解”。

为什么调整最大连接数需要重启,不能修改为reload方式?
这个问题不详细展开,简单来说最大连接数参数和共享内存及信号量等相关的,设置最大连接数后,在启动时系统会相应分配这些资源。那原生的最大连接数参数看起来只能是重启方式修改生效了。

原生pg除了max_connections,还可以ALTER USER / DATABASE  CONNECTION LIMIT connlimit通过user和 database这两个维度去动态调整连接数。不过只适用于比较特定的场景下,稍微推敲一番,就会发现大部分场景下不适用。比如同实例多database,多user,并且每个user需要一定数量的连接数,那瓶颈又回到了max_connections

思考了一番,决定尝试新增一个在线参数db_connlimit,可以动态调整最大连接数,参数级别为PGC_SIGHUP即reload生效。
默认值为-1,保持默认值,最大连接数还是沿用原生的逻辑。取值范围从-1(default)至max_connnections 。也就是说系统资源还是由max_connections参数来管理,db_connlimit参数只用来管理连接数个数,资源满足情况下适当放大max_connections,例如将db_connlimit设置为max_connections的一半,在连接数已满的情况下,可以继续增大db_connlimit,来缓解一些压力。

当然这个方案是存在问题的:例如未考虑版本差异。要想做到兼容所有版本,那么可以考虑HooK extensions 或者中间件方式,不过这样架构可能会更加复杂了。研发大佬肯定有成熟的方案,作为运维同学,我们就最后挣扎一下,验证下这个方案。

这里使用pg13.0进行测试。

二、问题思考

db_connlimit参数必须满足以下几点要求:

  1. 对原生影响要小,可以关闭(default为关闭)关闭后不影响原生逻辑;

  2. 可靠性,连接数达到最大时要和原生处理逻辑一致;

  3. 最大值生效问题,最大值必须小于等于max_connnections 当试图配置大于这个值时reload不能生效,并且保持原有值

三、代码修改

针对以上3个问题,依次来看:

  • 增加参数db_connlimit

global.c 设置全局变量DbConnlimit

/*
* Modify by Nickxyang at 2021-06-07 PM
* Add the global variable DbConnlimit.
*/
int DbConnlimit = 100;
/*
* Modify End at 2021-06-07 PM
*/

miscadmin.h

/*
* Modify by Nickxyang at 2021-06-07 PM
*/
extern PGDLLIMPORT int DbConnlimit;
/*
* Modify End at 2021-06-07 PM
*/

guc.c新增参数db_connlimit, 首个-1为default,第二个-1为最小值

/*
* Modify by Nickxyang at 2021-06-07 PM
* Add the parameter db_connlimit, you can modify the maximum number of connections online.
* The valid range is -1 (default) to Maxconntions. A value of -1 means that the parameter is not open.
*/
{
{'db_connlimit', PGC_SIGHUP, CONN_AUTH_SETTINGS,
gettext_noop('Sets the maximum number of concurrent connections, which can be set online.'),
NULL
},
&DbConnlimit,
-1, -1, MAX_BACKENDS,
check_dbconnlimit, NULL, NULL
},
/*
* Modify End at 2021-06-07 PM
*/

guc.c增加check函数,该函数由函数指针指向,在initdb以及start环节check该参数合法性

/*
* Modify by Nickxyang at 2021-06-07 PM
*/
static bool
check_dbconnlimit(int *newval, void **extra, GucSource source)
{
if ((*newval + autovacuum_max_workers + 1 +
max_worker_processes + max_wal_senders > MAX_BACKENDS) && *newval > MaxConnections)
return false;
return true;
}
/*
* Modify End at 2021-06-07 PM
*/

postgresql.conf.sample

#db_connlimit = -1 # range -1 to max_connections
  • 保证对原生影响不大,参数关闭时,不影响原生逻辑
  • 可靠性,保证达到最大连接数时,处理逻辑和原生一致

postinit.c 中InitPostgres函数

/*
* Modify by Nickxyang at 2021-06-07 PM
*
* If the DbConnlimit parameter is configured
* and the number of connections reaches DbConnlimit - ReservedBackends,
* non-superuser will not be able to create new connections
*/
if (DbConnlimit > 0 && DbConnlimit  0 &&
!HaveNFreeProcs(MaxConnections - DbConnlimit + ReservedBackends))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg('remaining connection slots are reserved for non-replication superuser connections,please check parameter db_connlimit.')));

if (!am_walsender &&
!HaveNFreeProcs(MaxConnections - DbConnlimit))
ereport(FATAL,
(errcode(ERRCODE_TOO_MANY_CONNECTIONS),
errmsg('sorry, too many clients already,please check parameter db_connlimit.')));
}
/* Modify End at 2021-06-07 PM */

这里处理逻辑完全沿用原来的逻辑,老的逻辑保留着,加入了db_connlimit参数的控制逻辑。If条件中DbConnlimit > 0可以保证在使用默认值-1时不进入该逻辑。

另外判断连接数是否已满HaveNFreeProcs函数修改了入参
针对非超级用户的判断逻辑,入参为:MaxConnections – DbConnlimit + ReservedBackends,
针对所有用户入的判断逻辑,入参为(包涵ReservedBackends):MaxConnections - DbConnlimit

该函数是通过查看共享内存ProcGlobal->freeProcs列表长度来判断是否连接数已满。

  • 最大值生效问题,有效最大值必须不超过MaxConnections

guc-file.c 中ProcessConfigFileInternal函数

/* Modify by Nickxyang at 2021-06-07 PM */
char *dbconnectlimit = 'db_connlimit';
/* Modify End at 2021-06-07 PM */
/* Modify by Nickxyang at 2021-06-07 PM* When the db_connlimit configuration is greater than MaxConnections * or is less than ReservedBackends (when ReservedBackends > 0)* reload will not be effective */if (strcmp(item->name,dbconnectlimit) == 0 && atoi(item->value) != -1&& (atoi(item->value) > MaxConnections || atoi(item->value)  ReservedBackends || atoi(item->value) == 0)){if (ReservedBackends > 0 && atoi(item->value)  ReservedBackends){ereport(elevel,(errmsg('parameter '%s' is not set to '%s', because it must be bigger than superuser_reserved_connections=%d',item->name, item->value,ReservedBackends)));}else{if (atoi(item->value) == 0){ereport(elevel,(errmsg('parameter '%s' is not set to '%s', because this will not allow non-superuser to login',item->name, item->value)));}else{ereport(elevel,(errmsg('parameter '%s' is not set to '%s', because it is bigger than max_connnections=%d',item->name, item->value, MaxConnections)));}}scres = -1;}else/* Modify End at 2021-06-07 PM*/scres = set_config_option(item->name, item->value,context, PGC_S_FILE,GUC_ACTION_SET, applySettings, 0, false);

这里只有当修改了db_connlimit 参数,并且参数值不满足要求(值大于MaxConnections 或者小于ReservedBackends或者值等于0)才会进入该逻辑,会在pglog里边记录一条设置失败的信息。并把scres赋值为-1,不会调用set_config_option函数使参数修改值生效。

如果不修改或者关闭db_connlimit参数,还是原生的逻辑。

四、测试验证

编译安装并启动数据库,查看参数信息

postgres=# select * from pg_settings where name='db_connlimit';
-[ RECORD 1 ]---+----------------------------------------------------------------------------
name | db_connlimit
setting | -1
unit |
category | Connections and Authentication / Connection Settings
short_desc | Sets the maximum number of concurrent connections, which can be set online.
extra_desc |
context | sighup
vartype | integer
source | configuration file
min_val | -1
max_val | 262143
enumvals |
boot_val | -1
reset_val | -1
sourcefile | /data/pg13-0debug/data/postgresql.conf
sourceline | 3
pending_restart | f

postgres=#
  • 场景一:达到最大连接数,看看是否会出现无法连接的情况

修改参数值为5

postgres=# alter system set db_connlimit to 5 ;
ALTER SYSTEM
postgres=# select pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)

postgres=# show db_connlimit ;
db_connlimit
--------------
5
(1 row)

postgres=# show max_connections ;
max_connections
-----------------
10
(1 row)

postgres=# show superuser_reserved_connections ;
superuser_reserved_connections
--------------------------------
3
(1 row)

postgres=#

可以看到使用testuser(testuser是非超级用户)建立2个连接后,非管理员就不能再新建连接了

[postgres@NickCentos:pg13.0:5432 ~]$psql -U testuser -d postgres
psql: error: could not connect to server: FATAL: remaining connection slots are reserved for non-replication superuser connections, please check parameter db_connlimit
[postgres@NickCentos:pg13.0:5432 ~]$psql -U testuser -d postgres
psql: error: could not connect to server: FATAL: remaining connection slots are reserved for non-replication superuser connections, please check parameter db_connlimit
[postgres@NickCentos:pg13.0:5432 ~]$psql -U postgres -d postgres
psql (13.0)
PG干O,天天象上!

Type 'help' for help.

postgres=# select count(1),usename from pg_stat_activity where backend_type='client backend' group by 2 order by 1;
count | usename
-------+----------
1 | postgres
2 | testuser
(2 rows)

postgres=#

当连接数达到db_connlimit 后,superuser也无法登陆了

postgres=# select count(1),usename from pg_stat_activity where backend_type='client backend' group by 2 order by 1;
count | usename
-------+----------
2 | testuser
3 | postgres
(2 rows)

postgres=# ^Z
[1]+ Stopped psql -U postgres -d postgres
[postgres@NickCentos:pg13.0:5432 ~]$psql -U postgres -d postgres
psql: error: could not connect to server: FATAL: sorry, too many clients already, please check parameter db_connlimit
[postgres@NickCentos:pg13.0:5432 ~]$psql -U postgres -d postgres
psql: error: could not connect to server: FATAL: sorry, too many clients already, please check parameter db_connlimit
[postgres@NickCentos:pg13.0:5432 ~]$

这里要修改连接数就需要手动改文件,pg_ctl reload生效,刚好我们验证下在线增加功能,
结果来看是生效了。

[postgres@NickCentos:pg13.0:5432 ~]$psql -U postgres -d postgres
psql: error: could not connect to server: FATAL: sorry, too many clients already, please check parameter db_connlimit
[postgres@NickCentos:pg13.0:5432 ~]$psql -U postgres -d postgres
psql: error: could not connect to server: FATAL: sorry, too many clients already, please check parameter db_connlimit
[postgres@NickCentos:pg13.0:5432 ~]$vim $PGDATA/postgresql.conf
[postgres@NickCentos:pg13.0:5432 ~]$vim $PGDATA/postgresql.auto.conf
[postgres@NickCentos:pg13.0:5432 ~]$grep db_connlimit $PGDATA/postgresql*conf
/data/pg13-0debug/data/postgresql.auto.conf:db_connlimit = '8'
/data/pg13-0debug/data/postgresql.conf:db_connlimit = 8 # range -1 to max_connections
[postgres@NickCentos:pg13.0:5432 ~]$pg_ctl reload
server signaled
[postgres@NickCentos:pg13.0:5432 ~]$psql -U postgres -d postgres
psql (13.0)
PG干O,天天象上!

Type 'help' for help.

postgres=# select count(1),usename from pg_stat_activity where backend_type='client backend' group by 2 order by 1;
count | usename
-------+----------
2 | testuser
4 | postgres
(2 rows)

postgres=#
  • 场景二:修改参数为0或者大于MaxConnections 看看是否生效
postgres=# alter system set db_connlimit to 11 ;
ALTER SYSTEM
postgres=# select pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)

postgres=# show db_connlimit ;
db_connlimit
--------------
8
(1 row)

postgres=# alter system set db_connlimit to 0 ;
ALTER SYSTEM
postgres=# select pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)

postgres=# show db_connlimit ;
db_connlimit
--------------
8
(1 row)

postgres=# alter system set db_connlimit to 2 ;
ALTER SYSTEM
postgres=# select pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)

postgres=# show db_connlimit ;
db_connlimit
--------------
8
(1 row)

postgres=#

这里并未生效,可以看到日志里有打印原因。

2021-06-07 14:36:48.866 CST [720] LOG: received SIGHUP, reloading configuration files
2021-06-07 14:36:48.866 CST [720] LOG: parameter 'db_connlimit' is not set to '11', because it is bigger than max_connnections=10.
2021-06-07 14:37:29.966 CST [720] LOG: received SIGHUP, reloading configuration files
2021-06-07 14:37:29.967 CST [720] LOG: parameter 'db_connlimit' is not set to '0', because it must be bigger than superuser_reserved_connections=3.
2021-06-07 14:37:47.122 CST [720] LOG: received SIGHUP, reloading configuration files
2021-06-07 14:37:47.123 CST [720] LOG: parameter 'db_connlimit' is not set to '2', because it must be bigger than superuser_reserved_connections=3.

场景三:修改为其他无效值

  • 无效值不会被识别

postgres=# alter system set db_connlimit to -2 ;
ERROR: -2 is outside the valid range for parameter 'db_connlimit' (-1 .. 262143)
postgres=# alter system set db_connlimit to a ;
ERROR: invalid value for parameter 'db_connlimit': 'a'
postgres=# alter system set db_connlimit to # ;
ERROR: syntax error at or near '#'
LINE 1: alter system set db_connlimit to # ;
^
postgres=#
  • 场景四:修改为-1(关闭参数)看原生逻辑是否正常

配置如下:

postgres=# alter system set db_connlimit to -1 ;
ALTER SYSTEM
postgres=# select pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)

postgres=# show db_connlimit ;
db_connlimit
--------------
-1
(1 row)

postgres=# show max_connections ;
max_connections
-----------------
10
(1 row)

postgres=# show superuser_reserved_connections ;
superuser_reserved_connections
--------------------------------
3
(1 row)

postgres=#

这里非超级用户只能建立7个连接,可以看到结果是正常的。

[postgres@NickCentos:pg13.0:5432 ~]$psql -U testuser -d postgres
psql: error: could not connect to server: FATAL: remaining connection slots are reserved for non-replication superuser connections
[postgres@NickCentos:pg13.0:5432 ~]$psql -U testuser -d postgres
psql: error: could not connect to server: FATAL: remaining connection slots are reserved for non-replication superuser connections
[postgres@NickCentos:pg13.0:5432 ~]$psql -U postgres -d postgres
psql (13.0)
PG干O,天天象上!

Type 'help' for help.

postgres=# select count(1),usename from pg_stat_activity where backend_type='client backend' group by 2 order by 1;
count | usename
-------+----------
1 | postgres
7 | testuser
(2 rows)

postgres=#

后续也进行了多次测试是符合预期的。

五、小结

尝试加入在线管理参数db_connlimit初步验证是符合预期的。这只是一个设想的方案,适不适合生产还得慎重考虑。在应用连接数异常暴增且不能重启的场景下,这个方案可以缓解一些压力,不至于修改最大连接数但不能重启生效那么尴尬,但是同时要考虑信号量等系统资源问题。

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认