从PostgreSQL支持100万个连接聊起

背景

100万个数据库连接,绝逼疯了,常人怎么会干这种事情。

没错,数据库支持100万个连接意味着什么呢?不能用连接池吗?

除了抱着玩一玩的心态,也能了解到操作系统层的一些知识,何乐不为?

碰壁

根据我前几天写的《如何度量Kernel Resources for PostgreSQL》,我们可以评估得出,如果要支持100万个数据库客户端连接,操作系统应该如何配置。

但是实际上能如愿吗?

以PostgreSQL 9.5为例,100万个连接,需要多少信号量?

  • 需要多少组信号量?

    SEMMNI >= (max_connections + max_worker_processes + autovacuum_max_workers + 5) / 16
    

    100万连接,SEMMNI >= 62500

  • 需要多少信号量?
    SEMMNS >= ((max_connections + max_worker_processes + autovacuum_max_workers + 5) / 16) * 17 + 其他程序的需求
    

    100万连接,SEMMNS >= 1062500

  • 每组需要多少信号量?
    SEMMSL >= 17
    

测试环境如下

CentOS 6.x x64, 512GB内存。

kernel.sem = 18 2147483647      2147483646      512000000  

max number of arrays = 512000000
max semaphores per array = 18
max semaphores system wide = 2147483647
max ops per semop call = 2147483646

以上内核配置,信号量完全满足100万连接需求。

那么数据库能启动吗?

vi postgresql.conf
max_connections = 1000000

pg_ctl start

启动失败。

原因分析

报错来自如下代码:
使用semget创建sem失败。
src/backend/port/sysv_sema.c

       /*
   76  * InternalIpcSemaphoreCreate
   77  *
   78  * Attempt to create a new semaphore set with the specified key.
   79  * Will fail (return -1) if such a set already exists.
   80  *
   81  * If we fail with a failure code other than collision-with-existing-set,
   82  * print out an error and abort.  Other types of errors suggest nonrecoverable
   83  * problems.
   84  */
   85 static IpcSemaphoreId
   86 InternalIpcSemaphoreCreate(IpcSemaphoreKey semKey, int numSems)
   87 {
   88     int         semId;
   89
   90     semId = semget(semKey, numSems, IPC_CREAT | IPC_EXCL | IPCProtection);
   91
   92     if (semId < 0)
   93     {
   94         int         saved_errno = errno;
   95
   96         /*
   97          * Fail quietly if error indicates a collision with existing set. One
   98          * would expect EEXIST, given that we said IPC_EXCL, but perhaps we
   99          * could get a permission violation instead?  Also, EIDRM might occur
  100          * if an old set is slated for destruction but not gone yet.
  101          */
  102         if (saved_errno == EEXIST || saved_errno == EACCES
  103 #ifdef EIDRM
  104             || saved_errno == EIDRM
  105 #endif
  106             )
  107             return -1;
  108
  109         /*
  110          * Else complain and abort
  111          */
  112         ereport(FATAL,
  113                 (errmsg("could not create semaphores: %m"),
  114                  errdetail("Failed system call was semget(%lu, %d, 0%o).",
  115                            (unsigned long) semKey, numSems,
  116                            IPC_CREAT | IPC_EXCL | IPCProtection),
  117                  (saved_errno == ENOSPC) ?
  118                  errhint("This error does not mean that you have run out of disk space.  "
  119           "It occurs when either the system limit for the maximum number of "
  120              "semaphore sets (SEMMNI), or the system wide maximum number of "
  121             "semaphores (SEMMNS), would be exceeded.  You need to raise the "
  122           "respective kernel parameter.  Alternatively, reduce PostgreSQL's "
  123                          "consumption of semaphores by reducing its max_connections parameter.\n"
  124               "The PostgreSQL documentation contains more information about "
  125                          "configuring your system for PostgreSQL.") : 0));
  126     }
  127
  128     return semId;
  129 }

semget之所以失败,并不是kernel.sem的配置问题,而是操作系统内核的宏限制。

sem的分组数量不能大于semvmx,也就是说,最多能开50多万个连接。

如下
kernels/xxx.x86_64/include/uapi/linux/sem.h

