背景

进程模型数据库,需要为每个会话指派独立的进程与之服务,在连接数非常多,且大都是活跃连接时,进程调度浪费或引入的开销甚至远远大于实际任务需要的开销(例如上下文切换,MEMCPY等),性能下降会较为严重。

pic

PostgreSQL与Oracle Dedicate Server一样,属于进程模型。在非常高并发的情况下,性能会下降比较厉害,通常社区版本可以通过加连接池来解决,例如pgbouncer,但是加连接池也会带来一些问题:

1、绑定变量无法很好的满足,当然,PostgreSQL 11会增加类似Oracle cursor force的功能,内部将非绑定变量的SQL转换为绑定变量。

《PostgreSQL 11 preview - 强制auto prepared statment开关(自动化plan cache)(类似Oracle cursor_sharing force)》

2、连接池会使得跳数增加,增加了延迟。

3、数据库防火墙配置的变化。从直接控制应用端来源,变成了连接池端来源。(除非修改连接池层的代码,做到来源IP和端口透传)

Oracle为了解决性能问题,提出了shared server的概念,类似数据库端的backend process pool,一个process可能服务于多个client。

PostgreSQL也可以如法炮制,比如阿里云RDS PG内核层面增加了内置的POOL。在高并发的情况下,性能好很多。

测试CASE

1、测试64 ~ 16384个并发

2、测试TPC-B,包含5亿数据量。

3、测试logged table与unlogged table

4、测试对比社区PostgreSQL 10 与 阿里云PostgreSQL 10

测试环境准备

1、数据库使用huge page

《PostgreSQL Huge Page 使用建议 - 大内存主机、实例注意》

2、修改pgbench,支持超过1000个连接的测试

《PostgreSQL 11 preview - pgbench 支持大于1000链接(ppoll()代替select())》

https://commitfest.postgresql.org/18/1388/

《从PostgreSQL支持100万个连接聊起》

如果使用ppoll,则pstack pgbench可以看到类似如下信息

  1. Thread 1 (Thread 0x7f3f4d89d840 (LWP 116621)):
  2. #0 0x00007f3f4ca4569d in poll () from /lib64/libc.so.6
  3. #1 0x00007f3f4d45a9cf in poll (__timeout=-1, __nfds=1, __fds=0x7ffcd6e13c80) at /usr/include/bits/poll2.h:46
  4. #2 pqSocketPoll (end_time=-1, forWrite=0, forRead=28675152, sock=<optimized out>) at fe-misc.c:1129
  5. #3 pqSocketCheck (conn=conn@entry=0x1b58c50, forRead=forRead@entry=1, forWrite=forWrite@entry=0, end_time=end_time@entry=-1) at fe-misc.c:1071
  6. #4 0x00007f3f4d45aa50 in pqWaitTimed (forRead=forRead@entry=1, forWrite=forWrite@entry=0, conn=conn@entry=0x1b58c50, finish_time=finish_time@entry=-1) at fe-misc.c:1003
  7. #5 0x00007f3f4d454012 in connectDBComplete (conn=0x1b58c50) at fe-connect.c:1902
  8. #6 PQconnectdbParams (keywords=<optimized out>, values=<optimized out>, expand_dbname=<optimized out>) at fe-connect.c:542
  9. #7 0x000000000040576a in doConnect ()
  10. #8 0x0000000000406e29 in threadRun ()
  11. #9 0x0000000000403a1b in main ()

3、修改系统配置,保证有足够的fd, proc等

《PostgreSQL 10 + PostGIS + Sharding(pg_pathman) + MySQL(fdw外部表) on ECS 部署指南(适合新用户) - 珍藏级》

