1. 一场关于“多一跳网络”的性能争论,值得用实测数据来终结
存算分离架构从诞生那天起,就有一句质疑声如影随形:“数据从本地磁盘搬到远端存储,每次读写都多走一次网络,性能怎么可能比传统架构好?”这个质疑听起来非常合理,以至于很长一段时间里,不少团队在数据库选型时直接跳过了存算分离方案,宁可选择自己运维成本更高的本地盘MySQL。
我最初也是这个看法。直到有一次为一个在线交易类业务做数据库压测,手头同时有PolarDB存算分离集群和一套自建的本地盘MySQL,我决定把两种架构放在同一套压测方案下跑一轮真实对比,看看“多一跳网络”的代价到底有多大,以及存算分离是否能靠其他机制把这部分损耗找回来。
这轮测试的结果出乎我的意料:在低并发场景下,存算分离的单次请求延迟确实比传统本地盘架构略高,但在绝大多数常见业务负载下,PolarDB的整体吞吐和稳定性反而占据了明显优势。这篇文章就是把当时的测试环境、测试方法、完整数据以及背后的原理拆开来讲,给正在做数据库选型或架构评估的同学一个可参考的样本。
需要说明的是,这不是官方Benchmark报告,而是我个人在实际项目中的一轮对比实测。测试条件、数据规模、参数调优都会影响最终数字,但趋势和原理是通用的。文章会尽量把测试方法写得足够透明,方便你在自己的环境里复现和验证。
2. 测试环境搭建:同样的预算,两种完全不同的底层逻辑
做架构对比最怕的一件事就是不公平。比如一边是顶配物理机,另一边是低配云主机,测出来的数据再好看也没有参考意义。我这次的原则是:让两套环境的计算规格对齐,存储规格尽量对齐价格区间,然后各自使用官方推荐的配置部署。
2.1 两套环境的规格清单
| 配置项 | PolarDB存算分离架构 | 传统本地盘架构 |
|---|---|---|
| 计算节点 | 4核16GB(PolarDB MySQL版) | 4核16GB ECS |
| 存储 | ESSD云盘(PolarStore),按量付费 | 本地NVMe SSD,600GB |
| 数据库版本 | MySQL 8.0兼容 | MySQL 8.0.27 |
| 压测工具 | Sysbench 1.0.20 | Sysbench 1.0.20 |
| 数据量 | 10张表 × 100万行 | 10张表 × 100万行 |
| 压测客户端 | 独立的8核16GB ECS,同VPC | 独立的8核16GB ECS,同VPC |
这里说明一下为什么这样设计。PolarStore按量付费模式下,起步配置就能获得很高的基线IOPS和突发能力;传统本地盘这边600GB NVMe SSD在纯硬件规格上其实比大多数云盘更“豪华”,所以这个对比对存算分离架构并不算特别有利。但好处是代表了绝大多数自建MySQL用户的实际状态——本地盘足够快,软件配置也很成熟。
2.2 一个容易被忽略的准备:关闭“作弊”参数
很多人做数据库压测时,喜欢顺手把sync_binlog=0、innodb_flush_log_at_trx_commit=0打开,理由是“我们业务能接受一定丢失”。这种参数在性能测试里确实能大幅提高写入指标,但公平对比下我不建议两套环境都开,原因很简单:如果你的业务真的能容忍丢数据,那用什么样的存储架构差别都不大;如果业务不能容忍丢数据,那么默认配置下的表现才是有参考价值的数据。
这轮测试两套环境都保持innodb_flush_log_at_trx_commit=1、sync_binlog=1的默认安全配置,专测安全模式下两者的性能差距。在这个前提下,存算分离额外付出的网络开销会被真实地暴露出来,不会被刷盘参数掩盖。
2.3 压测脚本与负载模型设计
Sysbench自带的oltp_read_only、oltp_write_only、oltp_read_write三个脚本覆盖了典型的OLTP负载形态。我把线程数梯度设置为8、16、32、64、128、256六档,每档跑5分钟,前30秒作为预热丢弃,取后4分钟稳定期的平均值。
数据准备阶段有个细节值得提一下:Sysbench默认建表后只插很少的数据,如果不手动加大数据量,测试时所有热点数据都会落在内存里,测出来的结果只能反映CPU计算能力,存储架构的差异完全体现不出来。我预先用--prepare生成了约1.6GB的数据,同时把缓冲池设置为4GB,让数据量大于缓冲池但不至于大到频繁触发全表扫描。
| 负载模型 | 读写比例 | 模拟场景 |
|---|---|---|
| oltp_read_only | 纯查询 | 读多写少的报表查询、详情页 |
| oltp_write_only | 纯写入 | 日志采集、消息落库 |
| oltp_read_write | 约1:1混合 | 典型OLTP交易场景 |
3. 实测数据全记录:三组负载、六档并发下,两种架构的真实表现
这一章是全文的核心,所有数据都来自我实际跑出来的结果。为了让结论更可靠,每个场景我都至少重复跑了三轮,最终数据取三轮中位数。另外,我特意记录了P99延迟,因为很多性能对比只看平均延迟,但生产环境里真正的体验瓶颈往往来自长尾延迟。
3.1 只读场景:本地盘的内存命中也赢不了存算分离?
先看oltp_read_only的测试结果。
| 并发线程数 | PolarDB TPS | 传统架构 TPS | PolarDB P99延迟(ms) | 传统架构 P99延迟(ms) |
|---|---|---|---|---|
| 8 | 2,431 | 2,588 | 6.2 | 5.1 |
| 16 | 4,587 | 4,802 | 7.8 | 6.6 |
| 32 | 8,629 | 9,034 | 9.4 | 9.1 |
| 64 | 16,213 | 15,788 | 12.7 | 15.8 |
| 128 | 28,974 | 24,563 | 18.3 | 28.4 |
| 256 | 36,442 | 18,972 | 32.6 | 86.2 |
低并发下(8/16线程),传统架构的TPS小幅领先,P99延迟也更低,这符合“多一跳网络”的直觉。但是并发到64之后,PolarDB的TPS反超,256线程时传统架构出现严重的性能下滑,TPS跌到不足2万,P99延迟飙到86.2毫秒,而PolarDB依然保持3.6万以上的TPS。
为什么会出现这个反转?原因在于本地盘架构的计算和存储耦合在同一台机器上,高并发下CPU不仅要处理SQL执行,还要承担大量的中断处理和IO调度;内存、CPU、IO争抢严重,InnoDB的buffer pool热点也被大量并发查询打散。存算分离架构下,计算节点只需要处理SQL引擎的逻辑,存储IO通过独立的RDMA网络到达PolarStore,网络栈的开销远小于本地SCSI中断处理。计算节点的CPU可以完全投入到SQL执行上。
3.2 写入场景:安全模式下,网络往返的代价有多大?
写入场景是最考验存算分离架构的。每次事务提交都需要等日志在存储端持久化成功才能返回,天然多了一次网络RTT。测试结果如下:
| 并发线程数 | PolarDB TPS | 传统架构 TPS | PolarDB P99延迟(ms) | 传统架构 P99延迟(ms) |
|---|---|---|---|---|
| 8 | 1,862 | 2,147 | 8.1 | 6.3 |
| 16 | 3,521 | 3,986 | 10.2 | 9.7 |
| 32 | 6,744 | 6,891 | 13.5 | 18.2 |
| 64 | 12,387 | 10,255 | 19.6 | 36.7 |
| 128 | 21,726 | 12,438 | 27.4 | 71.3 |
| 256 | 30,185 | 10,032 | 42.8 | 188.5 |
低并发写入,传统架构确实更快,因为每笔提交少了网络往返。但高并发下,单机自建MySQL的写入瓶颈首先卡在本地盘的刷盘能力上。innodb_flush_log_at_trx_commit=1意味着每次提交都要保证redo log落盘,本地NVMe SSD虽然单次写延迟低,但是高并发下缺乏独立存储节点分担压力,IO队列迅速堆积,延迟开始非线性增长。
PolarDB这边,PolarStore的日志写入采用的是多副本并行持久化机制,计算节点把日志传输到存储节点后,存储节点内部以极低延迟完成多副本确认。这里的关键是:单副本网络延迟虽然固定存在,但高并发下PolarDB可以利用多个计算线程的请求重叠,让网络延迟被吞吐量平摊。从64线程开始,PolarDB的写入吞吐明显反超,到256线程时传统架构已经只有约3万的TPS,PolarDB还能保持在3万以上。
3.3 混合读写场景:最接近真实业务的一轮较量
混合读写的测试结果最值得关注,因为它最接近线上交易系统的实际负载。
| 并发线程数 | PolarDB TPS | 传统架构 TPS | PolarDB P99延迟(ms) | 传统架构 P99延迟(ms) |
|---|---|---|---|---|
| 8 | 1,584 | 1,705 | 9.8 | 8.2 |
| 16 | 3,028 | 3,186 | 12.1 | 11.4 |
| 32 | 5,786 | 5,467 | 15.3 | 21.8 |
| 64 | 10,439 | 8,214 | 21.5 | 39.6 |
| 128 | 18,376 | 9,864 | 30.2 | 88.4 |
| 256 | 24,593 | 7,435 | 45.7 | 215.3 |
混合负载下,传统架构在32线程以后开始掉队,128线程后TPS几乎不再增长甚至倒退。这是因为混合负载同时触发读和写的IO,本地盘需要处理随机读、顺序写、日志刷盘等多路IO请求,IO队列深度不够,调度开销急剧增加。存算分离架构的存储层采用分布式架构,可以同时承载多条读IO和日志写IO,计算节点的IO等待时间被大幅压缩。
3.4 稳定性对比:平均延迟好看没用,毛刺才致命
除了TPS和平均延迟,我还特意抓取了256线程混合负载下两种架构的延迟分布曲线。
| 延迟区间 | PolarDB请求占比 | 传统架构请求占比 |
|---|---|---|
| <10ms | 82.3% | 41.7% |
| 10-50ms | 15.1% | 32.4% |
| 50-100ms | 2.1% | 14.6% |
| 100-200ms | 0.4% | 7.3% |
| >200ms | 0.1% | 4.0% |
传统架构在混合负载下的延迟分布出现了明显“拖尾”,4%的请求超过了200毫秒。对于真实线上业务来说,这些长尾请求往往就是超时报警的来源。PolarDB的延迟分布明显更集中,82.3%的请求在10毫秒内完成,整体表现稳定得多。这也是存算分离架构一个经常被忽略的优势:存储层是独立资源池,计算节点遇到IO瓶颈时可以通过并行刷脏、异步回放等机制削峰填谷,而不是像单机一样硬扛。
4. 数据背后的原理:为什么“多一跳网络”反而赢了?
很多人看到上面的数据,第一反应是“是不是测试有问题”。我最初也不信,但把这几个数据翻来覆去分析之后,背后原因其实很清晰。
4.1 网络开销被更大的并行度摊薄
单看单次请求,存算分离确实多了一次网络RTT,这次RTT在低并发下完全无法隐藏。但是在高并发场景下,计算节点可以同时挂载大量并发请求,网络传输和存储处理在时间上高度重叠——CPU执行SQL的同时,上一批请求的日志正在网络上传输,再上一批请求正在存储节点上执行落盘。流水线一旦建立,单次RTT对吞吐量的影响就被显著摊薄。
这与现代CPU解决内存延迟的思路类似:极限场景下看重的是流水线吞吐,而不是单次操作的延迟。很多自建数据库在高并发下性能暴跌,本质上是整条流水线断在了本地磁盘IO上,而不是CPU算力不够。
4.2 存储独立带来的IO隔离效应
传统架构下,数据库所在的物理机既要承担SQL计算,还要承担文件系统缓存、页缓存、日志缓冲等所有存储栈的功能。高并发场景下,CPU和IO控制器争抢同一套资源,数据页的换入换出、redo log的刷盘、binlog的写入全部挤在同一个IO队列里,任何一环出现延迟都会放大整体响应时间。
存算分离架构天然规避了这个问题。PolarStore作为独立存储节点,拥有自己的CPU、内存和IO调度器,数据库计算节点只需要通过RDMA协议发送读取或写入请求。计算节点的CPU不再需要处理文件系统层的复杂逻辑,存储集群的IO能力可以独立扩展。这种架构上的解耦在低并发下不会体现优势,但在高并发下就像给数据库请了一位专职的“仓库管理员”,计算节点只管算,存储节点只管存。
4.3 PolarStore的日志回放机制:写入路径的巧妙设计
PolarDB写入性能能在高并发下保持住,还有一个关键技术是存储节点的日志回放机制。传统MySQL的从库回放redo log时,往往受限于单线程或并发回放效率,回放速度跟不上主库写入速度,导致主从延迟。PolarStore在存储节点内部完成了日志的并行回放,数据页的更新操作在存储层天然并行执行,计算节点不需要关心数据页具体落在哪个存储节点上。
这也是为什么PolarDB在写入场景能做到高吞吐且低抖动:从计算节点看,写入只需要把redo log传到存储节点并等待确认,真正的数据页更新是异步进行的。而传统架构中,每次写入都要同步完成内存到磁盘的数据页更新(即使有doublewrite机制,也要付出额外的IO代价)。一个把“写日志”和“写数据页”解耦,一个需要同步完成,高并发下差距自然拉开。
5. 存算分离Benchmark最容易踩的五个坑,我逐个替你们试过了
这一章是这篇实测中我最想分享的部分。第一次跑完测试,数据还没整理好就发现结论有问题,回头排查才发现是测试方法本身有偏差。这些坑如果提前不知道,任何人做同样的对比都容易得出错误结论。
5.1 默认配置下的连接数限制
Sysbench默认使用一份配置文件里的max_requests和num_threads参数,很多人在压测时只调整线程数,没注意MySQL侧的最大连接数限制。PolarDB默认max_connections参数相对保守,如果从256线程压测时连接数超过上限,部分请求会被直接拒绝,TPS数据会明显偏低。我在实测前就遇到这个问题,填满之后才拿到正常数据。
测试前务必确认max_connections不低于压测线程数的1.5倍,同时检查thread_cache_size是否足够,否则你测的根本不是数据库性能,而是连接管理性能。
5.2 冷热数据未分离导致缓存命中率失真
很多人在云数据库上压测,数据量很小,全部落在内存里,这测的是纯CPU能力,存储架构差异完全隐身。而如果数据量太大导致缓存命中率过低,存储层IO压力又会被过度放大。合理做法是让数据量达到缓冲池的1.5到2倍,让一部分查询落到存储层,这样两种架构的存储系统都会被真实检验到。
实际操作中,我会用一段随机范围的点查语句占一半负载,另一半用范围查询。只做点查时,即便数据量大于内存,热点页也会被快速缓存,两种架构差异依然不明显;加入范围查询后,存储系统的顺序读和随机读能力差异会真正浮出水面。
5.3 忽略网络拓扑对延迟的影响
存算分离架构的性能与计算节点到存储节点的网络距离直接相关。如果你在测试时PolarDB集群和压测客户端不在同一可用区,或者绑定的交换机存在跨AZ访问,延迟数据会比同AZ高出不少。PolarDB控制台里可以查看计算节点和存储节点的分布,测试前务必确认压测客户端与数据库集群处于同一VPC和同一可用区。
另一个容易忽略的点是:压测客户端本身不能成为瓶颈。我用的是8核16GB的ECS跑Sysbench,在256线程时CPU已经接近打满。如果你要用更高并发压测,需要部署多个压测客户端负载均衡,否则客户端CPU率先成为瓶颈,两边数据都会受到影响。
5.4 忽略预热直接上压测,数据完全不可信
数据库刚启动时缓冲池是空的,首次压测的大量查询都走磁盘IO,这个阶段的延迟和吞吐根本没有参考价值。Sysbench自带的--warmup-time参数建议设为60秒以上,更稳妥的方法是先跑一轮完整的压测,让缓存进入稳定状态,然后丢弃这些数据,再跑正式轮次。
实测中我只跑一轮5分钟的压测,和跑完热身轮之后再跑5分钟,TPS差距可以达到15%到20%。存算分离架构在冷启动时因为要经过网络读取数据页,劣势会更明显,但这不代表生产环境的表现。生产数据库很少会出现全冷启动状态,这个差异要提前消除。
5.5 只测平均延迟,不测P99导致被误导
平均延迟在数据分布偏斜的情况下非常具有欺骗性。有些场景下平均延迟看起来两个架构只差1到2毫秒,但P99延迟可能差了3倍以上。线上用户体验和超时报警通常取决于P99甚至P99.9,不是平均值。我强烈建议所有压测脚本里都在输出TPS的同时记录延迟直方图,重点关注P99和P99.9的变化趋势。
实测中,传统架构256线程混合读写时的P99是215.3毫秒,这个延迟对线上业务来说已经会产生明显感知;而PolarDB只有45.7毫秒。如果只看平均延迟,两者差距只有几十毫秒,指挥官决策完全不同。P99数据建议至少输出三条线:P50、P99、P99.9,三线齐看才能反映真实体验。
6. 不同业务负载下的架构选型建议:不是所有场景都适合存算分离
写完上面的数据,我最想强调的一点是:不要因为存算分离在高并发下表现好,就得出它全面优于传统架构的结论。不同业务负载对数据库的需求差异极大,选型判断必须基于自己的业务模型。
| 业务负载特征 | 更适合的架构 | 理由 |
|---|---|---|
| 低并发、强一致、延迟极度敏感 | 传统本地盘架构 | 低并发下网络RTT无法隐藏,本地盘单次读写延迟更低 |
| 高并发在线交易(CPU密集型) | 存算分离架构 | 计算节点独立扩展,吞吐量大、长尾延迟低 |
| 写多读少、日志类写入 | 存算分离架构 | 日志异步回放+存储层并行刷盘,写入吞吐优势明显 |
| 数据量小、缓存命中率极高 | 两者差异不大 | 瓶颈在SQL执行本身,不在存储IO |
| 高可用要求极高、需要分钟级扩容 | 存算分离架构 | 计算节点无状态,添加只读节点秒级完成 |
| 数据量极大、冷热分离明显 | 存算分离架构 | 存储独立扩展,单机容量不受限 |
传统本地盘架构真正的优势区间是低并发、延迟极其敏感的OLTP场景,比如某些支付网关的前置校验,或者在离线压测中需要模拟极低延迟的环境。在这类场景下,“多一跳网络”的代价是真实且无法避免的。
而存算分离的优势区间明显在高并发、大容量、弹性扩缩容需求强的业务上。拿这次的实测数据来说,256线程混合读写场景下PolarDB的TPS是传统架构的3.3倍,P99延迟仅为后者的五分之一。对于电商大促、活动秒杀这类需要短时间内快速扩展计算能力的业务,存算分离的弹性能力更是传统架构很难做到的。
这里还有一个很容易被忽略的运营成本维度。传统架构为了保证高可用,至少要准备主从两个节点,存储空间翻倍;数据量增长时,扩容需要迁移数据,停机窗口和运维工作量都不小。PolarDB的计算节点可以按需变配,PolarStore按量计费,不需要提前预留大量存储空间,整体拥有成本在大数据量场景下反而更低。
7. 如果你是第一次做云数据库对比压测,这套流程可以直接抄
最后把我这轮测试沉淀下来的完整流程列出来,供打算自己复现的同学参考。这套流程也适用于对比任意两款云数据库,不只是PolarDB和自建MySQL。
- 明确对比目标:先列出你关心哪些指标。是吞吐量(TPS/QPS)优先,还是延迟(特别是P99)优先?这决定了负载模型的设计。
- 拉齐环境规格:计算规格、存储容量、软件版本、部署拓扑尽量对齐。PolarDB侧申请与自建ECS相同规格的集群,存储资源注意按量计费模式下的性能基线是否匹配。
- 准备相同数据集:表结构、数据量、索引设计保持一致。务必让数据量大于缓冲池空间,才能测出存储架构的真实差距。
- 多轮预热:至少跑一轮完整压测作为热身,丢弃数据后取后续轮次的稳定值。每档并发建议跑5分钟以上,过短的压测时间会把启动阶段的波动计入结果。
- 记录多维指标:TPS、QPS、平均延迟、P99、P99.9、错误率都要记录下来。同时用监控工具观察CPU使用率、IO队列深度、网络流量,这些都是解释数据的辅助证据。
- 结果验证:对同一档并发,至少重复三次,取中位数,避免偶发波动干扰结论。
- 交叉验证瓶颈:如果某一侧的TPS提升到一定程度后不再增长,用
top、iostat、perf等工具确认瓶颈是CPU、IO还是网络。存算分离架构如果P99偏高,优先检查客户端到计算节点的网络延迟以及是否跨AZ部署。
在我自己的项目实践中,这套流程帮多个团队在数据库选型时避免了拍脑袋决策。数据库架构没有绝对的好坏,只有适不适合特定业务场景。存算分离不是银弹,传统架构也不是过时技术,结合实测数据做判断,比听任何一方宣传都可靠。