#define SEMMNI  128             / <= IPCMNI  max # of semaphore identifiers /
#define SEMMSL  250             / <= 8 000 max num of semaphores per id /
#define SEMMNS  (SEMMNISEMMSL) / <= INT_MAX max # of semaphores in system */
#define SEMOPM  32              / <= 1 000 max num of ops per semop call /
#define SEMVMX  32767           / <= 32767 semaphore maximum value /
#define SEMAEM  SEMVMX          / adjust on exit max value /

/ unused /
#define SEMUME  SEMOPM          / max num of undo entries per process /
#define SEMMNU  SEMMNS          / num of undo structures system wide /
#define SEMMAP  SEMMNS          / # of entries in semaphore map /
#define SEMUSZ  20              / sizeof struct sem_undo /

超过32767个array后会报错

$ ipcs -u

------ Semaphore Status --------
used arrays = 32768
allocated semaphores = 557056

$ pg_ctl start
FATAL:  could not create semaphores: No space left on device
DETAIL:  Failed system call was semget(1953769, 17, 03600).
HINT:  This error does not mean that you have run out of disk space.
It occurs when either the system limit for the maximum number of semaphore sets (SEMMNI),
or the system wide maximum number of semaphores (SEMMNS), would be exceeded.
You need to raise the respective kernel parameter.
Alternatively, reduce PostgreSQL's consumption of semaphores by reducing its max_connections parameter.
        The PostgreSQL documentation contains more information about configuring your system for PostgreSQL.

使用ipcs -l也能查看到当前值

semaphore max value = 32767

这个值只能重新编译内核来修改。

在semctl和semop的手册中也能看到,有此说法。

man semctl
       SEMVMX Maximum value for semval: implementation dependent (32767).

man semop
       SEMVMX Maximum allowable value for semval: implementation dependent (32767).

       The implementation has no intrinsic limits for the adjust on exit maximum value (SEMAEM), the system wide maximum number of undo structures (SEMMNU) and the per-process maximum number of undo entries system
       parameters.

100万连接没戏了吗?

当然可以办到。

例如修改SEMVMX,并重新编译内核显然是一条路,但是还有其他路子吗?

柳暗花明又一春, PostgreSQL 支持POSIX sem

我们前面看到,报错的代码是
InternalIpcSemaphoreCreate@src/backend/port/sysv_sema.c

查看对应头文件,发现PG支持几种创建信号量的方式,真的是柳暗花明又一春 :

sysv, posix(named , unamed), win32

对应的头文件源码如下
src/include/storage/pg_sema.h

/*
 * PGSemaphoreData and pointer type PGSemaphore are the data structure
 * representing an individual semaphore.  The contents of PGSemaphoreData
 * vary across implementations and must never be touched by platform-
 * independent code.  PGSemaphoreData structures are always allocated
 * in shared memory (to support implementations where the data changes during
 * lock/unlock).
 *
 * pg_config.h must define exactly one of the USE_xxx_SEMAPHORES symbols.
 */

#ifdef USE_NAMED_POSIX_SEMAPHORES

#include <semaphore.h>

typedef sem_t *PGSemaphoreData;
#endif

#ifdef USE_UNNAMED_POSIX_SEMAPHORES

#include <semaphore.h>

typedef sem_t PGSemaphoreData;
#endif

#ifdef USE_SYSV_SEMAPHORES

typedef struct PGSemaphoreData
{
        int                     semId;                  / semaphore set identifier /
        int                     semNum;                 / semaphore number within set /
} PGSemaphoreData;
#endif

#ifdef USE_WIN32_SEMAPHORES

typedef HANDLE PGSemaphoreData;
#endif

其中posix的named和unamed分别使用如下系统调用
posix named 方式创建信号 :

        mySem = sem_open(semname, O_CREAT | O_EXCL,
                         (mode_t) IPCProtection, (unsigned) 1);

posix unamed 方式创建信号 :

  sem_init(sem, 1, 1)

posix源码如下,注意用到的宏
src/backend/port/posix_sema.c

#ifdef USE_NAMED_POSIX_SEMAPHORES

/*
 * PosixSemaphoreCreate
 *
 * Attempt to create a new named semaphore.
 *
 * If we fail with a failure code other than collision-with-existing-sema,
 * print out an error and abort.  Other types of errors suggest nonrecoverable
 * problems.
 */