4、postgresql.conf 通用配置

  1. listen_addresses = '0.0.0.0'
  2. max_connections = 30000
  3. superuser_reserved_connections = 13
  4. unix_socket_directories = '/tmp,.'
  5. tcp_keepalives_idle = 60
  6. tcp_keepalives_interval = 10
  7. tcp_keepalives_count = 0
  8. shared_buffers = 32GB
  9. huge_pages = on
  10. maintenance_work_mem = 1GB
  11. dynamic_shared_memory_type = posix
  12. vacuum_cost_delay = 0
  13. bgwriter_delay = 10ms
  14. bgwriter_lru_maxpages = 500
  15. effective_io_concurrency = 0
  16. max_parallel_workers_per_gather = 0
  17. wal_level = minimal
  18. fsync = on
  19. synchronous_commit = on
  20. full_page_writes = on
  21. wal_buffers = 32MB
  22. checkpoint_timeout = 15min
  23. max_wal_size = 64GB
  24. min_wal_size = 16GB
  25. checkpoint_completion_target = 0.1
  26. max_wal_senders = 0
  27. random_page_cost = 1.2
  28. log_destination = 'csvlog'
  29. logging_collector = on
  30. log_truncate_on_rotation = on
  31. log_checkpoints = on
  32. log_connections = on
  33. log_disconnections = on
  34. log_error_verbosity = verbose
  35. log_timezone = 'PRC'
  36. autovacuum = on
  37. log_autovacuum_min_duration = 0
  38. autovacuum_freeze_max_age = 900000000
  39. autovacuum_multixact_freeze_max_age = 900000000
  40. autovacuum_vacuum_cost_delay = 0ms
  41. vacuum_freeze_min_age = 500000
  42. vacuum_freeze_table_age = 1500000000
  43. vacuum_multixact_freeze_min_age = 5000000
  44. vacuum_multixact_freeze_table_age = 1500000000
  45. datestyle = 'iso, mdy'
  46. timezone = 'PRC'
  47. lc_messages = 'en_US.utf8'
  48. lc_monetary = 'en_US.utf8'
  49. lc_numeric = 'en_US.utf8'
  50. lc_time = 'en_US.utf8'
  51. default_text_search_config = 'pg_catalog.english'

5、社区版本与阿里云版本的差异配置
native

  1. port = 1921

aliyun

  1. port = 1999
  2. shared_preload_libraries = 'pg_concurrency_control.so'
  3. pg_concurrency_control.query_concurrency=64
  4. pg_concurrency_control.bigquery_concurrency=64
  5. pg_concurrency_control.transaction_concurrency=64
  6. pg_concurrency_control.autocommit_concurrency=64

测试TPC-B

TPC-B测试SQL如下

scale=5000

  1. \set aid random(1, 100000 * :scale)
  2. \set bid random(1, 1 * :scale)
  3. \set tid random(1, 10 * :scale)
  4. \set delta random(-5000, 5000)
  5. BEGIN;
  6. UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
  7. SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
  8. UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
  9. UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
  10. INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
  11. END;

logged table

1、初始化

  1. ./pgsql11/bin/pgbench -i -s 5000

2、表大小

  1. postgres=# \dt+
  2. List of relations
  3. Schema | Name | Type | Owner | Size | Description
  4. --------+------------------+-------+----------+---------+-------------
  5. public | pgbench_accounts | table | postgres | 63 GB |
  6. public | pgbench_branches | table | postgres | 216 kB |
  7. public | pgbench_history | table | postgres | 0 bytes |
  8. public | pgbench_tellers | table | postgres | 2200 kB |
  9. (4 rows)

3、社区版本测试脚本如下

  1. vi test_native.sh
  2. #!/bin/bash
  3. export PGHOST=/tmp
  4. export PGPORT=1921
  5. export PGUSER=postgres
  6. export PGDATABASE=postgres
  7. Y=32
  8. for ((i=1;i<=7;i++))
  9. do
  10. X=$((Y*2))
  11. psql -c "vacuum freeze"
  12. psql -c "checkpoint"
  13. ./pgsql11/bin/pgbench -M prepared -n -r -P 3 -c $X -j 64 -T 300 > ./test_native_$X.log 2>&1
  14. Y=X
  15. done
  16. psql -c "vacuum freeze"
  17. psql -c "checkpoint"
  18. ./pgsql11/bin/pgbench -M prepared -n -r -P 3 -c 8192 -j 128 -T 600 > ./test_native_8192.log 2>&1
  19. psql -c "vacuum freeze"
  20. psql -c "checkpoint"
  21. ./pgsql11/bin/pgbench -M prepared -n -r -P 3 -c 16384 -j 256 -T 600 > ./test_native_16384.log 2>&1

测试方法

  1. chmod 700 ./test_native.sh
  2. nohup ./test_native.sh >/dev/null 2>&1 &

