Skip to content

Commit 6364b14

Browse files
author
xiongcc
committed
添加第10章:How to troubleshoot Postgres performance using FlameGraphs and eBPF (or perf)
1 parent 38372b7 commit 6364b14

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# How to troubleshoot Postgres performance using FlameGraphs and eBPF (or perf)
2+
3+
今天我们将讨论如何在 Linux 机器上了解 Postgres 后台进程在 CPU 内的具体操作 (in-CPU analysis)。
4+
5+
RDS 以及其他托管 Postgres 服务的用户们 — 很遗憾,这个方法不能在你们的环境中使用。建议与服务提供商沟通:你们支付的不仅仅是虚拟机的费用,但他们只提供了 SQL 级的访问 — 这不太公平。
6+
7+
我将尽可能简化这个教程,并演示一个有趣的示例,在某些情况下可能会有帮助 — 我们将尝试了解在查询规划阶段幕后到底发生了什么。
8+
9+
要进行 in-CPU analysis,我们将使用 Brendan Gregg 的 [flamegraph](https://brendangregg.com/FlameGraphs/cpuflamegraphs.html) (火焰图)。
10+
11+
Brendan Gregg 将火焰图描述为"分层数据的可视化,旨在可视化分析软件的堆栈跟踪,以便快速准确地识别最常见的代码路径” (discussed [here](https://brendangregg.com/flamegraphs.html))。
12+
13+
要生成火焰图,我们需要执行几个非常简单的步骤:
14+
15+
1. 安装用于收集分析数据的工具。这里有两个选项:用于 eBPF 的工具或 `perf` 工具。
16+
2. 安装带有 Postgres 调试符号的包,如果需要,还要安装所使用的扩展的调试符号包。
17+
3. 克隆 FlameGraph 的仓库。
18+
4. 获取我们要分析的 Postgres 后台进程的进程 ID。
19+
5. 收集数据。
20+
6. 生成火焰图。
21+
22+
## 示例设置 (表结构和负载)
23+
24+
在讨论分析步骤之前,先介绍一下我们要探索的内容。以下步骤在连接到本地 Postgres 的 psql 中执行。
25+
26+
表结构:
27+
28+
```sql
29+
create table t1 as select i from generate_series(1, 1000000) i;
30+
create table t2 as select i from generate_series(1, 10000000) i;
31+
alter table t1 add primary key (i);
32+
alter table t2 add primary key (i);
33+
vacuum analyze;
34+
```
35+
36+
然后在无限循环中执行一些 Postgres 规划器的活动,并保持其运行:
37+
38+
```sql
39+
select pg_backend_pid(); -- remember it for step 4
40+
(but check again if Postgres is restarted)
41+
42+
explain select from t1
43+
join t2 using (i)
44+
where i between 1000 and 2000 \watch .01
45+
```
46+
47+
现在,开始分析。
48+
49+
## 第一步:安装用于收集数据的工具
50+
51+
我们将安装 `bcc-tools` (eBPF) 和 `linux-tools-***` (`perf`) 来展示其工作原理 — 你可以选择其中之一。
52+
53+
以 eBPF 和 Ubuntu 22.04 为例:
54+
55+
```shell
56+
sudo apt update
57+
apt install -y \
58+
linux-headers-$(uname -r) \
59+
bpfcc-tools # 可能叫bcc-tools
60+
```
61+
62+
安装 perf 工具:
63+
64+
```shell
65+
sudo apt update
66+
sudo apt install -y linux-tools-$(uname -r)
67+
```
68+
69+
如果不工作,尝试检查你有哪些可用的工具:
70+
71+
```shell
72+
uname -r
73+
sudo apt search linux-tools | grep $(uname -r)
74+
```
75+
76+
此页面提供了一些关于安装 `perf` 的不错建议,包括如何与容器一起使用:
77+
78+
- [perf, what’s that?](https://www.swift.org/documentation/server/guides/linux-perf.html)
79+
80+
使用 perf 的一个好方法是直接运行 `top` 命令:
81+
82+
```shell
83+
perf top
84+
```
85+
86+
然后观察系统整体的运行情况。注意,许多函数将以难以读取的形式出现,例如:`0x00000000001d6d88` — 某些名称无法解析的函数的内存地址。
87+
88+
![img](https://gitlab.com/postgres-ai/postgresql-consulting/postgres-howtos/-/raw/main/files/0010_perf_top_wo_debug_symbols.png)
89+
90+
这是因为我们还没有 Postgres 的调试符号。让我们修复这个问题。
91+
92+
## 第二步:安装Postgres调试符号
93+
94+
我推荐在所有生产系统中都安装调试符号。
95+
96+
对于Postgres 16:
97+
98+
```shell
99+
apt install -y postgresql-16-dbgsym
100+
```
101+
102+
你可能需要更多与 Postgres 相关的软件包,例如:
103+
104+
```shell
105+
apt search postgres | grep dbgsym
106+
```
107+
108+
注意,并不是所有与 Postgres 相关的软件包都带有 "`postgres`" 一词 — 例如,对于 `pgBouncer`,你需要安装 `pgbouncer-dbgsym`
109+
110+
安装好调试符号包之后,别忘了重启 Postgres (以及我们在 psql 中使用 `EXPLAIN.. \watch` 的无限循环)。
111+
112+
现在,`perf top` 将看起来更好 — 所有 Postgres 相关的行都有了函数名称:
113+
114+
![img](https://gitlab.com/postgres-ai/postgresql-consulting/postgres-howtos/-/raw/main/files/0010_perf_top_w_debug_symbols.png)
115+
116+
### 第三步:获取FlameGraph工具
117+
118+
```shell
119+
git clone https://github.com/brendangregg/FlameGraph
120+
cd FlameGraph
121+
```
122+
123+
## 第四步:获取Postgres的进程ID (PID)
124+
125+
如果你需要知道 Postgres 后台进程的 PID:
126+
127+
```sql
128+
select pg_backend_pid();
129+
```
130+
131+
如果你已有某些进程在运行,可以使用`pg_stat_activity`
132+
133+
```sql
134+
select pid, query
135+
from pg_stat_activity
136+
where
137+
application_name = 'psql'
138+
and pid <> pg_backend_pid();
139+
```
140+
141+
一旦我们知道了 PID,便可以将它保存为一个变量:
142+
143+
```shell
144+
export PGPID=<你的值>
145+
```
146+
147+
## 第五步:收集分析数据
148+
149+
eBPF 版本,收集 PID 为 $`PGPID` 的进程在 10 秒内的数据:
150+
151+
```shell
152+
profile-bpfcc -p $PGPID -F 99 -adf 10 > out
153+
```
154+
155+
如果你更喜欢 `perf`
156+
157+
```shell
158+
perf record -F 99 -a -g -- sleep 10
159+
perf script | ./stackcollapse-perf.pl > out
160+
```
161+
162+
## 第六步:生成火焰图
163+
164+
```shell
165+
./flamegraph.pl --colors=blue out > profile.svg
166+
```
167+
168+
## 结果
169+
170+
完成了。现在你需要将 `profile.svg` 文件复制到你的机器上,打开它,例如在浏览器中 — 它将显示这个 SVG 文件,而且支持点击各个区域 (对于探索各个区域非常有用)。
171+
172+
以下是我们的进程在无限 EXPLAIN 循环中的结果:
173+
174+
![img](https://gitlab.com/postgres-ai/postgresql-consulting/postgres-howtos/-/raw/main/files/0010_flamegraph.png)
175+
176+
有趣的是,约 35% 的 CPU 时间用于分析是否值得使用 `Merge Join`,而最后规划器选择了 `Nested Loop`
177+
178+
```sql
179+
postgres=# explain (costs off) select
180+
from t1 join t2 using (i)
181+
where i between 1000 and 2000;
182+
QUERY PLAN
183+
---------------------------------------------------
184+
Nested Loop
185+
-> Index Only Scan using t1_pkey on t1
186+
Index Cond: ((i >= 1000) AND (i <= 2000))
187+
-> Index Only Scan using t2_pkey on t2
188+
Index Cond: (i = t1.i)
189+
(5 rows)
190+
```
191+
192+
在这个案例中,计划时间非常短,低于毫秒级 — 但我遇到过一些情况,计划阶段非常慢,持续数秒甚至几十秒。最终 (多亏了火焰图!)发现,分析 Merge Join 路径是造成问题的原因,因此禁用 Merge Join (`set enable_mergejoin = off`) 后,计划时间大幅下降,恢复到了合理的水平。但这是另一个故事。
193+
194+
## 推荐阅读
195+
196+
- Brendan Gregg 的书:"Systems Performance" and "BPF Performance Tools"
197+
- Brendan Gregg 的演讲 — 例如 ["eBPF: Fueling New Flame Graphs & more • Brendan Gregg"](https://youtube.com/watch?v=HKQR7wVapgk)
198+
- [Profiling with perf](https://wiki.postgresql.org/wiki/Profiling_with_perf) (Postgres wiki)
199+
200+
---
201+
202+
这篇文章对你有帮助吗?告诉我吧 — 也请与使用#PostgreSQL的同事和朋友分享!
203+
204+
# 我见
205+
206+
一图胜千言
207+
208+
![image-20240920091816290](/Users/xiongcancan/Library/Application Support/typora-user-images/image-20240920091816290.png)

0 commit comments

Comments
 (0)