static sem_t *
PosixSemaphoreCreate(void)
{
    int         semKey;
    char        semname[64];
    sem_t      *mySem;

    for (;;)
    {
        semKey = nextSemKey++;

        snprintf(semname, sizeof(semname), "/pgsql-%d", semKey);

        mySem = sem_open(semname, O_CREAT | O_EXCL,
                         (mode_t) IPCProtection, (unsigned) 1);

#ifdef SEM_FAILED
        if (mySem != (sem_t *) SEM_FAILED)
            break;
#else
        if (mySem != (sem_t *) (-1))
            break;
#endif

        / Loop if error indicates a collision /
        if (errno == EEXIST || errno == EACCES || errno == EINTR)
            continue;

        /*
         * Else complain and abort
         */
        elog(FATAL, "sem_open(\"%s\") failed: %m", semname);
    }

    /*
     * Unlink the semaphore immediately, so it can't be accessed externally.
     * This also ensures that it will go away if we crash.
     */
    sem_unlink(semname);

    return mySem;
}
#else                           / !USE_NAMED_POSIX_SEMAPHORES /

/*
 * PosixSemaphoreCreate
 *
 * Attempt to create a new unnamed semaphore.
 */
static void
PosixSemaphoreCreate(sem_t * sem)
{
    if (sem_init(sem, 1, 1) < 0)
        elog(FATAL, "sem_init failed: %m");
}
#endif   / USE_NAMED_POSIX_SEMAPHORES /

从src/include/storage/pg_sema.h 可以看到,在pg_config.h中必须有一个指定的USE_xxx_SEMAPHORES symbols。

这个symbol不是直接设置pg_config.h来的,是在configure时设置的,会自动加到pg_config.h 。

 Select semaphore implementation type.
if test "$PORTNAME" != "win32"; then
  if test x"$USE_NAMED_POSIX_SEMAPHORES" = x"1" ; then

$as_echo "#define USE_NAMED_POSIX_SEMAPHORES 1" >>confdefs.h

    SEMA_IMPLEMENTATION="src/backend/port/posix_sema.c"
  else
    if test x"$USE_UNNAMED_POSIX_SEMAPHORES" = x"1" ; then

$as_echo "#define USE_UNNAMED_POSIX_SEMAPHORES 1" >>confdefs.h

      SEMA_IMPLEMENTATION="src/backend/port/posix_sema.c"
    else

$as_echo "#define USE_SYSV_SEMAPHORES 1" >>confdefs.h

      SEMA_IMPLEMENTATION="src/backend/port/sysv_sema.c"
    fi
  fi
else

$as_echo "#define USE_WIN32_SEMAPHORES 1" >>confdefs.h

  SEMA_IMPLEMENTATION="src/backend/port/win32_sema.c"
fi

默认使用SYSV,如果要使用其他的sem方法。

可以这么做

export USE_UNNAMED_POSIX_SEMAPHORES=1
LIBS=-lpthread ./configure  --prefix=/home/digoal/pgsql9.5

记得加-lpthread ,否则报错

/bin/ld: port/pg_sema.o: undefined reference to symbol 'sem_close@@GLIBC_2.2.5'
/bin/ld: note: 'sem_close@@GLIBC_2.2.5' is defined in DSO /lib64/libpthread.so.0 so try adding it to the linker command line
/lib64/libpthread.so.0: could not read symbols: Invalid operation
collect2: error: ld returned 1 exit status
make[2]: * [postgres] Error 1
make[2]: Leaving directory `/home/digoal/postgresql-9.5.3/src/backend'
make[1]: * [all-backend-recurse] Error 2
make[1]: Leaving directory `/home/digoal/postgresql-9.5.3/src'
make: * [world-src-recurse] Error 2

通过这些系统调用的Linux编程帮助文档,了解一下posix的信号量管理

man sem_overview
man sem_init , unnamed sem
man sem_open , named sem

        EINVAL value was greater than SEM_VALUE_MAX.

可以得知sem_open 也受到semvmx的限制

因此为了在不修改内核的情况下,实现PostgreSQL支持100万个连接,甚至更多。

必须使用USE_UNNAMED_POSIX_SEMAPHORES

开工,让PostgreSQL支持100万个连接

使用USE_UNNAMED_POSIX_SEMAPHORES编译

export USE_UNNAMED_POSIX_SEMAPHORES=1
LIBS=-lpthread ./configure  --prefix=/home/digoal/pgsql9.5
make world -j 32
make install-world -j 32