5、阿里云版本测试脚本如下

  1. vi test_aliyun.sh
  2. #!/bin/bash
  3. export PGHOST=/tmp
  4. export PGPORT=1999
  5. export PGUSER=postgres
  6. export PGDATABASE=postgres
  7. Y=32
  8. for ((i=1;i<=7;i++))
  9. do
  10. X=$((Y*2))
  11. psql -c "vacuum freeze"
  12. psql -c "checkpoint"
  13. ./pgsql11/bin/pgbench -M prepared -n -r -P 3 -c $X -j 64 -T 300 > ./test_aliyun_$X.log 2>&1
  14. Y=X
  15. done
  16. psql -c "vacuum freeze"
  17. psql -c "checkpoint"
  18. ./pgsql11/bin/pgbench -M prepared -n -r -P 3 -c 8192 -j 128 -T 600 > ./test_aliyun_8192.log 2>&1
  19. psql -c "vacuum freeze"
  20. psql -c "checkpoint"
  21. ./pgsql11/bin/pgbench -M prepared -n -r -P 3 -c 16384 -j 256 -T 600 > ./test_aliyun_16384.log 2>&1

测试方法

  1. chmod 700 ./test_aliyun.sh
  2. nohup ./test_aliyun.sh >/dev/null 2>&1 &

unlogged table

1、初始化

  1. ./pgsql11/bin/pgbench -i -s 5000 --unlogged-tables

2、修改数据库配置

  1. vi $PGDATA/postgresql.conf
  2. synchronous_commit = off
  3. pg_ctl reload

3、测试同样的脚本

性能对比

1 logged table对比

1、TPS对比

连接数社区版本TPS阿里云版本TPS社区版本TPS
(过滤首尾干扰值)
阿里云版本TPS
(过滤首尾干扰值)
646921667853无干扰无干扰
1286921165712无干扰无干扰
2566296462775无干扰无干扰
51244595533824614154988
102435055442953702248679
204826791388813032744358
409624218269903202339086
81927064243041861134316
163845046124781002029499

1.6万并发时,约3倍于社区版本。

2、事务整体RT对比

连接数社区版本RT阿里云版本RT
640.923 ms0.941 ms
1281.839 ms1.936 ms
2564.010 ms4.021 ms
51211.151 ms9.269 ms
102427.475 ms21.070 ms
204867.295 ms46.063 ms
4096127.923 ms104.689 ms
8192999.236 ms239.466 ms
163841594.185 ms577.913 ms

3、实际SQL RT对比

连接数社区版本RT阿里云版本RT
640.428 ms0.465 ms
1280.698 ms0.734 ms
2561.784 ms1.658 ms
5124.736 ms4.378 ms
102411.082 ms8.664 ms
204837.258 ms8.007 ms
409665.486 ms7.395 ms
8192818.411 ms6.472 ms
163841183.571 ms4.927 ms

1.6万连接时,真实SQL响应速度约240倍于社区版本。

4、RT 标准方差对比

连接数社区版本RT DEV阿里云版本RT DEV
642.960 ms2.863 ms
1287.559 ms4.914 ms
2566.595 ms6.090 ms
51211.810 ms8.704 ms
102430.656 ms46.411 ms
204888.371 ms68.239 ms
4096183.815 ms140.255 ms
819220114.612 ms345.584 ms
163842404.222 ms1116.238 ms

5、建立完所有连接的耗时对比

连接数社区版本阿里云版本
640 s0 s
1280 s0 s
2564.8 s5 s
5128.9 s11.3 s
102418.5 s27.4 s
204836.3 s37.8 s
409673.5 s93.6 s
8192150.9 s168.6 s
16384306 s341.8 s

6、释放完所有连接的耗时对比

连接数社区版本阿里云版本
640 s0 s
1280 s0 s
2560 s0 s
5120 s0 s
10240 s0 s
20480 s0 s
40960 s0 s
8192594 s9 s
1638421 s24 s

2 unlogged table对比

1、TPS对比

连接数社区版本TPS阿里云版本TPS社区版本TPS
(过滤首尾干扰值)
阿里云版本TPS
(过滤首尾干扰值)
649908695932无干扰无干扰
1288680786719无干扰无干扰
25669805743717076675143
51249147594235036961153
102442295458834479848910
204832147386983672944552
409623556276043150438334
819217037245242293734553
1638419612668194330273

2、事务整体RT对比

连接数社区版本RT阿里云版本RT
640.644 ms0.666 ms
1281.466 ms1.466 ms
2563.617 ms3.391 ms
51210.115 ms8.343 ms
102422.761 ms20.864 ms
204855.771 ms45.903 ms
4096130.195 ms107.858 ms
8192356.904 ms239.312 ms
1638466640.630 ms570.207 ms

