|
| 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 | + |
| 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 | + |
| 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 | + |
| 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 | + |
0 commit comments