修改参数,允许100万个连接

vi postgresql.conf
max_connections = 1000000

重启数据库

pg_ctl restart -m fast

测试100万个数据库并发连接

pgbench是很好的测试工具,只不过限制了1024个连接,为了支持100万个连接测试,需要修改一下。

代码

vi src/bin/pgbench/pgbench.c
#ifdef WIN32
#define FD_SETSIZE 1024                     / set before winsock2.h is included /
#endif   / ! WIN32 /

/ max number of clients allowed /
#ifdef FD_SETSIZE
#define MAXCLIENTS      (FD_SETSIZE - 10)
#else
#define MAXCLIENTS      1024
#endif

                        case 'c':
                                benchmarking_option_set = true;
                                nclients = atoi(optarg);

                if (nclients <= 0)  // 改一下这里 || nclients > MAXCLIENTS)
                                {
                                        fprintf(stderr, "invalid number of clients: \"%s\"\n",
                                                        optarg);
                                        exit(1);
                                }

测试表

postgres=# create unlogged table test(id int, info text);
CREATE TABLE

测试脚本

vi test.sql

\setrandom s 1 100
select pg_sleep(:s);
insert into test values (1,'test');

继续碰壁

开始压测,遇到第一个问题

pgbench -M prepared -n -r -f ./test.sql -c 999900 -j 1 -T 10000
  need at least 999903 open files, but system limit is 655360
  Reduce number of clients, or use limit/ulimit to increase the system limit.

这个问题还好,是打开文件数受限,改一些限制就可以解决

修改ulimit

vi /etc/security/limits.conf
* soft nofile 1048576
* hard nofile 1048576
* soft noproc 10000000
* hard noproc 10000000
* soft memlock unlimited
* hard memlock unlimited

修改内核参数

sysctl -w fs.file-max=419430400000

重测,再次遇到问题,原因是pgbench使用了ip地址连接PG,导致pgbench的动态端口耗尽。

pgbench -M prepared -n -r -f ./test.sql -c 999900 -j 1 -T 10000
connection to database "postgres" failed:
could not connect to server: Cannot assign requested address
        Is the server running on host "127.0.0.1" and accepting
        TCP/IP connections on port 1921?
transaction type: Custom query
scaling factor: 1
query mode: prepared
number of clients: 999900
number of threads: 1
duration: 10000 s
number of transactions actually processed: 0

换成unix socket连接即可解决。

pgbench -M prepared -n -r -f ./test.sql -c 999900 -j 1 -T 10000 -h $PGDATA
connection to database "postgres" failed:
could not fork new process for connection: Cannot allocate memory

could not fork new process for connection: Cannot allocate memory
transaction type: Custom query
scaling factor: 1
query mode: prepared
number of clients: 999900
number of threads: 1
duration: 10000 s
number of transactions actually processed: 0

不能fork new process,后面跟了个Cannot allocate memory这样的提示,我看了当前的配置

vm.swappiness = 0
vm.overcommit_memory = 0

于是我加了交换分区,同时改了几个参数

dd if=/dev/zero of=./swap1 bs=1024k count=102400 oflag=direct
mkswap ./swap1
swapon ./swap1
sysctl -w vm.overcommit_memory=1     (always over commit)
sysctl -w vm.swappiness=1

重新测试,发现还是有问题

pgbench -M prepared -n  -f ./test.sql -c 999900 -j 1 -T 10000 -h $PGDATA

could not fork new process for connection: Cannot allocate memory

使用以下手段观测,发现在约连接到 65535 时报错

sar -r 1 10000

psql
select count(*) from pg_stat_activity;
\watch 1

支持百万连接目标达成

找到了根源,是内核限制了

kernel.pid_max=65535

修改一下这个内核参数

sysctl -w kernel.pid_max=4096000

重新测试

pgbench -M prepared -n  -f ./test.sql -c 999900 -j 1 -T 10000 -h $PGDATA

继续观测

psql
select count(*) from pg_stat_activity;
\watch 1

sar -r 1 10000

连接到26万时,内存用了约330GB,每个连接1MB左右。

看起来应该没有问题了,只要内存足够是可以搞定100万连接的。

小结

为了让PostgreSQL支持100万个并发连接,除了资源(主要是内存)要给足。