3、实际SQL RT对比

连接数社区版本RT阿里云版本RT
640.475 ms0.501 ms
1280.934 ms0.854 ms
2562.109 ms1.842 ms
5124.656 ms4.587 ms
10249.837 ms8.69 ms
204836.882 ms7.928 ms
409667.513 ms7.522 ms
8192201.208 ms6.536 ms
1638465428.243 ms4.811 ms

4、RT 标准方差对比

连接数社区版本RT DEV阿里云版本RT DEV
642.941 ms1.767 ms
1284.445 ms2.763 ms
2565.515 ms2.775 ms
51211.424 ms4.447 ms
102428.950 ms16.575 ms
204887.051 ms52.400 ms
4096200.132 ms149.614 ms
8192403.771 ms358.461 ms
16384462277.689 ms1161.376 ms

5、建立完所有连接的耗时对比

连接数社区版本阿里云版本
640 s0 s
1280 s0 s
2564.9 s5.3 s
5129.4 s10.2 s
102418.5 s20.2 s
204837.6 s40 s
409675 s81.3 s
8192151.6 s168.4 s
16384312.1 s341.5 s

6、释放完所有连接的耗时对比

连接数社区版本阿里云版本
640 s0 s
1280 s0 s
2560 s0 s
5120 s0 s
10240 s0 s
20480 s0 s
40963 s3 s
81926 s9 s
163843312 s27 s

小结

进程模型数据库,需要为每个会话指派独立的进程与之服务,在连接数非常多,且大都是活跃连接时,进程调度浪费或引入的开销甚至远远大于实际任务需要的开销(例如上下文切换,MEMCPY等),性能下降会较为严重。

阿里云RDS PG,采用与Oracle Shared Server模式类似的方案,解决了进程模式在高并发的情况下性能下降的问题。

在超过1万个活跃并发的情况下,阿里云RDS PG的TPC-B测试指标依旧能够保持15万左右的QPS (消除干扰项),吞吐能力是社区版本的3倍。同时,在低并发的情况下,性能不减,与社区版本相当。

具体测试结果分析:

1、阿里云RDS PG在高并发下,TPS相比社区版本好很多,更加平稳。

2、阿里云RDS PG引入了POOL机制后,响应延迟,抖动相比社区版本低了很多。

3、启用POOL后,整个事务的RT,相比社区版本降低,使得整个处理吞吐得到提升。

4、启用POOL机制,使得一个事务中,真正执行SQL的时间大大缩短。同时还避免了锁等待的问题。

16384个连接,社区版本

  1. 1.750 BEGIN;
  2. 21.531 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
  3. 0.745 SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
  4. 461.077 UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
  5. 700.583 UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
  6. 1.958 INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
  7. 408.864 END;

16384个连接,阿里云版本

  1. 559.291 BEGIN;
  2. 2.359 UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;
  3. 1.223 SELECT abalance FROM pgbench_accounts WHERE aid = :aid;
  4. 1.191 UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;
  5. 2.310 UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;
  6. 0.981 INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
  7. 13.695 END;

对比以上两个版本的事务BEGIN的耗费时间、SQL执行时间的分布:

社区版本的SQL执行时间耗时更高(基本达到了500毫秒左右);

阿里云的PG版本,SQL执行时间非常短(基本都在1毫秒左右)。

实际的DML SQL执行越久,持锁就越久,并发1万多时,社区版本PG出现较多WAITING状态,就可以说明问题。

  1. 0:00.18 postgres: postgres postgres [local] UPDATE waiting
  2. 0:02.62 postgres: postgres postgres [local] UPDATE waiting
  3. 0:00.15 postgres: postgres postgres [local] UPDATE waiting
  4. 0:00.17 postgres: postgres postgres [local] UPDATE waiting
  5. 0:00.12 postgres: postgres postgres [local] UPDATE waiting
  6. 0:00.11 postgres: postgres postgres [local] UPDATE waiting
  7. ..............................
  8. 0:00.13 postgres: postgres postgres [local] COMMIT
  9. 0:00.13 postgres: postgres postgres [local] UPDATE waiting
  10. 0:00.13 postgres: postgres postgres [local] UPDATE waiting
  11. 0:00.16 postgres: postgres postgres [local] UPDATE waiting
  12. 0:00.14 postgres: postgres postgres [local] UPDATE waiting
  13. .....................

