ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

pgBadger实战:从PostgreSQL日志中快速定位慢查询与性能瓶颈

pgBadger实战:从PostgreSQL日志中快速定位慢查询与性能瓶颈 简介pgBadger是一款用纯Perl语言编写的PostgreSQL日志分析器以解析速度见长能够自动识别syslog、stderr、csvlog等常见日志格式并借助JavaScript图表库生成可视化报告适合DBA、运维工程师及需要深度排查数据库性能的开发者在日常巡检和故障分析中使用。资源为pgBadger 11.5的开源发行版共54个文件、约2.2MB除核心Perl脚本外还包含jQuery、jqplot等前端图表资源、测试脚本、文档与许可证说明整体结构紧凑便于直接部署或二次开发。目前已有440人学习查看。压缩包内附有完整源码、构建配置如Makefile.PL与MANIFEST、单元测试样例及详细文档读者既可快速上手生成日志分析报表也能参考其内部实现扩展自定义功能同时自带对gzip压缩日志的支持有助于在日志量大、存储受限的环境下高效完成分析任务。1. pgBadger 是什么一份 PostgreSQL 日志能榨出多少信息有一次线上慢查询排查手里摆着 2GB 的 PostgreSQL 日志我 grep 了大半个下午才凑出几条 5 秒以上的语句那滋味不想再试第二次。pgBadger 就是一个为提高速度而构建的 PostgreSQL 日志分析器Perl 写的命令行工具喂给它 PostgreSQL 的文本日志它吐出一份可交互的 HTML 报告慢查询 Top、锁等待、Checkpoint、临时文件、全表扫描在这些区块里一次看全。它不装数据库插件、不写回数据库、不需要改业务代码唯一的前提是你把 PostgreSQL 的日志开关按格式打开。适合经常要处理慢查询的 DBA、后端负责人和 SRE日志越乱越有价值这份工具就是把“出了什么问题”从黑匣子里翻出来的那双手。它是开源项目系统包里一条命令就能装后面我会把配置、命令和踩过的坑一起展开。2. 先搞清它分析什么pgBadger 的输入、报告与提速原理2.1 它吃的是原生日志不是 pg_stat_statements上手 pgBadger 之前必须先分清两个概念。pg_stat_statements 是 PostgreSQL 内置插件长期累计每类 SQL 的总耗时、执行次数适合看趋势而 pgBadger 解析的是数据库进程写出来的原始日志文件回答的是“这段时间里到底发生了什么”。常见误解是我已经开了 pg_stat_statements还需要日志分析器吗需要。pg_stat_statements 只记录执行过的语句聚合不记录锁等待过程、checkpoint 行为、临时文件写入、连接断开这类旁路信息而这些往往是数据库抖动的大头。还有一层容易忽视的区别MySQL 的慢日志有固定表结构、自带查询接口把这个习惯带过来会发现 PostgreSQL 完全不同——它的默认日志是纯文本没有内置慢日志表最多靠 log_line_prefix 控制格式。这就是 pgBadger 这类开源项目存在的意义把一条条裸日志变成可读的指标。能让 pgBadger 出内容的日志开关大致有五个我一般这样开log_min_duration_statement 记录超过阈值的慢语句log_checkpoints 记录检查点事件log_lock_waits 记录锁等待log_temp_files 记录临时文件log_autovacuum_min_duration 记录自动 vacuum。每开一个报告里就多一个区块开少了报告就瘦开多了文件涨得快第 3 章会给出完整配置。2.2 原生日志里有什么一行日志能被拆出哪些字段pgBadger 的解析逻辑本质上就是把你日志里每个字段映射到报告维度。拿一条真实的日志为例2024-06-01 10:00:00.123 CST [23456]: [123-1] userdba,dbapp,apppsql,client127.0.0.1 LOG: duration: 2500.123 ms statement: SELECT * FROM orders WHERE user_id 123;拆开看这一行值多少钱开头时间戳给报告时间轴[23456] 是进程号用于区分连接[123-1] 是会话行号pgBadger 靠它把一条跨行消息拼回同一事件user、db、app、client 分别是用户、数据库、客户端应用、来源地址报告按库按用户分组全得靠它们duration 是慢查询耗时statement 是语句正文。pgBadger 还会把这条语句里的数字参数替换成 ?做参数归一化所以同一条 SQL 的不同参数值会聚合在一起排名不会因为 where 条件里数字不同就被拆成几百行。这里面最容易被忽略的是 [123-1] 的会话行号。PostgreSQL 的日志里一条 ERROR 常常带出 detail 和 context 多行内容而只有第一行才有前缀里的完整字段没有序号多行消息会被当成独立事件报告里的语句会被拦腰截断。所以 log_line_prefix 里有没有 %l直接决定报告能不能拼全语句。这也是为什么很多人换了 log_line_prefix 之后报告突然“少了一半数据”的根本原因。2.3 单遍流式解析快在哪和自写脚本的差距标题里“为提高速度而构建”不是宣传话术是它的实现方式决定的。pgBadger 处理日志是单遍流式一行一行读正则提取出时间、进程号、数据库、用户、语句耗时在内存里做累加全程不把日志导入数据库也不需要二次 ETL。这样做最大的收益是内存可控处理 10GB 级别的日志时内存占用通常维持在几百 MB 以内而不是像导入数据库统计那样先把数据搬一遍。对比一下常见的替代方案就能看出差别。用 grep 加 awk 自己统计grep 本身很快但你要自己处理多行消息、自己按分钟聚合、自己做 Top 排序写出来能用维护成本不小把日志灌进 PostgreSQL 再 SQL 分析要先清洗格式再建表导入日志一多大半时间花在导入而不是分析上。pgBadger 把这几步都省了一条命令出 HTML还能直接读 .gz、.bz2、.xz 压缩日志不用先解压占磁盘。新版本还支持多线程并行解析不同日志文件适合日志被按天切成很多小文件的场景。有一点要提醒它快不代表它对日志格式不挑。输入格式越规整解析越快如果 log_line_prefix 配得乱七八糟它会先花时间做格式猜测甚至直接拒绝。所以想享受速度红利前提是第 3 章的配置一步都不能省。2.4 开源分发与版本差异从 GitHub 拿源码还是走系统包pgBadger 是开源项目用的 PostgreSQL License属于宽松类许可证改完再分发也没有像 GPL 那样强的传染性公司内部二次开发负担小。你直接搜 GitHub 的 darold/pgbadger 就能看到仓库和 release 包很多 postgresql 安装教程的末尾也会顺手提它一句。获取方式取决于你的操作系统。Debian/Ubuntu 的 apt 仓库、RHEL/CentOS 的 EPEL 仓库里基本都有 pgbadger 包一条命令装完好处是依赖有人管。但系统仓库里的版本可能落后 GitHub 一个大版本而新版本往往意味着支持更新的 PostgreSQL 日志格式比如 PostgreSQL 15 之后新增的 jsonlog 日志格式老版本 pgBadger 就不认。我的习惯是生产环境优先用系统包遇到“日志格式识别不了”这类问题时再去 GitHub 拉新 release 装源码版。装完先跑一句 pgbadger --version确认版本再往下走。3. 从 PostgreSQL 开日志到跑出第一份 HTML完整配置与命令3.1 PostgreSQL 日志参数log_line_prefix 必须按这个格式配pgBadger 解析依赖两个东西日志里有内容格式它能认。PostgreSQL 侧需要改 postgresql.conf 里的这几项改完执行 SELECT pg_reload_conf(); 即可不用重启实例logging_collector on log_destination stderr log_line_prefix %t [%p]: [%l-1] user%u,db%d,app%a,client%h log_min_duration_statement 1000 log_checkpoints on log_lock_waits on log_temp_files 0 log_autovacuum_min_duration 0log_line_prefix 是 pgBadger 最看重的格式。%t 是带毫秒的时间戳%p 是进程号%l 是会话行号前面说过它负责把跨行日志拼回同一事件%u、%d、%a、%h 分别是用户、数据库、应用名、客户端地址报告里按库按用户分组就靠它们。注意行尾我留了一个空格这是为了让消息正文与前缀隔开pgBadger 匹配前缀时会少很多边界问题。log_min_duration_statement 设 1000 表示只记录执行超过 1 秒的语句如果你怀疑慢查询被漏了先往小调比如 100代价是日志文件明显变大。log_temp_files 设 0 表示记录所有落盘的临时文件不设这个值报告里的 Temp files 区块就是空的。改完在 psql 里验证SHOW log_line_prefix; SELECT pg_reload_conf();SHOW 输出的字符串要和配置文件完全一致如果这里带转义字符或者换行pgBadger 那边大概率要踩坑。注意 reload 只对新产生的日志生效已经写出去的旧日志格式不会变所以改完配置最好等几分钟再跑分析或者直接把旧日志移走。我在 5.1 里讲的那个翻车案例就是没等新日志直接排了 cron。3.2 安装 pgBadgerapt、yum、源码三选一安装这一步没有难度三个常见渠道如下# Debian / Ubuntu sudo apt install pgbadger -y # RHEL / CentOS 需要先开 EPEL sudo dnf install epel-release -y sudo dnf install pgbadger -y # 从 GitHub release 拿源码包安装 tar xzf pgbadger-*.tar.gz cd pgbadger-* perl Makefile.PL make sudo make installapt 和 dnf 适合图省事的场景装完直接有 pgbadger 命令。源码安装的好处是版本新perl Makefile.PL 阶段会提示缺哪些 Perl 模块常见的有 Text::CSV_XS 这类纯文本解析加速模块缺什么用 cpan 装上即可。装完验证一下pgbadger --version命令能输出版本就说明可执行文件在 PATH 里。如果日志文件是 postgres 用户写的而你用 root 跑 pgbadger记得文件要能读、目录要有执行权限否则后面会遇到 Permission denied。还可以顺手看一眼 pgbadger --help 里的支持列表确认你本地的版本支持哪些日志格式尤其是 jsonlog。3.3 第一条命令与报告验证最小命令就一行指定输出文件再把日志文件或通配符丢进去pgbadger -o /tmp/pg_report.html /var/log/postgresql/postgresql-*.log-o 指定 HTML 输出路径。stderr 是最常见的日志格式pgBadger 会自动识别所以不用显式写 -f stderr如果你用的是 csvlog 或 jsonlog才需要在 -f 参数里指定。日志多、内存少时可以加 -q 关闭进度条输出。跑完屏幕上会出现一行类似 “Html report written to /tmp/pg_report.html” 的提示然后用浏览器打开该文件。这里有个新手常踩的点日志文件名带不带日期后缀取决于你部署 PostgreSQL 时是否开了日志轮转通配符写得不对就会“找不到文件”。先 ls /var/log/postgresql/ 看真实文件名再写命令比盲猜靠谱。如果命令报 “Wrong log_line_prefix” 或者解析完报告里全是 0回到 3.1 检查 SHOW log_line_prefix 的输出。第一次打开报告先看总请求量有没有数字再翻到 Top Queries 看有没有语句这两处都有内容才说明日志喂对了。3.4 新报告先看哪五个区块第一次打开 HTML 报告很多人会被满屏图表晃到我建议只看五个区块它们信息密度最高报告区块能看出什么对应日志开关General Statistics总请求数、每分钟请求数、平均耗时先看量级是否异常log_min_duration_statementTop Queries慢语句排行按总耗时、平均耗时、最大耗时排序log_min_duration_statementCheckpoints检查点频率和耗时看是否写盘压力大log_checkpointsLock Waits锁等待次数、等待时间定位锁竞争log_lock_waitsTemp Files每次查询落盘的临时文件大小判断 work_mem 够不够log_temp_filesTop Queries 里的语句是做了参数归一化的同一条 SQL 不同参数值会被聚合成一类字面量被替换成 ?所以排名更公平不会出现同一逻辑的 SQL 因为参数不同被拆成几百行。看的时候先按“总耗时”排再按“平均耗时”排前者找总量凶手后者找单次异常。另外报告里有按数据库、按用户的筛选器排查某个业务模块的问题时先把范围缩到一个库再看 Top比全局列表直观得多。4. 把 pgBadger 变成日常巡检时间窗、增量解析与定时任务4.1 常用参数怎么设-b、-e、-t、-d 的组合用法单次跑报告只是开始要把 pgBadger 用成日常巡检工具必须学会限定时间窗。下面几个参数是我每次写脚本都要用到的参数作用我的常用值-b / --begin只解析该时间之后的行“2024-06-01 00:00:00”-e / --end只解析该时间之前的行“2024-06-02 00:00:00”-t / --topTop 查询显示条数30-d / --dbname只看某个数据库业务库名-s / --size最多读取多少日志量视日志大小而定组合起来是这样pgbadger -b 2024-06-01 00:00:00 -e 2024-06-02 00:00:00 \ -t 30 -o /tmp/report_day.html /var/log/postgresql/postgresql-*.log-b 和 -e 解析的是日志里的时间戳不是文件修改时间所以即使日志文件混着好几天的内容也能精确切出一天。时间格式要带空格命令行里必须加引号这个细节在 cron 里最容易翻车少一对引号 shell 就把时间拆成了两个参数。如果你只要最近一小时-b 也可以用 date 命令动态生成但注意时区和第 5 章那个坑。-s我一般用在探测场景不确定日志有多大、机器内存紧的时候先限制只读最后 2GB 跑一份小报告观察资源消耗再决定要不要全量。4.2 增量分析不要让报告一次比一次慢全量日志只有几 GB 时多久跑一次无所谓跑了一个月后还每次从头解析就是在浪费时间。增量分析的目的很直接只处理上次跑完之后新产生的日志。常见做法有两种。第一种是时间窗法配合日志轮转每天用 -b 限定昨天零点、-e 限定今天零点只解析前一天。第二种是偏移记录法pgBadger 支持记录上次解析到的日志偏移下次从偏移处继续参数名在不同版本里略有差别装好后先跑一下 pgbadger --help 确认你本地版本的写法。我自己的生产环境更常用时间窗法因为逻辑直白、排障容易偏移法省解析时间但日志被轮转删除后会丢失中间段反而不适合日志保留期短的环境。# 每天只解析 24 小时内的日志 pgbadger -b $(date -d yesterday 00:00 %Y-%m-%d %H:%M:%S) \ -e $(date %Y-%m-%d %H:%M:%S) \ -o /var/www/pgbadger/daily.html \ /var/log/postgresql/postgresql-*.log这段脚本用 date 动态生成起止时间配合 crontab 每天执行就是一套最朴素的增量日报。注意 date -d 语法在 Linux 和 macOS 上不一样Linux 用 -dmacOS 要写 -v-1d服务器场景基本都是 Linux不会有歧义。切换增量方式或者日志目录变化之后先跑一次全量报告作为新基线免得新旧报告口径对不上。4.3 用 cron 定时生成日报并自动清理旧报告定时任务我放在 /opt/scripts/pgbadger_daily.sh内容比单条命令多两件事输出目录按日期命名、超过 90 天的旧报告自动删除。日志权限记得给执行用户可读权限我是用一个专门的 postgres 系统账户跑的避免和业务权限搅在一起。#!/bin/bash LOG_DIR/var/log/postgresql OUT_DIR/var/www/pgbadger YESTERDAY$(date -d yesterday %Y-%m-%d) TODAY$(date %Y-%m-%d) pgbadger -b $YESTERDAY 00:00:00 -e $TODAY 00:00:00 \ -o $OUT_DIR/report-$YESTERDAY.html \ $LOG_DIR/postgresql-*.log /dev/null find $OUT_DIR -name report-*.html -mtime 90 -deletecrontab 里加一行30 2 * * * /opt/scripts/pgbadger_daily.sh /var/log/pgbadger_cron.log 21每天凌晨 2 点半执行正好避开业务高峰早上上班打开浏览器就能看到昨天全天的报告。重定向到日志文件是为了排障脚本里 date、通配符出错时能看到痕迹。跑失败了别只盯着 pgbadger 报错先看这个日志文件血泪经验是八成问题出在引号或文件权限而不是 pgBadger 本身。4.4 巡检阈值哪些数字一报警就该处理报告生成只是第一步会读才有效果。我按踩过坑的经验给一档初始阈值你可以根据业务调整指标注意阈值说明每分钟请求数较前一日骤降 50% 以上连接可能被锁或连接池耗尽慢查询最大耗时超过 5 秒单语句异常重点看 Top Queries锁等待次数大于 0 且平均等待超 1 秒业务侧要查锁来源临时文件总量单日累计超 1GB优先调大 work_mem 再观察Checkpoint 间隔频繁且耗时高检查 max_wal_size 和磁盘写入这些阈值不是真理是一份让你有据可依的起点。每种业务对耗时的容忍度不同运行一个月后把报告里的数字和线上故障时间对上再回改阈值比拍脑袋准得多。特别是临时文件这个指标它和你业务里报表查询的频度强相关同一套阈值放到 OLTP 和 OLAP 集群上完全是两个世界。5. 避坑pgBadger 出真报告前最容易翻车的五个地方5.1 log_line_prefix 不匹配一上来就报错现象命令执行后立刻报 “Wrong log_line_prefix” 或 “cant parse”报告文件没生成。原因postgresql.conf 里的 log_line_prefix 不是 pgBadger 认识的格式常见是少了 %t 或 %l或者手写的格式和实际配置有出入。解决先在 psql 里执行 SHOW log_line_prefix;拿真实输出检查。最简单的做法是把 3.1 的配置原样粘贴并 reload再等新日志产生后重跑。如果你的前缀必须带业务标识把 SHOW 输出的字符串原样用 -p 参数传给 pgBadger两边对齐就不再报错。这属于格式对不上就老老实实改配置的问题没有玄学空间。5.2 报告空白但日志明明有内容现象报告生成成功打开 General 页面也有连接数但 Top Queries 和慢查询区块是空的。原因日志里只有连接、断开的记录没有语句执行时长。log_min_duration_statement 没开或者设得太大示例里设 1000 意味着只有超过 1 秒的语句才带 duration 字段小于 1 秒的语句完全不进日志。解决SHOW log_min_duration_statement; 确认值。想分析所有语句临时设成 0日志量和报告体积会明显变大不要一直开着想找相对慢的设 100 到 300 更实用。另外注意 log_statement 和 log_min_duration_statement 不要同时开两者叠加会产生重复记录报告里的数字是虚胖的。5.3 报告时间和本地时间对不上现象报告的横轴是 UTC 时间和业务日志、告警平台差 8 小时对不上号。原因PostgreSQL 的 log_timezone 参数控制日志时间戳时区默认可能跟服务器本地时区不一致pgBadger 按日志内的 %t 解析不换算所以报告时间和日志本身一致但和你 date 看到的本地时间不一致。解决在 postgresql.conf 里把 log_timezone 设成业务所在时区比如log_timezone Asia/Shanghaireload 后重新生成的日志就对齐了。历史日志没法补救只能按差值换算。写 -b/-e 时也带上时区偏移比如 “2024-06-01 00:00:0008”让 pgBadger 明确边界避免整点对不上。5.4 增量数字重复越堆越大现象每天的日报里某个慢查询的总耗时比昨天翻倍明明线上没有新慢查询。原因时间窗没生效cron 命令里的引号丢了date 命令实际执行成了两个参数-b 被 shell 拆掉pgBadger 退化成解析全部日志或者日志文件没轮转天天解析同一批文件。解决先手动跑一遍 4.3 的脚本看生成报告的 General 页时间范围是不是只有昨天一天再把日志轮转配好建议 PostgreSQL 侧按天产生新文件已归档的压缩移走。清理一次全量报告作为新基线之后增量才会准。增量功能依赖稳定的文件列表日志文件改个名、目录换位置都会打破它变更后先跑一次全量。5.5 日志太大把磁盘和内存打爆现象解析 30GB 日志时/tmp 写满或内存飙升机器卡死。原因pgBadger 虽然流式读日志但最后生成 HTML 需要把所有聚合结果写进一个文件报告本身可能几百 MB开启多线程并行解析时每个线程都有自己的缓冲内存占用成倍上涨输出路径默认在 /tmp小分区很快被撑爆。解决先用 -s 限制日志读取量探测性能比如 pgbadger -s 2G -o /tmp/test.html 先跑一小段把 -O 指向有足够空间的目录别用 /tmp日志按天拆开、分批解析成多个报告再配合归档。压缩日志能减少 I/OpgBadger 直接支持 .gz 文件建议轮转时顺手 gzip既省磁盘又省命令行通配符的长度。6. 接进告警与抽查验证用 pg_stat_statements 给报告背书pgBadger 报告的价值在于快速定位但别把唯一一份报告当真相。我现在的习惯是报告出了 Top 慢查询先去数据库里用 pg_stat_statements 交叉验证一遍。SELECT query, calls, total_exec_time, mean_exec_time FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;注意 PostgreSQL 11 之前的字段叫 total_time之后改成了 total_exec_time版本不同查询也要跟着改。对照方式很简单报告里的 Top 语句应该能在上面结果里找到同款数值量级大致一致。如果报告里某条语句总耗时很高、pg_stat_statements 里却几乎没有先别急着下结论检查是不是日志采样窗口和统计窗口不一致或者语句在日志里被拆成了多行没拼全。报告负责“怀疑”pg_stat_statements 负责“确认”两个工具对着看就不会被单个视角带偏。关于归档我还会把每天的 HTML 报告按周打包保留 90 天方便追溯“上周五那次抖动是不是同一批慢查询”。打包用 tar 就行不展开。真要接告警我会在 cron 脚本里加一行 grep从当天日志里捞出耗时最大的那条语句拼成一行文本告警不要直接解析 HTML那东西改版一次坏一次。最后分享一个血泪教训我刚开始用 pgBadger 时改完配置直接排了 cron结果第二天报告全空查了一圈发现是 reload 之后忘记等新日志产生历史日志又是旧前缀白白浪费一天。现在我的固定动作是改完配置先手动跑一条 10 分钟的小日志确认报告有数据、时间正确再上 cron。先小后大、先时间窗后全量、先单文件后通配符这三条顺序能挡住绝大部分翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表