数据库本身编译也需要注意,还需要操作系统内核也需要一些调整。

  1. 编译PostgreSQL 时使用 posix unname sem 。

    export USE_UNNAMED_POSIX_SEMAPHORES=1
    LIBS=-lpthread ./configure  --prefix=/home/digoal/pgsql9.5
    make world -j 32
    make install-world -j 32
    

    如果你不打算使用unnamed posix sem,那么务必重新编译操作系统内核,增加SEMVMX.

  2. 打开文件数限制
    ulimit
    vi /etc/security/limits.conf
    * soft nofile 1048576
    * hard nofile 1048576
    * soft noproc 10000000
    * hard noproc 10000000
    

    修改内核参数

    sysctl -w fs.file-max=419430400000
    
  3. 使用unix socket
    突破pgbench测试时,动态端口数量限制。
  4. 每个连接约1MB,100万个连接,需要约1TB内存,需要给足内存。
    启用swap
    dd if=/dev/zero of=./swap1 bs=1024k count=102400 oflag=direct
    mkswap ./swap1
    swapon ./swap1
    .
    .
    sysctl -w vm.overcommit_memory=0
    sysctl -w vm.swappiness=1
    

    实际上还是发生了OOM,而且hang了很久。

    [67504.841109] Memory cgroup out of memory: Kill process 385438 (pidof) score 721 or sacrifice child
    [67504.850566] Killed process 385438, UID 0, (pidof) total-vm:982240kB, anon-rss:978356kB, file-rss:544kB
    [67517.496404] pidof invoked oom-killer: gfp_mask=0xd0, order=0, oom_adj=0, oom_score_adj=0
    [67517.496407] pidof cpuset=/ mems_allowed=0
    [67517.496410] Pid: 385469, comm: pidof Tainted: G           --------------- H
    
  5. 最大PID值的限制
    加大
    sysctl -w kernel.pid_max=4096000
    
  6. pgbench客户端的限制
    修改源码,支持无限连接。
  7. ipcs不统计posix sem的信息,所以使用posix sem后ipcs看不到用了多少sem.
  8. System V 与 POSIX sem
    NOTES
       System V semaphores (semget(2), semop(2), etc.) are an older semaphore API.
       POSIX semaphores provide a simpler, and better designed interface than System V semaphores;
       on the other hand  POSIX  semaphores  are  less  widely available (especially on older systems) than System V semaphores.
    

参考

  • 《DBA不可不知的操作系统内核参数》
  • 《如何度量Kernel Resources for PostgreSQL》

祝大家玩得开心,欢迎随时来 阿里云促膝长谈业务需求 ,恭候光临。

阿里云的小伙伴们加油,努力 做好内核与服务,打造最贴地气的云数据库

时间: 2024-10-30 19:40:45

从PostgreSQL支持100万个连接聊起的相关文章

PostgreSQL支持100万个连接测试详解

背景 100万个数据库连接,绝逼疯了,常人怎么会干这种事情. 没错,数据库支持100万个连接意味着什么呢?不能用连接池吗? 除了抱着玩一玩的心态,也能了解到操作系统层的一些知识,何乐不为? 碰壁 根据我前几天写的<如何度量Kernel Resources for PostgreSQL>,我们可以评估得出,如果要支持100万个数据库客户端连接,操作系统应该如何配置. https://yq.aliyun.com/articles/58690 但是实际上能如愿吗? 以PostgreSQL 9.5为例

百度说吧推出“群聊”功能 最多支持100人

中介交易 http://www.aliyun.com/zixun/aggregation/6858.html">SEO诊断 淘宝客 云主机 技术大厅 11月9日消息 日前,百度社交平台说吧(http://t.baidu.com/)正式推出"群组"功能,允许用户自由创建.加入各种基于真实身份的社交圈子,并提供集体讨论交流服务.外群组的创建权限对于所有通过实名制认证的用户均免费开放,私密群最多支持50人.公开群最多支持100人. 据了解,百度说吧开创了私密和开放式两种群组互

原墨迹天气联合创始人聊比特币:用100万赚到1300万

