在Postgre中设置max_connections时,为什么需要使用连接池 (译)

原文地址:https://www.enterprisedb.com/postgres-tutorials/why-you-should-use-connection-pooling-when-setting-maxconnections-postgres

PostgreSQL是“世界上最先进的开源数据库”,我相信这一点。在我从事it工作的10多年中,它一直很稳定,向SaaS提供每秒超过1000个查询的数据,很少出现故障,经受住了各种形式的事故(最终证明是软件工程错误)和性能下降(最终证明是用户错误)的指控。它有如此多的特性和扩展,可以满足每一个需求,可能有50-60%的用户不经常使用,或者根本没有听说过。不幸的是,在我最近的技术支持经验中,我了解到Postgres通常是非常值得信任的,但是很多技术都是如此;它不会判断您应该如何调整postgresql.conf中的参数,就像跑车上的油门踏板会影响行驶速度的一样。仅仅因为您可以踩到油门踏板上,并不意味着您应该在高峰时间执行此操作,因此并不是说将某些参数值其设置得很高,就适合并发较高的OLTP应用程序。
 
最容易引起误解的参数之一是max_connections。可以理解的是,在拥有大量CPU和大量RAM的现代系统上,向全球用户群提供现代SaaS负载,一次可以看到成千上万个用户会话,每个会话都试图查询数据库以更新用户状态、上载自拍或其他用户可能做的事情。当然,DBA会希望在postgresql.conf设置与应用程序发送到数据库的流量相匹配的值,但这是有代价的。这种代价的一个例子是生成连接/断开连接的延迟;对于创建的每个连接,操作系统都需要为打开网络套接字的进程分配内存,PostgreSQL需要自己进行内部计算来建立连接。将其扩展到数千个用户会话,而仅仅为用户准备好数据库连接就可能浪费大量时间。将max_connections设置为高的其他成本包括磁盘争用、操作系统调度,甚至CPU级缓存线争用。
 

max_connections究竟该设置为多大

没有太多科学数据可以帮助DBA将max_connections设置为其适当的值。因此,大多数用户发现PostgreSQL的默认值max_connections = 100太低。不断有人将其设置为4k,12k甚至30k以上(这些人都遇到了一些主要的资源争用问题)。与那里的任何PostgreSQL专家交谈,他们将给您一个范围“几百个”,或者有些人会断然地说“不超过500个”,并且“绝对不超过1000个”。但是这些数字从何而来?他们怎么知道的,我们该如何计算呢?提出这些问题,可能只会发现自己更沮丧,因为有一种公式化的方法来确定该数字。设置此值的困难在于数据库需要服务的应用程序。一些应用程序发送大量查询并关闭会话,而其他应用程序可能会突然发送查询,并且之间有很多空闲时间。此外,某些查询可能会花费大量CPU时间来执行联接和排序,而其他查询则会花费大量时间顺序扫描磁盘。我见过的最合理的答案是计算CPU的数量,占利用率的百分比(基于一个基准测试需要做的),然后乘以比例因子。但这甚至涉及一些“hand-waving”(译者注:专门搜索了文献,这里可以理解成拍脑袋做出的决定)。
 

测试tribal知识(译者注:原文作者用tribal这个词是类似于"江湖传闻"的一词的语音)

没有一种非常简明清晰的方法来计算max_connections,我决定至少要测试那里的tribal知识的有效性。确实应该是“几百个”,“不超过500”和“绝对不超过1000”吗?为此,我设置了一个AWS g3.8xlarge EC2实例(32 CPU,244GB RAM,1TB 3K IOPS SSD)来慷慨地模仿我在那里看到的一些数据库服务器,并使用--scale = 1000初始化了一个pgbench实例。 我还设置了10个较小的EC2实例,用作应用程序服务器,并且在每个实例上运行pgbench测试一小时,每小时将--client = NUM递增(因此它们将总共创建100,200,300 ... 5000个连接每小时的测试)。 autovacuum已关闭,以防止任何不必要的干扰导致结果偏差(尽管我在每次测试之间都进行了清理),否则将postgresql.conf调整为一些通常可接受的值。我将max_connections设置为12k,以确保我的测试使用的不超过最终测试中要求的5000。测试运行时我走开了,结果又回来了,就像这样:

下面是上图的放大图:

