介绍
在 PostgreSQL 数据库和应用服务器之间会看到许多的基础设施层,这是很常见的。最常见的有连接池、负载均衡器、路由器、防火墙等。对于这些组件所涉及的网络跳点及其对整体性能产生的额外开销,我们经常会忽视它们的影响,或认为这些开销是理所当然的。但在许多情况下,它可能会导致严重的性能损失和整体吞吐量的下降。
如何检测和衡量影响
没有简单的机制来衡量网络开销的影响。但是,对 pg_stat_activity 中的 wait_events 进行非常仔细的分析,可以尽可能地让我们清楚其影响。因此,我们应该对等待事件进行采样。有许多方法可用于等待事件采样,包括扩展。但是在用户环境中安装用于等待事件采样的特殊工具或扩展,不是很方便。不过,我们可以使用 pg_gather 作为收集和研究等待事件的方法,因为它是一个独立的 SQL 脚本,不需要在数据库系统上安装任何东西。它的设计也非常轻巧。每次会话将收集 2,000 个样本。
pg_gather 分析报告可以显示与每个会话关联的等待事件和其他信息。
pid | state | User | client | Last statement | Connection Since | Transaction Since | xmin age | Statement since | State since | waits |
6594 | active | postgres | 10.197.42.240 | COPY pgbench_accounts (aid, bid, abalance, filler) TO stdout; | 00:00:08.650416 | 00:00:08.644447 | 0 | 00:00:00 | 00:00:00 | ClientWrite:14, DataFileRead:278, CPU:1708 |
但是,本教程将仅讨论和重点介绍其中的等待事件部分,同时介绍不同类型的业务负载,以及如何在等待事件中查看网络性能。
案例 1: 查询检索大量行
让我们考虑一下在另一台机器上用 pg_dump 进行逻辑备份的情况。如果网络速度很快,我们可能会看到大量的 CPU 使用率和 “DataFileRead” 一类的等待事件。
waits |
ClientWrite:14,DataFileRead:278,CPU:1708 |
当然,还有 “ClientWrite” 事件,在本例中,这是与将数据发送到客户端(pg_dump)相关的等待事件。如果客户端是像 psql 这样的轻量级工具,并且网络速度非常快,则 “ClientWrite” 可能会变得几乎不可见。
但是,让我们来看看如果网络变慢,等待事件会是什么样子。
waits |
CPU:19,ClientWrite:1821,ClientRead:158,DataFileRead:2 |
我们可以看到 CPU 使用率和 “DataFileRead” 等待事件下降了,这表明服务端整体的会话活动速度变慢了。同时,“ClientWrite” 上升到了 1821,这表明会话花费了大量时间将数据发送到其客户端(pg_dump)。还有 “ClientRead”,表明 pg_dump 的确认需要时间。
“ClientWrite” 中的飙升与客户端工具无关。以下是一个常规的 psql 会话,在查询检索大量记录时的屏幕截图。
waits |
CPU:129,ClientWrite:1869,DataFileRead:2 |
在这些情况下,这种过多的 “ClientWrite” 足以暴露出问题。
案例 2: 批量数据加载
这与前一个案例相反。但是 PostgreSQL 对于批量数据的写入操作,需要做更多的工作。以下等待事件是在非常快速/低延迟的网络环境中捕获到的。
waits |
CPU:1725,DataFileExtend:94,WALWrite:75,WALSync:63,DataFileWrite:41,WALBufMapping:2 |
显然,PostgreSQL 进程必须在 “DataFileExtend”、“WALWrite” 和 “WALSync” 这些事件上花费时间。现在,如果网络速度变慢,随着性能瓶颈的出现,我们看到的许多等待事件可能会变得不可见。
以下是通过较慢的网络加载相同批量数据的等待事件。
waits |
CPU:3,ClientRead:1997 |
正如我们所看到的,“ClientRead” 已成为主要的等待事件。这意味着服务器会话花费了更多时间从其客户端读取数据。
在许多系统中,这种变化可能并不明显,但总体上 “ClientRead” 会变得更加突出。
案例 3: 对事务的影响
有人可能会问,事务有什么特别之处。在 OLTP 业务负载下,语句可能简单而短小,这会导致任何可观察到的网络影响。但是服务器和客户端之间的来回通信,可能会导致语句与最终的提交或回滚之间出现不必要的延迟。也就是说,在每个语句之间会有延迟/间隙。
以下是使用 pgbench 在快速网络环境处理小事务发生的等待事件。
waits |
WALWrite:714,CPU:573,WALSync:442,ClientRead:139,DataFileRead:35,DataFileWrite:18,transactionid:17,BufferContent:2, Net/Delay*:59 |
显然,存在大量 WAL 相关的等待事件和 CPU 使用率。但是我们可以看到,也有相当多的 “ClientRead”。发生这种情况是因为小事务会有很多网络交互。ClientRead 对于事务来说是不可避免的,预计在其中出现 5-10% 是可以的。
但随着网络速度变慢,“ClientRead” 变得越来越突出。以下是在较慢的网络环境上,相同的 pgbench 事务工作负载的信息。
waits |
DataFileRead:1,WALSync:20,CPU:23,ClientRead:1700,transactionid:2, Net/Delay*:252 |
在这种情况下,ClientRead 成为了最突出的等待事件。
您可能想知道,显示的 “Net/Delay*” 是什么?新版本的 pg_gather(版本 21)中提供了这种额外的分析,用以评估事务块之外的延迟。有关详细信息,请参阅下一节。
案例 4: 连接利用率
随着网络延迟的增加,客户端连接将无法尽可能地使用服务器会话。服务器会话必须在 “ClientRead”/“ClientWrite” 事件上等待或处于空闲状态。无论哪种方式,它都会极大地影响系统的吞吐量。
在事务中,延迟被捕获为 “ClientRead”,但不会捕获到两个事务之间的延迟,因为会话暂时会变为 “空闲”。pg_gather 新版本会对这种瞬时切换到空闲状态的过程进行估计,作为服务器浪费时间或 “Net/Delay*”。这可能是由于网络延迟或应用程序响应不佳引起。从数据库方面来看,很难区分它们。但是 “Net/Delay*” 可以很好地说明浪费了多少服务器时间。
如果可以在应用程序服务器上安装 PostgreSQL 客户端工具,则很容易模拟负载,来研究网络延迟和应用程序端响应延迟,并将其与实际数据进行比较。
当客户端和服务器之间有大量的来回通信时,延迟会变得更加明显。这可以通过创建单个语句文件轻松测试出来。
echo "SELECT 1" > query.sql
这可以通过 TCP 连接对远程数据库执行指定秒数。
pgbench -h 10.197.42.1 -T 20 -f query.sql
在服务器之间采用快速网络连接的情况下,可以获得以下结果,以作为单个会话的 TPS。
...
latency average = 0.030 ms
initial connection time = 5.882 ms
tps = 32882.734311 (without initial connection time)
但 pg_gather 的等待事件分析表明,在 Net/Delay* 上花费了更多的时间。
waits |
ClientRead:38,CPU:566, Net/Delay*:1392 |
这是有道理的,因为 “SELECT 1” 在服务器上没有太多事情要做,而且这种业务负载都是关于发送来回通信的。
使用本地 Unix 套接字连接时,单个会话吞吐量增加了一倍多!
latency average = 0.013 ms
initial connection time = 1.498 ms
tps = 75972.733205 (without initial connection time)
但等待事件分析告诉我们,客户端-服务器通信仍然是一个主要的时间消耗点。
waits |
ClientRead:271,CPU:575, Net/Delay*:1148 |
这种高度交互的业务负载,可以采用服务端编程(存储过程/函数)甚至扩展来优化。有趣的是,当使用 Unix 套接字连接时,与 TPS 相比,CPU 使用率的比例较小;这是需要重点注意的一个地方。“ClientRead” 增加是因为从客户端传输了更多数据。
如果在这种情况下网络速度变慢,则 “Net/Delay*” 也会增加,并且 CPU 使用率和 TPS 会下降,因为会话在处理两个语句之间要花费更多时间。
waits |
CPU:51, Net/Delay*:1882 |
由于这种特定的业务负载没有事务,并且要发送到服务器的数据较少,因此正如我们所看到的,“ClientRead” 可能会下降到一个不明显的水平。
总结
pg_stat_activity 中的 “等待事件” 信息可以告诉我们,有关性能和网络拥塞的许多详细信息。不仅仅是事件的汇总,在两个等待事件和模式之间的间隙,也有很多信息需要深入研究。正确地收集和分析数据,可以从 PostgreSQL 的角度检测发生的事情,以及它受到网络的影响程度。更重要的是,分析过程可以不依赖数据库托管机器和操作系统级工具。无需任何复杂的工具或框架即可实现此目的。像这样独立的 SQL 脚本可以方便地发现问题和瓶颈。尽管这篇教程专门介绍了网络,但对于等待事件的分析,在许多情况下可能是通用的。