第 4 章 · 延迟

延迟:一个总数说明不了任何事

一、为什么要分四段

"Claude 好慢"这句话背后有四种完全不同的原因,修法也完全不同:

段量的是什么慢了说明怎么修
DNSgetaddrinfo 返回耗时解析器慢,或者 DNS 请求本身被绕了一大圈换解析器;关掉不必要的 split-DNS
TCP三次握手完成耗时到出口节点的物理距离 / 拥塞。这一段基本就是一个 RTT换出口节点
TLS握手完成耗时正常是 1~2 个 RTT。明显高于 TCP 段的倍数 → 链路丢包在重传换节点或换协议;查丢包
首字节(TTFB)请求发出到收到第一个字节服务端在算,或者中间代理在排队这一段你改不动,只能确认不是自己的问题

只有一个总数的时候,这四种情况长得一模一样。

一个真实的读法示例(探测 api.anthropic.com):

DNS      3 ms    2%
TCP     98 ms    8%
TLS    143 ms   12%
首字节  920 ms   79%

79% 花在首字节上 —— 意味着换节点几乎没用:网络这一段(TCP+TLS)总共 才 241 ms,剩下的时间在对面。这时候去折腾代理配置是白费功夫。

反过来,如果 TCP 占了 60%,那就是纯粹的距离问题,换个近的节点立刻见效。


二、这个工具怎么量

不用 requests / urllib,用裸 socket + ssl 自己走一遍,因为:

  1. 高层库拿不到分段耗时;
  2. 拿不到这条 TCP 实际连到了哪个对端地址 —— 在 fake-ip 环境里, 这个地址和 DNS 查出来的完全是两回事;
  3. 要能在同一个进程里既走直连、又走系统代理,用来对比两条路径。

实现在 0,两个函数:

  • timed_get() —— 完整请求,四段全量。用在有 cdn-cgi/trace 的域名上。
  • tcp_probe() —— 只握手,不发请求。用在遥测 intake 上。

第二个的存在是有意的:往别人的遥测接入点发 HTTP 请求, 是在污染他们的数据,也会让这个监控工具自己变成一个上报源。 握手已经能回答"通不通、多快"。

代理路径下 DNS 那一段的含义变了

走代理时,域名解析是代理服务器做的,本机只解析了代理自己的地址 (通常是 127.0.0.1,接近 0 ms)。所以:

  • 代理路径的 DNS 段永远很快,但没有意义;
  • 那部分解析耗时被算进了 TCP / 首字节里。

结果里 dns_by_proxy 标记了这件事。对比两条路径的 DNS 段时要记得这一点, 否则会得出"走代理反而 DNS 更快"的错误结论。


三、分位数而不是最近一次

界面上的「分位数」表给的是当前窗口内的 p50 / p95 / min / max。

单次测量在代理链路上噪声很大 —— 一次 3 秒的抖动不代表什么。 看 p50 判日常体感,看 p95 判抖动是否让人难受: p50 300 ms、p95 3000 ms 的链路,用起来比 p50 600 ms、p95 700 ms 的 更让人烦躁,尽管前者"平均更快"。

分位数用最近邻插值算,实现在 0 的 percentile(),没有第三方依赖。

样本量太小时不要下结论。 表里的 n 列就是样本数。n=3 的 p95 基本等于 max,没有统计意义。要看抖动,至少让它跑几十轮。


四、对照组

清单里有一条 1.1.1.1,它不属于 Claude。它在这里是对照组:

  • 它和 claude.ai 都在 Cloudflare 上;
  • 所以两者的出口 / 延迟差异,只可能来自你本地的分流规则;
  • 如果 claude.ai 慢而 1.1.1.1 不慢 → 问题在这个域名那条链路上;
  • 如果两个都慢 → 问题在你的出口节点或者本地网络。

没有对照组的话,"慢"就没法归因。这是这一条存在的唯一理由。


五、几个不要做的推断

  • 不要拿延迟推地理位置。 300 ms 可能是"物理上远",也可能是 "近但绕了一圈"。地理位置看 cdn-cgi/trace 的 loc 和 colo, 那是目的地告诉你的,不是推的。
  • 不要拿 colo 当出口位置。 colo 是 Cloudflare 哪个边缘机房接的这次 请求,它通常离你的出口很近,但不是同一件事。出口位置看 ip + 它的归属。
  • 不要拿探测延迟当模型响应时间。 这里量的是一次轻量 HTTP 往返。 模型生成要几秒到几十秒,那个时间里绝大部分是在推理,不在网络。 网络延迟影响的是"开始出字之前的等待",不是总时长。