背景#

这轮压测的对象是一套用 WebFlux / R2DBC 写的响应式服务。数据库先从 OceanBase(下称 OB)换到 TiDB Cloud,又换回来,最后跑了一轮同拓扑 A/B 对照。被测接口每笔请求展开约 27 条串行 SQL(其中约 9 条写),外加 16.6 次 Redis 往返。

先把口径摆出来,后面所有数字都在这套配置下:

这次的取值
被测服务 WebFlux + R2DBC 响应式服务,跑在 K8s 的 perf 命名空间
OB 规格 3 × 8c32g observer + OBProxy 专属集群 6c12g × 2(最后一轮升到 32c70g × 3)
TiDB TiDB Cloud 按 RU 计费的那一版,4 台 tidb-server 网关,store 从 5 台加到 8 台
发压端 k6,先是笔记本走公网,之后改成集群内 Pod 直连 Service
阶梯 起始 200/s,步长 100/s,每档跑满 3 分钟,只取稳态窗口
负载打散 10 万玩家,最后一轮 100 万
应用连接池 R2DBC,max-size 一路从 10 调到 400、50、200、250

两边计费方式不同,对比方案要跟着改。OB 买的是固定规格,按 AWS 上的 vCPU 和内存付费,压不压成本都一样;我测的这版 TiDB 按 RU 计费,用多少算多少。直接比吞吐没有意义,所以顺序是先跑 OB,在固定规格下压出一个延迟可接受的 RPS 当基准,再看 TiDB 在相近延迟、相近 RPS 下要花多少。后面反推「每笔业务消耗多少 RU」就是为这一步。

压测前的数据准备#

准备数据的文档写了六节,前五节解决「请求能成功」,第六节解决「数字有意义」。后者的规则要多得多:

  • 请求里带的 transactionId 一旦重复,会命中 Redis 幂等缓存直接返回缓存结果,这时候测的是 Redis 而不是业务写链路,所以每个请求都得带全新 id。幂等键在 Redis 里存活 24 小时,清库不会清掉它,重跑必须换一组新 id。
  • 鉴权是 HMAC-SHA256,签名对象是实际发出的请求体字节。id 每次都不一样,请求体跟着变,签名没法预先算好存起来,发压端每个请求都要现算一次 HMAC。
  • 核心业务数据行有分布式锁加乐观版本号保护。流量全打在同一行上,测出来的是锁竞争,所以默认把负载打散到 10 万个互不相同的业务对象上。
  • A 类型请求每笔都会改写目标行,跑几百万笔之后数据早就不是初始值。每轮压测之前要把数据重置回标准态,否则轮与轮之间不可比。
  • 每笔请求还会往一张 append-only 表里插一行,它是幂等的数据库兜底层。这张表每轮涨几百万行,重置用的是带守卫的 TRUNCATE;早期用逐行 DELETE,在 OB 上还会牵出一次合并等待。

还有一条是踩出来才知道的:发压端生成的 id,长度受限于目标表最严格的那一列。有两轮成功率恒定在 0.85%,前两百多个请求成功,之后近乎全部返回 500。根因是 k6 Job 把完整 pod 名塞进了 RUN_ID,拼出来的业务 id 有 51 字符,超过全库最严的那列 VARCHAR(50);序号变长之后才开始溢出,所以早期请求都是成功的。这种延迟触发的失真在曲线上和真实故障区分不开,现在改成发压端启动时校验 id 长度、不合格就 fail-fast。

这些规则后来固化成了脚本里的一条命令:预检 → 数据重置 → 实测网络延迟 → 预热 → 阶梯加压 → 报告。手工走流程容易漏步骤,漏一步这轮数字就作废。

发压端的位置决定测量口径#

最早是拿笔记本从公网发压,第一组像样的数字是单副本约 450/s。拆开看,压低这个数字的主要不是服务端:

  • 发压端在笔记本,经公网到服务,RTT 全部计入连接占用时长。按 Little’s Law(吞吐 ≈ 连接数 ÷ 单请求持连时长),RTT 直接吃掉吞吐;
  • 笔记本经网关发压本身在 1800/s 左右就顶不上去了,网关侧的 CloudWatch 指标显示服务端没怎么受压。

服务端确实也占一份:每笔请求向连接池借十次左右连接,而不是借一次用到底,每次借出还跑一遍 SELECT 1 校验。这部分留到应用层那一节。