因此,对于改进后的设置使其类似于某些企业级计算机的服务器,最佳性能出现在300-500个并发连接的时候。在700之后,性能急剧下降(就每秒事务数和延迟而言)。 1000个以上的连接均表现不佳,并且延迟不断增加。最后,等待时间开始是非线性的-这可能是因为我没有将EC2实例配置为允许超过默认的~25M open filehandles,到在连接数超出3700之后,看到几个无法fork新的连接过程: could not fork new process for connection: Resource temporarily unavailable.
有趣的是,这三中说法都匹配了:“几百个”,“不超过500个”和“绝对不超过1000个”。这似乎太不可思议了,所以我再次进行了测试,直到增加到最大1800个并发连接。结果:

因此,对于该服务器而言,最有效的点似乎确实在300-400个连接之间,并且max_connections不应设置得比该值高很多,以免我们冒失去性能的风险。
译者注:这个结果是匹配原文作者的服务器配置:32 CPU,244GB RAM,1TB 3K IOPS SSD,应该是没有启动连接池的情况下得到的一个结果,而不是一个参考值或者标准值

如果需要更多的连接数,该怎么办?

显然,max_connections = 400不会允许高并发的应用程序处理用户提供给它的所有工作。某种程度上,需要扩大数据库以满足这些要求,但是这样做似乎需要一些魔法。一种选择是设置复制系统,以使读取分布在多个服务器上,但是如果并发写超过400个并发会话(这很有可能),则需要考虑其他选择。通过允许多个客户端会话共享数据库连接池并根据需要执行读写事务,并在空闲时将数据库连接的控制权移交给其他会话,连接池将满足此需求。在PostgreSQL社区中,池化应用程序的主要参与者是pgbouncer和pgpool-两者都经过了充分的测试,以使DBA能够将其PostgreSQL数据库扩展到成千上万的并发用户连接。
为了演示在使用连接池时提高的可伸缩性,我设置了一个类似于Alvaro Hernandez’s concurrent-connection test的m4.large EC2实例(译者注:上述连接在原文中也打不开,另外,m4.large配置为2 个 vCPU,8GiB 内存),
因为:1)我想使用一个不仅仅是我自己的基准数字;2)我想要节省一些成本。然后尝试得到与他相似的图形(中的结论):

译者注:
上述截图的大概的结论是:基于m4.large当前这个配置,在小于200个并发连接的时候,TPS会随着并发连接数的增加而增加,以下是原文的作者是尝试验证
Alvaro的测试结论,也就是上面这个截图的结果,从下文看,不管用不用连接池,两种情况下都无法得到这个结论,这说明Alvaro这个人的测试结果是值得商榷的,在这种配置下,10个并发连接得到的TPS才是最高的。

但是,创建此图时在pgbench中没有-C /-connect标志(译者注:为每个事务建立新的连接,而不是在一个连接中完成多个事务操作),这可能是Alvaro不是为了说明使用连接池的优点。因此,我重新运行了相同的测试,但是这次使用了-C:
如我们所见,由于每个事务都必须连接和断开连接,所以吞吐量下降了,这说明了建立连接的需要花费成本。然后我将pgbouncer配置为max_client_conn = 10000,max_db_connections = 300,pool_mode = transaction,然后再次使用pgbouncer端口运行相同的pgbench测试(-h <hostname> -p6432 -U postgres --client=<num_clients> --progress=30 --time=3600 --jobs=2 -C bouncer):

显然,虽然pgbouncer维护了与数据库的开放连接并与传入的客户端共享它们,但连接开销却降低了,从而提高了吞吐量。请注意,即使使用连接池池管理器,我们也永远无法获得Alvaro的图,因为在启用连接时总会有一些开销(即,客户端需要告诉OS分配一些空间并打开一个套接字以实际连接到pgbouncer) 。

结论

如我们所见,max_connections应该通过一些现场基准测试和一些自定义脚本来确定(请注意,所有这些测试都使用了内置的pgbench事务,该事务包含3个UPDATE,1个SELECT和1个INSERT-a 可以通过提供自定义.sql文件并使用-f /-file标志来创建真实测试)。从根本上说就是,做一下家庭作业:(基于自己的服务器)进行基准测试,找出可以可以提供良好性能的最大并发度,四舍五入到最接近的百位数(为您留出一些余地),并相应地设置max_connections。设置后,希望通过复制或连接池的任何组合来满足并发性的所有其余要求。连接池是任何高吞吐量数据库系统的重要组成部分,因为它可以消除连接开销,并为较小的数据库连接集保留更多的内存和CPU时间,从而防止不必要的资源争用和性能下降。
 
 
 
 
译者的总结