阿里云RDS PG内置POOL,不会导致SQL执行时间变长。因此有效的避免了持有资源锁的问题,是的真实的SQL RT非常的平稳。

连接数社区版本RT阿里云版本RT
640.475 ms0.501 ms
1280.934 ms0.854 ms
2562.109 ms1.842 ms
5124.656 ms4.587 ms
10249.837 ms8.69 ms
204836.882 ms7.928 ms
409667.513 ms7.522 ms
8192201.208 ms6.536 ms
1638465428.243 ms4.811 ms

5、启用POOL后,16384个连接高并发下,收尾时长缩短。从3312秒缩短到了27秒。

6、进程模式,建立连接比较耗时,如果业务上需要短时间内创建大量连接,也是一个瓶颈。比如创建16384个连接,串行创建,全部建立完16384个连接大概需要花费300秒。这样的业务,建议采用业务层连接池,并且配置较少的后端连接。

7、pgbench在统计TPS时,从所有连接建立完成,到所有连接退出,这之间产生的TPS。当需要建立很多连接或释放很多连接时,可能会耗时比较久,导致实际测试的性能不准,特别是在8000个连接以上时,断开连接过程中,TPS下降比较明显,并且会被统计进去,实测600秒,到1000多秒才完成统计,详见LOG。

8、阿里云RDS PG内置POOL,相比外置连接池,还有一个好处是“不会影响绑定变量的使用,也不会引入新的跳数,同时不会影响数据库pg_hba.conf防火墙的配置”。

在超过1万个活跃并发的情况下,阿里云RDS PG的TPC-B测试指标依旧能够保持15万左右的QPS (消除干扰项),吞吐能力是社区版本的3倍。真实SQL执行响应速度240倍于社区版本。同时低并发的情况下,性能不减,与社区版本相当。

欢迎使用阿里云RDS PG,我们会一如既往的优化,提高用户使用感受。这个特性将同时支持阿里云RDS PG以及RDS PPAS两个产品。

参考

《PostgreSQL Huge Page 使用建议 - 大内存主机、实例注意》

《PostgreSQL 11 preview - pgbench 支持大于1000链接(ppoll()代替select())》

https://commitfest.postgresql.org/18/1388/

《PostgreSQL pgbench : 冒号处理》

《PostgreSQL pgbench SQL RT 与 事务RT 浅析》

《从PostgreSQL支持100万个连接聊起》

《PostgreSQL 11 preview - 强制auto prepared statment开关(自动化plan cache)(类似Oracle cursor_sharing force)》

《PostgreSQL 10 + PostGIS + Sharding(pg_pathman) + MySQL(fdw外部表) on ECS 部署指南(适合新用户) - 珍藏级》

除此以外阿里云RDS PG还提供了很多企业级特性,比较典型的如:

1、varbitx,海量数据实时用户画像

《阿里云RDS PostgreSQL varbitx实践 - 流式标签 (阅后即焚流式批量计算) - 万亿级,任意标签圈人,毫秒响应》

《阿里云RDS for PostgreSQL varbitx插件与实时画像应用场景介绍》

《基于 阿里云 RDS PostgreSQL 打造实时用户画像推荐系统(varbitx)》

《惊天性能!单RDS PostgreSQL实例 支撑 2000亿 - 实时标签透视案例 (含dblink异步并行调用)》

《门禁广告销售系统需求剖析 与 PostgreSQL数据库实现》

2、oss二级存储,冷热数据分离存储

《打造云端流计算、在线业务、数据分析的业务数据闭环 - 阿里云RDS、HybridDB for PostgreSQL最佳实践》

《阿里云RDS PostgreSQL OSS 外部表实践 - (dblink异步调用封装并行) 从OSS并行导入数据》

《强制数据分布与导出prefix - 阿里云pg, hdb pg oss快速数据规整外部表导出实践案例》

《Greenplum insert的性能(单步\批量\copy) - 暨推荐使用gpfdist、阿里云oss外部表并行导入》

《阿里云RDS PostgreSQL OSS 外部表实践 - (dblink异步调用封装并行) 数据并行导出到OSS》

《ApsaraDB的左右互搏(PgSQL+HybridDB+OSS) - 解决OLTP+OLAP混合需求》