判断慢在哪一段不用登录服务器,k6 会把一次 HTTP 请求的耗时拆开分别统计:

指标 k6 的定义 读数偏高说明什么
http_req_blocked 发起请求前等一个空闲 TCP 连接槽的时间 发压端连接不够用:VU 数超过连接上限,或连接在反复重建
http_req_connecting 建 TCP 连接的时间 连接没复用上;这一段大约就是一个 RTT,公网发压时很显眼
http_req_tls_handshaking TLS 握手时间 同上,握手还要再走一到两个 RTT(TLS 1.3 一个,1.2 两个)
http_req_sending 往对端写数据的时间 请求体不大却很高,是本机写侧顶不上去
http_req_waiting 等第一个响应字节(TTFB) 网络路径和服务端处理混在一起
http_req_receiving 读响应体的时间 响应很小却很高,是本机读侧饱和

http_req_duration 只是后三段之和(sending + waiting + receiving),不含 blockedconnecting。只盯 duration 会漏掉一整类发压端问题:VU 排队等连接槽的时间全落在 http_req_blocked 里,duration 读数很漂亮,吞吐就是上不去。

六段里只有 waiting 可能包含服务端耗时,而且它还混着一个 RTT。其余四段都是发压端自己的开销,请求体和响应体都不大时它们接近 0;不接近 0 的时候,问题在发压机上。把发压机从公网搬进集群,waiting 会直接少掉一个 RTT,服务端什么都没改。

放公网测出来的是用户体感,RTT 本来就算在用户头上。把 k6 挪进集群、直连 Service 之后 RTT 小到可以忽略,剩下的 waiting 才基本是服务端的处理时间,同一个服务的数字也从 450/s 到了 3000/s。

停测判据:从延迟阈值改成吞吐平台#

阶梯加压要解决的是什么时候停。第一版判据是处理延迟 p99.9 超标即停,跑了两天发现两个问题:

  1. p99.9 对样本量太敏感。第一批尾部的几个慢请求就会触发提前中止,往往只攒了几千个样本,分不清是真到顶还是偶发抖动。
  2. 发压机和服务端有网络距离时,p99.9 会把网络抖动算到服务端头上。同一时间窗口里,服务端自己测的处理耗时明显低于客户端读数,多出来的时间在网络路径上。

改成看吞吐平台之后,逐档加压,只要实际吞吐还在随目标速率增长就继续。每档同时看两个比值,都达标才算这一档扛住了:

  • 交付率(本档稳态吞吐 / 目标速率)≥ 90%,绝对判据,管这一档本身跟不跟得上;
  • 增量捕获率(新增吞吐 / 新增目标速率)≥ 60%,相对判据,管新加的压力有多少变成了吞吐。

捕获率用增量而不是相对增长率,是因为步长是固定的绝对值:同样的余量,200 → 300 是 +50%,900 → 1000 只有 +11%,用固定的相对阈值会越到高速率越容易误判到顶。延迟仍然全程记录,但只用于诊断,不再触发停止。

吞吐的口径本身也有影响。k6 自带的 http_reqs.rate 是完成数除以整轮墙钟时间,把 0 到目标速率的爬坡段和降坡段一起算了进去,恒定速率也会被稀释,我实测目标 200/s 时它报 181.5,差约 9%。所以每档只取稳态窗口内的完成数除以稳态时长,爬坡段整段排除。

这两个比值能不能用,还取决于目标速率有没有真的打出去。压测用的是 arrival-rate 开放模型(k6 的 ramping-arrival-rate 执行器),维持目标速率需要的在途 VU 数约等于目标速率乘单请求耗时,而单请求耗时里含一个 RTT。maxVUs 不够时 k6 不报错,只把起不来的迭代记进 dropped_iterations。所以每轮发压前我都重测一次 RTT,拿它估这一轮要开多少 VU。有一轮 1300/s 档的 dropped_iterations 到了 22460,那是发压机并发不够。

某档交付不足时还要看扣掉 RTT 之后的尾部延迟。尾延迟高才是服务端真饱和;尾延迟还低却交付不足、又有迭代被丢弃,多半是发压端没把速率打出去(并发不够、发压机 CPU、网络距离),这种情况我不把这一档记成服务端上限,先调并发或者把发压机搬近再重跑。破线的那一档我会原速率重跑一次确认,重跑恢复正常就当首次是瞬时噪声(GC、邻居、网络抖动),接受这一档继续往上压。