1,数据库最大连接数和最大并发数的误区:
数据的最大连接数,也就是可支持的最大并发连接数,总有人把最大连接数和最大并发数混为一潭,联想到多线程,多线程总给人一种牛逼轰轰的,不用多线程就上不去台面的感觉,其实除了CPU密集型的运算之外,而日常的大部分操作,都是和IO以及网络密切相关的,处理某个单一任务,多线程或者说纯粹增加线程数据并不一定提升效率,这两者相比CPU,都会先于CPU成为瓶颈,也就是说不等你并发完全起来,IO和网络已经成为瓶颈了,举个最简单的例子:Redis。
1,即使是单核CPU的计算机也能“同时”运行数百个线程。但我们都[应该]知道这只不过是操作系统用时间分片玩的一个小把戏。
一颗CPU核心同一时刻只能执行一个线程,然后操作系统切换上下文,核心开始执行另一个线程的代码,以此类推。
给定一颗CPU核心,其顺序执行A和B永远比通过时间分片“同时”执行A和B要快,这是一条计算机科学的基本法则。一旦线程的数量超过了CPU核心的数量,再增加线程数系统就只会更慢,而不是更快。

2,在这一时间段(即"I/O等待")内,线程是在“阻塞”着等待磁盘,此时操作系统可以将那个空闲的CPU核心用于服务其他线程。
所以,由于线程总是在I/O上阻塞,我们可以让线程/连接数比CPU核心多一些,这样能够在同样的时间内完成更多的工作。
那么应该多多少呢?这要取决于磁盘。
较新型的SSD不需要寻址,也没有旋转的碟片。
可别想当然地认为“SSD速度更快,所以我们应该增加线程数”,恰恰相反,无需寻址和没有旋回耗时意味着更少的阻塞,所以更少的线程[更接近于CPU核心数]会发挥出更高的性能。只有当阻塞创造了更多的执行机会时,更多的线程数才能发挥出更好的性能。

最大连接数计算公式
下面的公式是由PostgreSQL提供的,不过我们认为可以广泛地应用于大多数数据库产品。你应该模拟预期的访问量,并从这一公式开始测试你的应用,寻找最合适的连接数值。
连接数 = ((核心数 * 2) + 有效磁盘数)

公理:你需要一个小连接池,和一个充满了等待连接的线程的队列
如果你有10000个并发用户,设置一个10000的连接池基本等于失了智。1000仍然很恐怖。即是100也太多了。你需要一个10来个连接的小连接池,然后让剩下的业务线程都在队列里等待。连接池中的连接数量应该等于你的数据库能够有效同时进行的查询任务数(通常不会高于2*CPU核心数)
这段话反复琢磨了很多次
对于非CPU密集型计算来说,单纯滴增加线程数可能会降低整体运算效果,然后想到了一个问题:为什么单线程的Redis单实例可以有10+QPS的超级响应速度,这里强调的单线程,除了哪些面试套路性的答案之外,其实很多问题上面也能够说明其原理。
1,redis基于内存的操作,IO的阻塞延迟相比SSD和HDD,分别低了3/6个数量级,可以认为基于内存的IO延迟是极底的
2,基于1,在没有IO阻塞或者IO延迟极地的情况下,相比多线程,单线程其实要比多线程更快。

20201209补充:数据库的 max_connections 真的越大越好吗?这里有一个实测连接数的数据

2,如何处理对数据库的并发请求数大于数据库的最大连接数:
如果连接数是一个合理的值,不是100或者200,真正的并发数超出最大连接数,此时必须使用连接池,否则会报超出最大连接数的错误,连接池不仅可以提高连接效率,在超出最大连接后等待机制,也可以起到了限流的作用。
而如果真的不去限制最大连接数,随着并发的增加去机械地增加连接数,只会冲垮数据库,即便冲不跨数据库,其结果就是所有的请求,在数据库里相互牵制相互拖累。
 
原文地址:https://www.cnblogs.com/wy123/p/14087274.html