ARTICLE DETAIL

资讯详情

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

PostgreSQL插入性能测试实战:场景拆分、工具选型与避坑指南

PostgreSQL插入性能测试实战:场景拆分、工具选型与避坑指南 前阵子我们内部要评估一个数据同步组件目标库是PostgreSQL业务方提了个很直接的问题单机每秒到底能写入多少行说实话插入性能这四个字听起来简单真要给一个可信的数字牵扯的东西比想象中多得多。我在设计测试方案的时候一边翻官方文档一边用DeepSeek帮我梳理盲区和生成脚本最后沉淀出了一套能直接复用的插入性能测试方法。下面就是我这次实践的全过程复盘内容包括测试场景怎么拆、环境参数怎么定、脚本怎么写、数据怎么解读以及几个我踩过、也大概率会坑到你的细节。适合正在做数据库选型、中间件压测或者想系统摸清PostgreSQL写入能力的DBA、后端开发和测试开发同学参考。1. 让DeepSeek参与测试方案设计先想清楚分工1.1 我抛给DeepSeek的第一个问题当时我把上下文压缩成一段话直接丢了过去我需要你帮我设计一套PostgreSQL插入性能测试方案。背景单机PostgreSQL 16主库异步复制表结构大约8个字段包含一个自增主键、两个普通索引未来会有每秒几万行的写入需求。请从测试目标、测试场景、工具选型、关键指标、环境参数、报告输出六个方面给我一份方案框架同时指出这个任务里我容易漏掉什么。DeepSeek给出来的方向是对的场景拆分、参数清单、pgbench做基准、自定义脚本做业务拟合、监控WAL这些都在点子上。但它列的测试场景有八类其中像备库延迟、同步提交、逻辑复制这类在我们当前架构里根本用不上。这个现象其实很典型AI擅长大而全不擅长判断哪些维度对你当前的问题是必要条件。所以我的处理方式是拿它的框架当对照清单再按业务目标做减法。这也是我想说的第一件事让AI参与设计不等于把方案交给AI它负责扩充你的思考边界最后拍板的人还是你。1.2 人机协作的边界它补齐盲区你定判定标准在这次协作里DeepSeek真正帮到我的有三个点第一把插入性能拆成单条事务、批量事务、COPY导入、并发写入四条线防止我只盯着某一种写法得出偏颇结论第二提示我记录WAL增长速率、checkpoint行为、锁等待事件这些指标平时容易被忽略第三帮我把采样脚本和压测脚本的框架搭好我只需要修改参数和补业务逻辑。但另一方面它不会告诉你你们业务根本不需要测同步复制这类上下文相关的事也不会告诉你这个环境是虚拟机测出来的绝对值没有参考意义。所以我在全流程里给自己定了一条规则AI提供的方案和脚本都当成初稿来用所有结论必须经过一次真实压测的验证。下文提到的所有命令、参数和坑都是我实际跑过之后才写下来的。协作结束之后我手里最终形成的检查清单是这么几条测试目标里必须写明业务形态是OLTP还是批处理测试环境必须记录硬件基线场景矩阵必须覆盖单条、批量、COPY、并发每次压测必须同时采集WAL和IO指标报告的每个结论必须能追溯到具体一次压测记录。AI能帮你把清单列得更全但你得自己验证每一条在当前项目里是否成立。2. 测试前的环境与参数先把坑填平再谈性能2.1 版本、部署方式和硬件基线首先把PostgreSQL的版本定下来。测试性项目我建议直接用当前稳定版比如16或17。不要用便携版或者来路不明的二进制做压测因为你无法判断它是不是带着奇怪的编译参数。我见过便携版压出来的结果比官方Docker镜像低接近20%的情况后来查下来是glibc版本太旧导致字符串排序和哈希路径异常慢。部署方式上如果要接近生产源码编译和官方Docker镜像都是可以接受的。源码编译并开启默认优化后性能通常最优Docker官方镜像和纯源码编译之间的差距一般在5%以内。我自己在这类测试里用Docker比较多主要是环境可重复性好容器一删全部依赖清干净反复调整参数很方便。但有一个前提不要在Docker Desktop这种虚拟化层很厚的环境下压测结果偏差太大。至少要扔到Linux物理机或规格明确的云服务器上跑。硬件基线至少要记录CPU型号、核数、内存、磁盘类型。插入性能对磁盘敏感度极高SSD和机械盘能差十倍不止如果目标环境是云盘还要标清楚是ESSD还是普通云盘IOPS上限是多少。这些信息不写进报告测试结果就失去可比性。我自己的习惯是压测前先用fio或dd简单摸一下磁盘顺序写和随机写的基线这样后续看到TPS上不去时能很快判断是不是磁盘本身就到顶了。2.2 参数组这些配置直接决定插入上限在开始压测之前必须定一个被测参数组。插入性能受以下参数影响最大参数对插入的影响测试建议fsync决定每次提交是否强制刷盘开启时TPS受磁盘IO直接制约关闭时数据可靠性基本丧失生产值测试必须开启单独测关掉后的极限可以作为理论天花板参考synchronous_commit设为off时会把WAL刷盘延后到组提交窗口能显著提升单条插入TPS生产默认on如果业务可接受可专门测off场景full_page_writes首次checkpoint后页面首次修改会写整页影响写入放大生产建议保持on压测时应理解它的写入放大效应wal_levelreplica以上会多写一些WAL信息对插入性能略有影响保持与生产一致max_wal_size / checkpoint_timeout决定checkpoint频率checkpoint期间的IO抖动能拉低插入峰值建议记录默认值和调优后两组shared_buffers主要影响读缓存对纯写入路径影响有限保持生产值maintenance_work_mem影响autovacuum效果间接影响长期压测的稳定性长时间压测时建议调大我让DeepSeek把参数清单拉出来之后挨个查了官方文档对每条的影响才决定哪些进测试基线。这里有个容易翻车的地方如果你为了刷高分把fsync和synchronous_commit全部关掉那你测出来的不是PostgreSQL的插入性能而是把数据先倒进内存再慢慢落盘的上限。这种数字自嗨可以但不能当生产依据。所以我的习惯是测两组一组生产参数组一组极限参数组两组数据并列摆出来谁也别想把极限值包装成生产值。2.3 表结构、索引数量和前置数据量测试表不是随便建一张就行。字段类型直接影响性能比如每行都带uuid生成函数和你用自增bigint做主键插入路径完全不同。索引数量的影响更夸张我实测过一张表从零索引到挂三个普通索引单条插入性能直接下降约40%到50%。原因很简单每次插入不只是往表里写一行还要维护所有索引页的变更。如果表还有触发器或外键约束开销还会继续往上加。前置数据量也要提前说清楚。空表插入和已经有1000万行的表插入表现差异很大。数据越多B树越深索引页更可能集中在热点区间插入时缓存命中率更差。所以测试方案里应该定义至少两个阶段空表起始阶段和生产数据量阶段。实际业务里绝大多数情况是持续写入一个已经有数据的大表不是往空表里灌数据这个细节非常影响结论的可信度。3. 四个插入场景的拆分插入性能从来不是一个数字3.1 场景A单行事务插入业务方问的每秒能插多少条最常指的是这个场景来一条写一条每条都是独立事务。这种模式在高吞吐业务里其实已经很少见但它是其他所有场景的基线。压这个场景最合适的工具是pgbench但要用自定义脚本默认的TPC-B脚本穿插了账户更新和查询不适合。先建表CREATE TABLE insert_test ( id bigint PRIMARY KEY, c1 text, c2 text, created_at timestamptz DEFAULT now() );然后写insert.sql\set id random(1, 1000000000) INSERT INTO insert_test(id, c1, c2) VALUES (:id, md5(random()::text), md5(random()::text));执行压测pgbench -f insert.sql -c 16 -j 4 -T 120 -P 5 testdb这条命令的意思是16个客户端、4个线程模拟并发持续压测120秒每5秒打印一次进度。-c是连接数-j是线程数通常设为CPU核数的一半到四分之一。单条事务场景下瓶颈基本都会出现在WAL刷盘上观察iowait和TPS曲线会看到明显的周期性抖动那是checkpoint在捣乱。3.2 场景B批量插入驱动批处理任务、日志落库这类场景批量插入的吞吐量会比单条事务高一个数量级。因为它把N条insert收进同一个事务只做一次提交刷盘。批大小的选择直接影响效果我建议分别测10、50、100、200、500、1000这几个档位。批量插入的脚本样本\set id random(1, 1000000000) INSERT INTO insert_test(id, c1, c2) SELECT :id g, md5(random()::text), md5(random()::text) FROM generate_series(1, 100) g;把100换成不同批大小跑一轮你会发现从10到100TPS几乎是线性爬升但从500到1000收益开始衰减甚至下降。原因是单事务太大事务日志写入量、内存中未提交的脏页都会增加到一定规模后反而拖慢速度。我们内部一般把100到500当甜点区但不同参数、不同磁盘下最优批大小不同不能照抄。3.3 场景CCOPY导入COPY是PostgreSQL导入数据的专用通道绕过SQL解析直接写入性能远超insert常用于初始化数据、离线导入、数据迁移。测COPYCOPY insert_test(id, c1, c2) FROM /tmp/test_data.csv WITH (FORMAT csv);如果目标是测文件导入速率直接看导入耗时即可。这里有个可以顺带验证的技巧COPY其实也可以塞进事务里控制WAL刷盘频率和批量insert的机制有互通的地方。压这个场景时最需要注意的一点是数据文件本身别放在和数据库相同的磁盘上否则磁盘IO会互相抢测出的数就不准了。3.4 场景D并发写入真实业务里插入性能最关心的是并发写到什么程度会退化成什么样。这个场景要把连接数作为变量从8、16、32、64逐一往上压同时观察两个东西TPS是否还随连接数线性增长以及等待事件是否大量出现。查询等待事件SELECT wait_event_type, wait_event, count(*) FROM pg_stat_activity WHERE state active GROUP BY 1, 2 ORDER BY 3 DESC;如果大量线程卡在WALWrite和WALArchive等事件上说明WAL已经成了瓶颈如果卡在buffer content之类的地方说明内存或锁竞争吃紧。并发场景的另一个观察点是死锁批量insert在并发场景中更容易出现主键冲突或锁升级问题测试脚本里如果用了固定随机种子务必留意。3.5 统一监控项WAL生成速率和checkpoint行为不管跑哪个场景我都建议同时监控WAL的生成速率。插入的所有数据最终都会变成WAL它比TPS更能反映磁盘的真实写压力。采集方式SELECT pg_current_wal_lsn();隔30秒再查一次通过pg_wal_lsn_diff算出两个LSN的差值就是这段时间的WAL量。换算成MB每秒再和磁盘的实际写入能力做比较你会对瓶颈在哪有更直观的判断。这个指标在批量插入和COPY场景里尤其重要因为看起来CPU不高磁盘可能已经默默打满了。4. 工具和脚本pgbench做标准自定义压测做业务贴合4.1 pgbench能测什么不能测什么pgbench是官方内置的压测工具优势是简单、可控、报告自带TPS和延迟分位。它的短板也很明显默认脚本不是纯插入自定义脚本虽然能改但只能做无状态SQL压测做不到复杂的业务时序。所以我的用法是pgbench负责标准场景的快速对比测试比如验证参数调整是否有效果真正要贴近业务形态另外写一套脚本做补充。4.2 一个基于asyncpg的插入压测脚本下面这段脚本是我用Python写的目标是模拟多个worker并发批量插入的业务形态同时每秒记录TPS。依赖asyncpg直接pip安装就行。import asyncio, time, random, statistics import asyncpg DSN postgresql://user:passwordlocalhost:5432/testdb TABLE insert_test BATCH 100 WORKERS 16 DURATION 60 async def worker(wid, stats, loop): conn await asyncpg.connect(DSN) sql ( fINSERT INTO {TABLE}(id, c1, c2) fSELECT $1::bigint g, md5(random()::text), md5(random()::text) fFROM generate_series(1, {BATCH}) g ) base wid * 100000000 i 0 deadline loop.time() DURATION while loop.time() deadline: start loop.time() await conn.execute(sql, base i * BATCH) cost loop.time() - start stats.append((int(loop.time() - start), BATCH / cost)) i 1 await conn.close() async def main(): loop asyncio.get_running_loop() stats [] workers [asyncio.create_task(worker(w, stats, loop)) for w in range(WORKERS)] start int(loop.time()) while any(not t.done() for t in workers): await asyncio.sleep(1) now int(loop.time()) points [v for k, v in stats if start k now] tps sum(points) / len(points) if points else 0 print(f{now - start}s avg_tps{tps:.0f}) stats[:] [(k, v) for k, v in stats if k now] await asyncio.gather(*workers) if __name__ __main__: asyncio.run(main())脚本的统计逻辑比较粗真实项目中建议把每次采样落进时序数据库但核心骨架就这些。这个脚本的意义在于批大小、并发数、表名都能用参数控制一张表可以快速测出不同配置组合的表现。4.3 DeepSeek帮我补上的三个脚本细节这个脚本第一版是我自己写的跑出来的数据总觉得哪里不对。后来让DeepSeek审了一遍它指出三个问题我觉得都值得记下来。第一压测前没有预热。数据库的shared_buffers在进程刚启动时是空的头几秒的TPS会被冷缓存拖低所以正式测试前要跑10秒到30秒的预热数据这段统计要丢弃。第二数据不够随机。第一版用了固定步长递增所有worker写入的行会集中在相近的索引页上导致索引热点锁竞争并不能代表生产环境的随机写入。改成md5之类的随机串之后数据分布更接近真实。第三采样粒度太粗。只看总TPS会掩盖周期性抖动checkpoint一触发TPS可能瞬间掉一半如果只看平均值你永远不知道发生了什么。所以脚本里我加了每秒采样后面画趋势图时才能看见那些坑。5. 跑完数据不能急着信六个实测坑5.1 关掉fsync跑出来的高性能是幻觉很多人压插入性能喜欢把fsyncoff、synchronous_commitoff一起开看到TPS从几千飙到几万就觉得解决了问题。这类数据的适用场景极其有限相当于宣布我们接受断电崩库丢数据的前提下能跑多快。我的做法是生产基线永远用默认配置压一组再把fsync关掉压一组两组都写进报告标注清楚各自假设。这样既能看到极限也不会给业务方错误的预期。5.2 索引是插入性能的隐形杀手我在2.3节提过索引的影响这里再说一个具体的例子。同样一张表无索引状态下批量插入可以达到约12万行每秒加上两个普通索引直接掉到6万行每秒。如果你在测试表上没有复刻生产环境的所有索引你测出来的数字没有任何参考价值。这个坑几乎每周都有人踩每次看到我们压测能到20万TPS后来生产被几条insert卡死基本都是测试环境表结构比生产瘦身太多的原因。5.3 连接池把TPS曲线抹平了如果你的压测客户端经过pgbouncer或者应用侧连接池再打到PostgreSQLTPS曲线会被连接池平滑掉不少。连接池不会提升数据库的实际处理上限它只是把请求排队的时间隐藏了。这会带来一个假象TPS很稳定P95很低。实际上请求是从池子里均匀放出去的单条请求的真实等待时间反而可能更长。测插入性能的时候建议直连和不直连各测一次这样能区分瓶颈在数据库还是连接链路。5.4 Docker桌面版和虚拟化环境数字不可信我在2.1节提过这个问题它的严重程度值得单独再说一遍。Docker Desktop在macOS和Windows上走了一层虚拟机和文件系统映射压出来的TPS和Linux物理机可以差3倍以上。如果只有笔记本可以用那它更适合做相对对比比如验证参数修改对趋势的影响但绝对数值一个字都不要往测试报告里放。5.5 压测时间太短结论等于抛硬币跑30秒和跑10分钟经常是两个结论。因为30秒通常覆盖不了一个完整的checkpoint周期而checkpoint期间的IO抖动恰恰是生产环境必然经历的。再加上autovacuum的参与短周期测试很容易高估性能。所以我的习惯是单场景至少压10分钟取趋势曲线看稳态而不是只抓一个最高峰。5.6 autovacuum和checkpoint会在背后悄悄搞事长时间压测时autovacuum会开始清理膨胀的dead tuple这本身也是IO开销。如果你要测的是长时间运行下的稳态性能应该在报告里注明autovacuum是否参与、它消耗了多少IO。有次我压测中途发现TPS周期性塌陷排查了半天才看出是checkpoint_timeout耗尽导致强制checkpoint当时磁盘的IOPS又不够每次checkpoint都把写入拖垮好几秒。解决办法要么调大max_wal_size要么接受这种抖动并把它的影响算进结论。6. 数据采集与报告模板让结果能被人信服6.1 同步采集的系统指标只记录TPS远远不够。要让一份压测报告站得住脚至少要同时采集以下内容指标采集方式用途TPS和P95/P99延迟pgbench输出或脚本采样核心业务指标WAL增长速度pg_wal_lsn_diff判断磁盘写压力iowait和磁盘吞吐vmstat / iostat判断是否卡在IOCPU使用率mpstat判断是CPU-bound还是IO-bound锁等待事件pg_stat_activity判断并发瓶颈checkpoint频率pg_stat_bgwriter解释TPS抖动这些数据要按时间对齐不然事后没法对应。我一般用压测脚本每秒打点写成CSV测试结束后直接画趋势图。趋势图比平均值重要得多因为平均值会把高峰和谷底全抹平。6.2 让DeepSeek生成报告框架再往里填实测值报告模板不需要自己从零憋。我让DeepSeek按测试环境、参数配置、场景矩阵、实测数据、瓶颈分析、结论建议六个模块生成了一版在此基础上每节补充真实数据。填完再回顾有缺项就补跑。这里推荐一个小习惯凡是表格里空着的数据要么补测要么删掉这一列绝不放待验证进去。报告是给人做决策用的空白会让整份报告的可信度往下掉。如果业务方的要求是保证每秒5万行写入不丢数据那你拿着报告应该能指出在什么并发、什么批大小、什么环境参数下这个目标有冗余在什么条件下达不到差距来自IO还是锁。6.3 一张可以直接抄的结论表下面是我这次压测用的结论模板字段可以按需改场景并发批大小平均TPSP95延迟(ms)WAL增速(MB/s)主要瓶颈单行事务16178002.62.4WAL刷盘批量插入16100520003.115.8磁盘IOCOPY导入110000138000-41.2磁盘IO并发写入32100610004.218.5锁等待填完这张表再往上一贴环境参数和被测参数组测试报告就能当结论给别人看了。最后说点自己的体会。这次测试做下来DeepSeek确实帮我省了很多查文档的时间方案的视野也比我一个人想得全但它给的所有东西我都当成待验证的初稿用指挥棒始终握在实测数据手里。如果你也打算用类似方式摸一摸数据库底细建议先花10分钟把环境参数和场景矩阵写清楚再让AI帮你补盲区最后不管数字好看难看都连环境一起如实写进报告。数据库不会骗人但测试方法会方法论对了结果才有意义。
返回列表