每档 3 分钟是够的。稳态吞吐是对整个窗口求平均,几千个样本就稳;需要更长步长的是想让报告里的 p99.9 也可信,那个由最慢的千分之一决定,经验值是稳态窗口至少三万个样本。

九层瓶颈的迁移路径#

瓶颈前后换了九层,路径是这样的:

flowchart LR A["笔记本公网发压
~450/s"] --> B["k6 进集群
3000/s"] B --> C["OBProxy CPU >90%
吞吐倒退"] C --> D["连接池自设上限
平台 ~3600/s"] D --> E["池 200 + HPA 打满
拐点 ~3900/s"] E --> F["TiDB RU 配额
~715/s"] F --> G["网关弹性
冷实例污染"] G --> H["store 写路径
prewrite 饱和"] H --> I["OB 升配后
Redis 单分片热点"]

每一段的处理方式相同:压到不涨、逐层排除、只改一个变量、重跑验证。只改一个变量这件事我没有每次都做到。有一次我同时改了代理的 CPU 和连接池大小,过载形态从崩溃变成了平台,看着像优化生效,但说不清是哪个变量起的作用。后来单独放大连接池,才确认那个平台是池容量卡的。

过载形态本身是线索。这轮见到三种,对应的排查方向不同:

形态 曲线长什么样 通常意味着 这次的例子
平台 目标速率继续加,稳态吞吐钉住不动,延迟随之抬升,错误率是平的 撞上某个固定容量:配额、池子上限、准入令牌 连接池自设上限;TiDB 的 RU 配额
崩溃 继续加压,稳态吞吐反而回落 过载后开始互相拖累,慢会话堆在同一处自反馈恶化 OBProxy CPU >90%,3400/s 档吞吐倒退到 2649/s
锯齿 吞吐和延迟周期性起伏,跟施加的压力对不上 有别的控制回路在动:弹性扩缩容、副本数被改、后台任务 副本数被两个控制器来回抢,3 ↔ 5 抖动

路径上的前两段前面已经交代过,剩下的按出现顺序分开说。

应用层:R2DBC 连接池的借还次数#

最早那版连接池每 pod 每库只有 10 条连接,并且没设获取超时。池打满时不报错,请求在池上排队,延迟从中位数 160ms 涨到 1000ms,应用日志里没有任何池相关的错误。

定位靠的是线性关系:3 副本(30 条连接)能干净地跑到约 700/s,6 副本(60 条连接)拐点在 1200~1300/s,天花板随连接数线性上移。反过来算,60 条连接支撑 1200/s,相当于每笔请求占用连接约 50ms;对着代码数了一遍,一笔请求约 12 次数据库往返,摊下来每次往返约 4ms,和实测吻合。

这 12 次分布在两处。没有显式事务边界包住的语句,各自向连接池借一次、用完立刻还回去,这类零散租借有 5 次:幂等占位的盲插、锁内预读、响应后的状态回写,以及 outbox 派发时先 SELECT 回查自己刚插的行、发完再 DELETE。事务内则是一条连接被钉住连续跑 7 次往返。R2DBC 的事务绑定单条连接,MySQL 协议又没有管线化,Mono.zip 里的语句在同一条连接上仍然串行执行。

连接池指标里 acquiredpending 对应两种不同的情况:pending > 0 是真缺连接,acquired 接近上限但 pending = 0 是连接被慢 SQL 占住了,前者加池有用,后者要去查上游为什么变慢。另一个读数是 max-acquire-time,不设等于无限排队,池饱和就从可告警的错误变成悄悄上涨的延迟;设成两三秒之后,饱和会以超时的形式暴露出来。

池饱和时 CPU 型 HPA 是盲的:pod 在等连接,不烧 CPU,HPA 看不到扩容的理由。后面 OBProxy 饱和那轮情况相反,HPA 看到应用 CPU 高,把副本从 12 一路加到 18,而每个新 pod 只是往已经饱和的代理再开一批连接。两次的共同点是 HPA 盯的指标和真正的瓶颈不在一层。

OceanBase 侧:OBProxy、连接池与分区 Leader#

把池放大到 400/pod、k6 搬进集群之后吞吐到了 3000/s。再往上,3400/s 那一档的吞吐倒退到 2649/s,捕获率 -85%,平均延迟 1.2 秒,属于崩溃形态。逐层排除:应用副本还有余量;池 acquired 约 330/400 而 pending = 0,说明不缺连接,是每条 SQL 变慢把连接占住了;发压端干净;锁超时为零。最后在 OB 控制台看到 OBProxy 主机 CPU 超过 90%。

给代理加了 1 个 vCPU,同时把池从 400 收到 50,拐点抬到约 3600/s,过载形态也从崩溃变成了平台。小池把排队关在应用内部(表现为 pending 上升),代理那边只见到不到 900 条连接,过载不再自反馈恶化。代价是突发流量在 HPA 追赶的窗口里会以延迟形式表现背压。连接池大小除了限制吞吐,也决定背压堆在哪一层。

这一轮同时改了两个变量,下一轮才补上单变量验证:只把池从 50 放大到 200,整条饱和带抬升 18%,拐点到约 3900/s、峰值 4250/s。这说明上一轮的平台是池容量卡的,不是代理 CPU;如果是代理 CPU,加连接不会涨。新的上限是 20 个 pod × 200 条连接全部占满,同时 HPA 撞上 max=20,池容量和副本拓扑同时到顶。

OB 侧还有一个更早遇到、后来变成例行检查项的问题:分区 Leader 倾斜。OB 的数据按分区切分,每个分区有一个 Leader 副本,写和默认的强一致读都只落到 Leader。我这套表按业务主键做了 32 路哈希分区,但分区数多不等于负载摊开。有一个阶段所有分区的 Leader 都落在同一个 observer 上,其他节点空转,表面上分了 32 个区,强一致读写实际全压在一台机器上。决定负载落在哪台 observer 的不是分区数,而是这些分区的 Leader 摆在哪。

排查看三样:DBA_OB_TABLE_LOCATIONS 里 LEADER 行是不是摊在多个 svr_ip 上、租户的 primary_zone 怎么写的、租户的 unit 是不是本来就只落在一台 observer 上。其中 primary_zone 的标点有区别:分号表示优先级,逗号才表示同优先级、打散。

-- 分号 = 严格优先级,所有 Leader 都会去 zone1
ALTER TENANT my_tenant primary_zone = 'zone1;zone2;zone3';
-- 逗号 = 同优先级,Leader 在三个 zone 均摊(RANDOM 同理)
ALTER TENANT my_tenant primary_zone = 'zone1,zone2,zone3';

均衡是异步的,改完等几分钟再复查。我把这套检查做成了一个只读诊断命令,压测前后各跑一次,直接给出 SPREAD / SKEWED 判定。后面几轮它一直是排除项,比如有一轮读数是 112/112/102 分布在 3 台 observer 上、primary_zone 是逗号,就可以确认不是 Leader 倾斜。

TiDB 侧:配额、弹性、物理三层#

数据迁到 TiDB 之前,有两件准备工作是压测有效性的前置条件,不是提速开关:

  • 把负载生成的 id 从连续递增改成随机的 UUID。连续递增的 id 带共享前缀,会在索引键空间里挤成连续一段,全部写进同一个 Region,形成索引写热点。换成 UUID,键从源头就散在整个空间里。
  • pre-split + scatter 摊开 Region。TiDB 只在 Region 超过 coprocessor.region-split-size(默认 96 MiB,v8.4.0 起官方改成 256 MiB)才自动分裂,而压测的表要么很小、要么每轮重置后被清空,永远长不到触发自动分裂的量,整张表的写就全落在少数几个 Region 上。手动 SPLIT TABLE … BETWEEN (…) REGIONS n 预先切出 Region 再 scatter,这次切了约 1800 个。动过集群拓扑之后,预切的 Region 会被平台合并回收,从 1800 缩回三十几个,所以每轮的重置流程里加了一步重切。

这两件事解决的问题和 OB 侧散 Leader 一样:负载没摊开的时候,测出来的是热点的上限。

迁移后测 TiDB,瓶颈有三层,每层的证据形态不同。

第一层:RU 配额#

阶梯打 2000、2500、3000,交付全部钉在 700~723/s,是平台形态。语句级时间分解给出了直接证据:几条热点语句的 AVG_QUEUED_RC_TIME(资源控制排队)占到总耗时 98%~99%,时间几乎全花在等配额上。

由此可以反推单价。当时买的是 100k RCU,100,000 RU/s ÷ 715/s ≈ 每笔 140 RU。提额之后吞吐按同样的比例抬上去,两个点基本落在一条直线上。这个数后面用来做成本核算。

这里有个容易看岔的地方:用户级资源组显示的是 UNLIMITED,但限制确实存在。配额施加在平台层,按购买的 RCU 生效,SQL 侧看不到配置,只看得到排队。配额额度和物理容量是两套独立的限制,谁先到顶谁决定吞吐。

第二层:网关弹性#

提额到 1M RCU 之后 RU 排队归零,p99 还是秒级。原因在接入层。上一轮压完后集群空闲了一个半小时,TiDB Cloud 的接入/计算层(无状态的 SQL 层)从 4 台被 auto-scale-in 缩回 2 台;这一轮开压,请求打进来它才反向 scale-out 补实例。新拉起来的是冷实例,JIT 还没把热点路径编译掉,连接池和结果缓存都是冷的。多出来的时间集中在 AVG_PD_TIME ≈ 90ms 上,那是 TSO(Timestamp Oracle,全局单调递增事务时间戳的分配端)等待,健康时在 2ms 以内。

同速率对照,4 台热网关 p99 = 192ms,2 台冷网关 477ms,差距在接入层的 cold start,不在底层存储。带弹性的架构里,弹性层本身也是被测系统的一部分。后面几轮我把底座钉住,让实例数固定;另一种做法是把预热对象从 JVM 扩到整个接入层,先用中等负载喂十来分钟,等实例数稳定、冷实例变热再进主窗口。不做这一步,测到的就是扩容过程的抖动。

第三层:store 写路径#

网关钉到 4 台之后,2500/s 达标(p99 = 305ms),但 3000/s 固定 17 副本复测 p99 还是 1300ms。这时排除法只剩一个方向:QUEUED_RC 归零(不是配额)、PD_TIME 0.4ms(不是网关)、store 自报不忙、读路径空闲。落到写语句上,平均耗时 33~36ms 里 prewrite 占了约 31ms,最大值 0.5~2 秒;再看分布,5 个 store 里有 2 个从迁移起就是 0 Region,真正在承接的只有 3 个。

拿这份证据开工单,供应商加了 3 个 store 之后,prewrite 从 31ms 降到 8ms,达标点从 2500/s 抬到 2700/s,3000/s 仍未达标(p99 = 598ms)。这几个数字来自复测,不是供应商那句「已处理」。

加压 20%,尾延迟为什么翻四倍#

2500 → 3000 只多加了 20% 的压力,p99 从 305ms 跳到 1300ms。排队论可以解释:排队时间大致按 1/(1−利用率) 放大,2500/s 大约是写管线 80% 的利用率,3000/s 到 95%,排队被放大四到五倍。

再叠上串行链的尾部放大。每笔请求九条写,单条语句按 0.3% 的概率变慢算,请求级落进尾部桶的概率是 1 − (1 − 0.3%)⁹ ≈ 3%。所以单语句平均 33ms 和请求级 p99 1300ms 并不矛盾。减少 SQL 条数因此同时是延迟优化和成本优化。

同拓扑 A/B 对照#

前面 OB 和 TiDB 的数字来自不同配置(连接池、副本数、HPA 都不一样),严格说不可直接对比。所以最后专门跑了一轮同拓扑 A/B:同一天、同样 17 个固定副本、同样 250 的连接池、同样发压节点、同样阶梯和停测判据,只换数据库连接串。

OB TiDB
p99 < 500ms 的最高达标点 2800/s @ p99 404ms 2700/s @ p99 331ms
3000/s 档的 p99 728ms 598ms
拐点前每档平均延迟 低 20~40ms
正确性 全程零 5xx、零锁超时 同左

两个系统的拐点落在同一个区间,相差 100/s 以内。我不会把这读成两家数据库性能相当,拐点位置更可能是被共同的因素决定的,也就是每笔 27 条串行 SQL:单语句延迟一边是亚毫秒、一边是 2~15ms,乘上 27 倍串行才变成总体延迟上的差异。

对比轮的几个约定我后来固定了下来:发压端用同一套,一个用笔记本、一个用集群内节点的话,比出来的是发压端而不是数据库;拓扑固定,HPA 的 min = max,弹性只留给找极限的轮次;每一档记录这档运行期间的副本数。最后一条是因为档内副本数一变读数就被污染,而客户端数字看不出异常,只有拓扑侧监控能暴露。这个问题我另外写过一篇,是 ArgoCD 的 selfHeal 和 HPA 在抢 spec.replicas

高压下 k6 报出很高的 p99 时,我会先做一次服务端交叉验证,比对服务端自己记录的处理耗时(Spring 的 http_server_requests_seconds_{count,sum,max})与 k6 客户端读数。这轮每个稳态窗口的平均值吻合到 ±0.5ms 以内,全程最慢的那一根请求服务端记 3840.0ms、k6 记 3840.6ms,是同一根,说明尾延迟是真实的。反过来,如果发压端在排队,k6 的均值会系统性高于服务端,因为排队加在客户端侧。这一步能拦掉把发压端排队当成数据库慢的误判。

最后一轮:瓶颈移到 Redis#

最后一轮把 OB 升到 32c70g × 3、OBProxy 同步升配、应用开到 40 副本、玩家基数拉到 100 万,目标是冲 8000~10000/s。结果拐点在约 5600/s,8000/s 那档 p99 到 2217ms 触发门禁停止。

这一轮逐层排除下来数据库没有饱和:OB 的 CPU 只有 22%,连接池 43/100、pending = 0,GC 30ms,消息消费实时跟得上。瓶颈在共享的 Redis 上。

数字对得上:每笔请求约 16.6 次 Redis 往返(GET 9.3、SET 3.2、EVALSHA 3.1、DEL 1.0),5640/s × 16.6 ≈ 93k ops/s,正好是观测到的命令速率平台。吞吐被钉在 Redis 上限除以每笔往返数。

饱和的只是其中一个分片。每笔都要 GET 的那个固定 key(凭据/配置缓存,所有请求同一个 key)全部哈希到同一个 slot:shard 0001 打到 60.7k ops/s、延迟 153ms,shard 0002 只有 31.5k ops/s、1.1ms。热点 key 靠加分片摊不开,一个 slot 不能再分。能动的只剩应用侧,用进程内的短 TTL 本地缓存挡掉这次 GET。

这一轮之后我做容量估算会先看每请求的下游扇出:SQL 条数、Redis 往返、消息事件。27 条 SQL 和 16.6 次 Redis 是同一类问题。一个瓶颈解决之后下一个才会显现,所以按当前瓶颈线性外推出来的容量通常偏乐观。

成本口径#

回到开头那个问题。两种计费模型的成本曲线形状不同,只比一个点不够。这一节只写口径,两边的具体金额和倍数不在这里写。

固定规格(OB 这套)的月账单是常数,跟请求量无关,每笔成本等于月费除以「这个规格能稳定承的 RPS × 计费周期秒数」,压得越满越便宜,闲着也照付。按量计费(RU 这版 TiDB)反过来,每笔成本等于每笔 RU 乘 RU 单价,基本与利用率无关,低峰期不花钱。所以两条曲线要在一个具体的流量剖面上才有交点:峰谷比大、低峰长的形状对按量计费有利,持续高负载对固定规格有利。

代进这次的数字:每笔约 140 RU,2400~3000/s 的运行区间就是 34~42 万 RU/s。这个数乘 RU 单价,是按量计费那边一个月的量;对面是固定规格的包月价除以同一个运行区间。两边都还有没计进来的部分,比如 CDC 和数据订阅,算总账时要补上。

同一个应用层问题在两种模型下的表现也不一样:27 条串行 SQL 放在固定规格上兑现为延迟,放在按量计费上直接兑现为账单。每个业务动作的下游往返数在按量计费下本身就是一个成本参数。

小结#

九个瓶颈里只有三个在数据库本体上:OB 侧的代理 CPU、连接池自设上限、分区 Leader 倾斜,TiDB 侧的配额、弹性、store 写路径,最后一个在 Redis 的单个分片上。剩下的都在应用和发压端自己身上。

能把它们一个个分开,靠的是压测前就固定下来的几条约束。数据有效性那几条规则(幂等、打散、重置、id 唯一且不超长)决定这批数字能不能信;发压端放在哪,决定测的是用户体感还是服务上限;停测判据后来只看吞吐平台,延迟退回诊断用途。定位的过程是一次只动一个变量、每一步留证据。

两套系统的对比是同一天、同拓扑、同口径跑出来的。计费方式不同,能比的也不是谁吞吐高,而是同等延迟下各自要花多少。

还没想清楚的是弹性配额型数据库怎么做稳态容量测试。只要弹性层还在跟着负载动,测出来的数字就带着扩容过程的噪声。这个我接下来想再整理一轮。

参考资料#

相关文章#