赵东TechWeb 12月4日报道 文/韩远海"那您现在持有比特币大约多少枚?""还是买入时候那个数目吧,2000个左右." 于是我在心里用当下比特币币值快速地算了一下:以当下比特币对人民币的币值估算,这2000枚比特币价值为1300多万人民币--原来在我面前坐着一位千万富翁.百万投入换回千万回报赵东,原墨迹天气联合创始人兼CTO,去年退出墨迹天气团队,卖掉了股份,又因为觉得房价过于泡沫卖掉了房子,于是手里颇有资金的他,拿出了其中闲置的100万,于今年3月开始了比特

Shopify的Docker实战经验(二)如何用容器支持10万的在线商店

本文讲的是Shopify的Docker实战经验(二)如何用容器支持10万的在线商店,[编者的话]Shopify是一个电子商务平台,提供专业的网上店面.目前的客户超过12万,包括GE.特斯拉汽车.GitHub等.作为首家市值超过10亿美元的加拿大网络公司,Shopify在欧美市场的影响力也与日俱增.Shopify是一个大型的Ruby on Rails应用,其产品服务器能通过给1700个处理核心和6TB RAM分配任务来完成每秒处理8000多个请求.Shopify在其博客上分享了系列内容来介绍他们的

随着下一个预览版本的推出,Linux SQL Server 2017镜像下载量超过了100万

本文讲的是随着下一个预览版本的推出,Linux SQL Server 2017镜像下载量超过了100万[译者的话]这篇文章介绍了在Linux Container中运行SQL Server的方案及其优势以及部分客户案例.SQL Server在很多企业中广泛运用,其容器化所带来的效益值得我们思考和借鉴. [3 天烧脑式容器存储网络训练营 | 深圳站]本次培训以容器存储和网络为主题,包括:Docker Plugin.Docker storage driver.Docker Volume Pulgin.

连续创业者说:如何获得你的前100万个用户?

到达第一个"百万用户",社交平台Twitter用了24个月,图片视觉分享网站Pinterest用了20个月,地理签到应用Foursquare 用了13个月,社交媒体Facebook用了10个月,网络文件共享工具Dropbox用了7个月,移动图片分享应用Instagram用了2个半月,而Path 2.0仅用了2周就达到了100万次的下载量.那么,你又如何获取第一百万个用户?连续创业者Michael Geer和大家分享了他的经验.你是否也认为好的产品就一定会吸引用户?是否也相信媒体报道最能

网络安全人才缺口100万 公司不得不外包安全保障

7月25日消息,安全漏洞攻击和信息安全问题每天都会出现在新闻上,然而比这些攻击本身更让人担心的是,专家们表示,网络安全行业根本没有足够的人才来应对这些攻击. 一份来自网络巨头思科公司的报告显示,全球网络安全行业人才缺口达到100万.而国际信息系统审计协会(ISACA,Information Systems Audit and Control Association)2015年发布的报告也显示,其86%的会员认为网络安全行业人手不足,只有38%的会员认为自己已经准备好了应对复杂的网络攻击. ISA

马化腾创业史:QQ卖100万被拒VC砸钱助上市

创业 "从1998年开始,我就考虑独立创业,却一直没想清楚要做什么,但创业的想法并没有起伏,我知道自己对着迷的事情完全有能力做好.我感觉可以在寻呼与网络两大资源中找到空间." 马化腾是个崇尚共享.自由精神的人,不会单纯强调"我"的价值,他知道团队的意义.腾讯的几个创始人都曾在深圳电信.网络界有多年的从业经验,几乎是深圳第一批搞互联网的人,这无疑可以在技术和业务层面为腾讯提供很多帮助."创业不是说着玩的事,腾讯也并非一帆风顺.一开始,我们的服务器都无处托管,

借势腾讯应用宝红码 招行掌上生活新增下载100万

中介交易 SEO诊断 淘宝客 云主机 技术大厅 移动互联网时代,大流量带来"变现"红利已成为一个不争的事实.刚在北京举行的全球互联网大会(简称GMIC)上,招商银行信用卡市场部相关负责人宣布,借助腾讯应用宝红码带来的上亿级流量,招商银行信用卡"掌上生活"手机客户端新增下载量超过100万.截止目前,招商银行信用卡掌上生活已经实现累计安装1300万户,牢牢占据了信用卡行业手机客户端安装量的第一把交椅. 招商银行信用卡中心市场企划部副总经理曹慰在腾讯开发者沙龙发表主题演讲