数据库连接数量
通过减少数据库连接数量并使用某种连接池,通常可以支持更多并发用户。本页试图解释其中的原因。
摘要
数据库服务器只有有限的资源,如果您没有足够的连接来使用所有资源,那么使用更多连接通常会提高吞吐量。一旦所有资源都处于使用状态,您将无法通过拥有更多连接来争夺资源来推动更多工作。实际上,由于这种争用造成的开销,吞吐量开始下降。通常可以通过将具有活动事务的数据库连接数量限制为与可用资源数量匹配,并将所有在限制范围内到达的开始新数据库事务的请求排队,来提高延迟和吞吐量。
与许多人最初直觉冲动相反,如果您在事务准备就绪但系统繁忙到资源饱和时将其排队,并在资源可用时将其启动,您经常会看到事务更快地完成。
Pg 通常会通过一次执行 5、10 或 20 个事务来比一次执行 500 个事务更快地完成相同的 10,000 个事务。确定一次应执行多少个事务取决于工作负载,需要调整。
对外部池的需求
如果您查看任何以连接数量为x轴、tps 为y轴(其他条件不变)的 PostgreSQL 性能图表,您会看到性能随着连接数量的增加而上升,直到达到饱和状态,然后出现一个“拐点”,之后性能下降。版本 9.2 已经做了很多工作来将这个拐点向右推,使下降更加平缓,但问题是固有的——如果没有内置的连接池,或者至少没有准入控制策略,拐点和随后的性能下降将永远存在。
决定不在 PostgreSQL 服务器本身内包含连接池器是经过深思熟虑的,有充分的理由
- 在许多情况下,如果您在单独的机器上运行连接池器,您会获得更好的性能;
- 对于所有需求来说,没有单一的“正确”池设计,将池放在核心服务器外部可以保持灵活性;
- 通过将连接池整合到客户端软件中,可以获得改进的功能;最后
- 一些客户端软件(如 Java EE / JPA / Hibernate)始终池化连接,因此 PostgreSQL 中的内置池化将是浪费的重复。
许多框架在运行在数据库服务器机器上的进程中进行池化(以最大程度地减少来自数据库协议的延迟影响),并接受运行特定功能的给定参数集的高级请求,整个功能作为一个单独的数据库事务运行。这确保网络延迟或连接故障不会导致事务在等待来自网络的东西时挂起,并提供了一种简单的方法来重试因序列化故障(SQLSTATE 40001 或 40P01)而回滚的任何数据库事务。
由于内置在数据库引擎中的池器会比较差(出于上述原因),社区决定不走这条路。
如果您的客户端软件没有内置的连接池化功能,那么有几种外部池化选项可用。持久的连接(例如 Apache 的 mod_php 所维护的)*不是*池化,仍然需要连接池。
超过“拐点”后性能下降的原因
有许多独立的原因导致性能随着更多数据库连接而下降。
- 磁盘争用。如果您需要进行随机访问磁盘(即您的数据未缓存在 RAM 中),大量连接往往会导致更多表和索引在同一时间被访问,从而导致在整个磁盘上进行更重的查找。旋转磁盘上的查找速度比顺序访问慢得多,因此对于使用传统硬盘的系统来说,由此产生的“抖动”会使系统速度大大降低。
- RAM 使用情况。
work_mem
设置会对性能产生重大影响。如果它太小,哈希表和排序会溢出到磁盘,位图堆扫描会变成“有损”,需要在每次页面访问时做更多工作,等等。所以您希望它很大。但work_mem
RAM 可以为每个连接上的每个查询的每个节点分配,所有这些都在同一时间进行。因此,一个大的work_mem
以及大量的连接会导致 OS 缓存周期性地被丢弃,迫使更多磁盘访问;或者甚至可能导致系统进入交换。因此,连接越多,您就越需要在慢速计划和抖动缓存/交换之间做出选择。
- 锁争用。这种情况发生在多个级别:自旋锁、LW 锁以及所有在
pg_locks
中显示的锁。随着更多进程竞争自旋锁(它保护 LW 锁的获取和释放,进而保护重量级锁和谓词锁的获取和释放),它们在使用的 CPU 时间中所占的比例很高。
- 上下文切换。处理器被中断,无法继续处理一个查询,必须切换到另一个查询,这包括保存状态和恢复状态。当核心忙于交换状态时,它不会对任何查询执行任何有用的工作。上下文切换比以前使用现代 CPU 和系统调用接口时便宜得多,但仍然不是免费的。
- 缓存行争用。一个查询可能正在处理 RAM 的某个特定区域,而接替它的查询可能正在处理另一个区域;导致缓存在 CPU 芯片上的数据被丢弃,而需要重新加载才能继续另一个查询。除此之外,各种进程会互相争夺缓存行的控制权,导致停顿。(幽默说明,在一个高度竞争的负载的 oprofile 运行中,10% 的 CPU 时间被归因于一个 1 字节的空操作;分析表明这是因为它需要等待缓存行以进行后续的机器码操作。)
- 一般缩放。一些根据
max_connections
分配的内部结构按 O(N^2) 或 O(N*log(N)) 的速度扩展。一些在较少连接时可以忽略不计的开销类型,在大量连接时会变得很明显。
如何找到最佳数据库连接池大小
一个多年来在许多基准测试中一直保持良好的公式是,对于最佳吞吐量,活动连接的数量应该接近 ((核心数量 * 2) + 有效主轴数量)。核心数量不应包括 HT 线程,即使启用了超线程也是如此。如果活动数据集完全被缓存,则有效主轴数量为零,并且随着缓存命中率的下降,它接近实际的主轴数量。版本 9.2 的 WIP 基准测试表明,该公式需要在该版本上进行调整。到目前为止,还没有关于该公式在 SSD 上的适用性的任何分析。
无论您选择连接池大小的起点,都应该在生产系统中尝试增量调整以找到硬件和工作负载的实际“最佳值”。
请记住,这个“最佳值”是针对正在积极执行工作的连接数量而言的。在计算适当的池大小时,忽略用于系统监控和控制的几乎处于空闲状态的连接。您应该始终使max_connections
比在连接池中启用的连接数量略大。这样,始终有一些空闲插槽可供直接连接以进行系统维护和监控。