📏 字节分布:大对象的典型体重(内存盘 3 图)

每个大对象都有「典型体重」:P95 分布就是物证尺——它解释了 RSS 为什么冲高:处理 200MB 的视频,内存必然先涨 200MB+;b64_json 天然比 URL 形态大 33%+。覆盖内存盘 📏 字节分布组 3 张面板(m109 m110 m123)· 仪表盘 siliang-memory。

同一时刻 对出体重 字节分布 —— 大对象的典型体重:RSS 冲高的物证尺 ① RSS 冲高时刻(m101) 某时刻内存跳一截——谁干的? ② 分布水桶(_bucket,le=上限) le=小 le=中 le=大 le=更大 P95 = 排到 95% 位置那件的体重 histogram_quantile 从累积桶里插值算出 m109 · 文件/媒体体重 p95 三类典型体重:ZIP 产物 · 视频转存 · TTS 音频 处理 200MB 视频 → 内存先涨 200MB+ 用法:RSS 冲高时来这里对时间戳 …_file_batch_archive / _video_transfer / _tts_audio_bytes_bucket m110 · 图像/整包体重 p95 五类图像资产:快照源图 / 分割图层 / skill 整包 provider 生图返回 / Seedance 参考图 突然变大 → 先怀疑上游换了默认分辨率 …_snapshot_compose_source / _provider_image_response_bytes_bucket 等 m123 · XAI 生图响应字节(p50/p95) p50 / p95 对照看漂移:典型 vs 最惨 5% b64_json 格式天然比 URL 形态大 33%+ 膨胀(格式切换/分辨率变大)放大内存峰值 histogram_quantile(0.95/0.50, …siliang_xai_image_response_bytes_bucket…) 事故现场 · 时间戳对齐方法论 RSS 冲高时刻 × 字节分布对时间戳 = 找到搬运工 在途(上一课)回答「谁在搬」,分布(本课)回答「搬的典型多大」 分布突然变大 = 上游变更(分辨率/格式),不是流量问题——量级直接决定 RSS 峰值 三连击:m101 冲高时刻 → m106/m107 谁在搬 → m109/m110/m123 箱子多重 读法:体重秤看趋势与突变,不看单点——今天的 P95 和上周的 P95 比,才是它存在的意义

💡 一句话理解

快递站每收一件包裹都要过秤,秤不记每一件的重量,只往一叠"累积水桶"里记账:不超过 1MB 的几件、不超过 10MB 的几件……P95 就是把所有包裹从轻到重排队,站在 95% 位置那件的体重——它代表"典型的大件有多重"。m109 / m110 / m123 就是 backend 的三张体重秤:文件/媒体一类、图像/整包一类、XAI 生图响应一类。

它解决一个具体问题:RSS 冲高时,说明内存里突然多了几百 MB,但不知道是谁搬进来的。拿冲高时刻来对时间戳——哪类东西的体量足够解释这次冲高,搬运工就是谁。顺带记住一个天然事实:b64_json 格式把图片塞进 JSON 文本,天生比 URL 形态大 33%+。

🧩 费曼拆解 讲给完全没接触过的小白

先懂秤的原理。延迟、体积这类"每次都不一样"的数,系统不存单值,存的是一叠累积水桶(bucket,每个桶带个上限标签 le):不超过 1MB 的有几件、不超过 10MB 的有几件……查询时用 histogram_quantile 从桶里插值算出分位数。所以面板名字里都有 _bytes_bucket 和 "p95"——P95 回答的不是"最重的那件多重",而是"典型的大件有多重",比最大值稳得多(最大值会被一个极端件带跑)。

再看三张秤各称什么。m109 称文件/媒体三类活:ZIP 打包的产物、视频转存的文件、TTS 合成的音频。它就是"RSS 为什么冲高"的答案:要处理一个 200MB 的视频,程序必须先把它读进内存——内存必然先涨 200MB+,这不是泄漏,是搬运的物理代价。m110 称五类图像资产:快照源图、分割图层、skill 整包、provider 生图返回、Seedance 参考图。m123 单独称 xAI 的生图响应,并且 P50/P95 两条线对照——P50 是典型件,P95 是大件,两条线的距离就是"件与件的差距"。

最后是把秤变成破案工具。上一课的在途线回答"谁在搬",本课的分布回答"搬的典型多大"——两个时间戳一对:RSS 冲高时刻 × 在途跳起的工 × 该工的典型体重,三者量级对得上,案子就结了。还有一个高阶用法:分布本身"突然变大"(比如 provider 生图 p95 翻倍),那不是流量问题,是上游变更——先怀疑它换了默认分辨率或返回格式(b64_json 就天然比 URL 大 33%+)。

类比里的东西系统里对应的东西
快递过秤的累积账本histogram bucket:每个桶带上限标签 le,只记"不超过 X 的几件"
排队第 95% 位置的包裹P95 体重:典型大件的代表,比最大值稳(不被单件极端值带跑)
文件/媒体行李秤m109:ZIP 产物 / 视频转存 / TTS 音频的 P95;200MB 视频 → 内存先涨 200MB+
图像资产秤m110:快照源图 / 分割图层 / skill 整包 / provider 生图 / Seedance 参考图
生图响应秤(p50/p95 双读数)m123:xAI 返回体积分布;b64_json 天然比 URL 形态大 33%+
调监控录像对时间RSS 冲高时刻 × 在途跳起 × 典型体重,三连击锁定搬运工

🛠 动手验证(4 步,在 Grafana 里亲手做一遍)

  1. 看 m109 三条线的量级——ZIP 产物 / 视频转存 / TTS 音频各自的 P95 大概多重,心里记一张"价目表"。
  2. 看 m110 五类图像资产——哪类最重?provider 生图和 Seedance 参考图最近有没有阶跃?
  3. 对照 m123 的 p50 与 p95——两条线距离近 = 件件均匀;距离突然拉大 = 出现一批大件。
  4. 翻 m101 找一次 RSS 冲高——对时间戳:冲高量级 ≈ 哪类东西的 P95?那就是搬运工。

🧠 必知必会 看懂本课全部面板的地基

直方图 = 水桶
体积/延迟不存单值,存一叠累积桶(bucket),桶的上限标签叫 le;分位数从桶里插值算出。
# 指标名长这样:_bytes_bucket,带 le 标签
siliang_xai_image_response_bytes_bucket{le="1000000"}
P95 读法
把所有件从轻到重排队,P95 = 95% 的件比它轻——"典型大件"的代表,比最大值稳定。
# m123 口径:p95 / p50 双线对照
histogram_quantile(0.95, sum(rate(siliang_xai_image_response_bytes_bucket[5m])) by (le))
by (le) 第一大坑
histogram_quantile 忘了 by (le),所有桶混成一锅,算出来的分位数是废的——这是直方图查询第一大坑。
# 错:没有 by (le);对:必须 by (le)(+业务维度)
… sum(rate(…_bytes_bucket[5m])) by (le)
b64 天然大 33%+
b64_json 把图片塞进 JSON 文本(base64 编码),天然比 URL 形态大 33%+——这是格式代价,不是 bug;但膨胀会直接放大内存峰值。
# m123 用法:判断响应是否异常膨胀
# p95 阶跃 ≈ 格式切换(URL → b64_json)或分辨率变大
搬运的物理代价
处理 200MB 的视频,内存必然先涨 200MB+——RSS 冲高不一定是泄漏,先看体重秤再定罪。
# 冲高量级 × 典型体重 对得上 = 正常搬运
# 对不上且在途滞留 → 才升级成泄漏排查
分布突变 = 上游变更
provider 生图 p95 突然变大,先怀疑上游换了默认分辨率/返回格式——体重是上游给的,不是我们养出来的。
# 与昨天对比(offset 是通用 PromQL)
… bytes_bucket[5m] offset 1d  # 昨天的同期体重
p50 与 p95 的距离
p50 是典型件、p95 是大件;两线距离突然拉大 = 出现了一批异常大的件。
# m123 双读数(照抄主图谱口径)
histogram_quantile(0.95/0.50, sum(rate(
  siliang_xai_image_response_bytes_bucket[5m])))

📋 逐面板精讲 3 张图一张不落

📏 m109 · 文件/媒体处理字节分布 p95

它是什么:三类文件/媒体活的"典型体重"(P95 直方图分布):ZIP 打包产物、视频转存文件、TTS 合成音频。它是趋势参考——直接解释了 RSS 为什么冲高:处理一个 200MB 的视频,内存必然先涨 200MB+。

回答什么问题:这次内存冲高,是不是有活正在搬大文件?大文件典型多大?

✅ 三条线量级平稳、与业务节奏同步;冲高量级 ≈ 对应产物 P95 时属正常搬运

🚨 RSS 冲高量级对不上任何产物的体重、且在途滞留——才升级成泄漏排查。动作:拿冲高时刻对时间戳,谁在什么时候搬了大文件一对照便知

# 面板 PromQL(主图谱口径:三类 bucket 各算一条 p95)
histogram_quantile(0.95, sum(rate(
  siliang_file_batch_archive_bytes_bucket[5m])) by (le))
# 同款:…_video_transfer_bytes_bucket / …_tts_audio_bytes_bucket

联动:主图谱 m109 ↗ · 相关课程:大对象在途(谁在搬) · P50/P95:排队

🖼 m110 · 图像/整包字节分布 p95

它是什么:五类图像资产的典型体重:快照源图、分割图层、skill 整包、provider 生图返回、Seedance 参考图。图像是解码放大最狠的一类(磁盘几 MB、内存几十 MB),所以这张秤对解释内存冲高特别有用。

回答什么问题:内存冲高时刻,是谁的大图在处理?上游给的图是不是变大了?

✅ 五类体量各自平稳;与图像类业务量同步起伏

🚨 provider 生图的 p95 突然变大——先怀疑上游换了默认分辨率。动作:对时间戳定位"内存冲高时刻是谁的大图在处理",并向上游确认变更

# 面板 PromQL(主图谱口径:五类 bucket 并列,此处举两类)
histogram_quantile(0.95, sum(rate(
  siliang_snapshot_compose_source_bytes_bucket[5m])) by (le))
# 同款:_image_segmentation_layer / _openapi_skill_package /
#       _provider_image_response / _seedance_reference_bytes_bucket

联动:主图谱 m110 ↗ · 相关课程:防护与缓存(安检门拦超大) · 直方图水桶与 topk

📐 m123 · XAI 生图响应字节分布

它是什么:xAI 返回生图的体积分布,P50/P95 双线对照——P50 是典型响应的体重,P95 是最重那批的代表。关键常识:b64_json 格式会把图片塞进 JSON 文本,天然比 URL 形态大 33%+。

回答什么问题:生图响应是不是异常膨胀了(格式切换 / 分辨率变大)?膨胀了多少内存峰值?

✅ p50/p95 平稳、两条线距离稳定;b64 格式的大 33% 属格式代价,计入基线

🚨 p95 阶跃式上涨 = 响应异常膨胀——要么上游换了分辨率,要么返回格式切到了 b64_json。动作:对时间戳与 RSS 峰值互证,膨胀会直接放大内存峰值

# 面板 PromQL(照抄主图谱口径:p95 / p50 双读数)
histogram_quantile(0.95/0.50,
  sum(rate(siliang_xai_image_response_bytes_bucket[5m])))

联动:主图谱 m123 ↗ · 相关课程:LLM 健康(provider 门诊) · P50/P95:排队

🏭 生产实战 real world

场景 1 · RSS 冲高破案三连击(时间戳对齐)

内存冲高报警,按这个顺序三步定案:谁在搬 → 搬多大 → 量级对不对得上。

# 第 1 步:锁定冲高时刻与量级(m101 口径)
max_over_time(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1m])
# 第 2 步:同一时间轴看在途——谁在那一刻跳起(m106/m107 口径)
max_over_time(siliang_file_batch_archive_active[1m])
max_over_time(siliang_image_segmentation_active[1m])
# 第 3 步:查搬运工的典型体重(m109 口径)
histogram_quantile(0.95, sum(rate(siliang_video_transfer_bytes_bucket[5m])) by (le))
# 定案:冲高量级 ≈ 在途工的 P95 体重 = 正常搬运;对不上 = 泄漏排查

场景 2 · provider 生图膨胀检测(和昨天比)

体重是上游给的——用 offset 与昨天同期对比,阶跃即变更。

# 今天的 provider 生图 p95(m110 口径)
histogram_quantile(0.95, sum(rate(
  siliang_provider_image_response_bytes_bucket[5m])) by (le))
# 昨天同期(offset 是通用 PromQL)
histogram_quantile(0.95, sum(rate(
  siliang_provider_image_response_bytes_bucket[5m])) by (le) offset 1d)
# 判读:阶跃式上涨(如翻倍)= 上游换默认分辨率/格式 → 找供应商确认
# 连带检查:安检门拦截速率是否同步抬头(m114 口径)

场景 3 · b64 膨胀判断:p50/p95 一起看

格式切换是"所有件一起变大",所以 p50 会先动——这是它与"偶发大件"的区别。

# p95 与 p50 双读数(m123 口径)
histogram_quantile(0.95, sum(rate(siliang_xai_image_response_bytes_bucket[5m])) by (le))
histogram_quantile(0.50, sum(rate(siliang_xai_image_response_bytes_bucket[5m])) by (le))
# 读法:
#   p50 与 p95 同步阶跃 ≈ +33% → 格式切到 b64_json(格式代价,计入基线)
#   p50 平稳、只有 p95 阶跃 → 出现一批大件(分辨率变化),单独查

场景 4 · 直方图查询的正确姿势(复习防身)

体重秤的所有查询都长一个样——把模板背下来,le 永远不丢。

# 通用模板:histogram_quantile + rate + sum by (le)
histogram_quantile(<分位数>,
  sum(rate(<…_bytes_bucket>[5m])) by (le, <业务维度>))
# 实例:Seedance 参考图 p95(m110 五类之一)
histogram_quantile(0.95,
  sum(rate(siliang_seedance_reference_bytes_bucket[5m])) by (le))
# 自查:结果数量 = le 桶数才对;只有一条线 = 忘了 by (le)

判读收尾:查询返回的序列数应等于桶数(或桶数 × 维度组合数),只有一条"诡异的总分位数"就是漏了 le。

场景 5 · 每周体重趋势巡检:价目表会漂移

上游悄悄变大是常态,每周看一眼趋势,别等内存峰值爆了才发现。

# 三张秤的周巡检(拉 7d 窗口看趋势)
histogram_quantile(0.95, sum(rate(siliang_file_batch_archive_bytes_bucket[5m])) by (le))
histogram_quantile(0.95, sum(rate(siliang_tts_audio_bytes_bucket[5m])) by (le))
histogram_quantile(0.95, sum(rate(siliang_xai_image_response_bytes_bucket[5m])) by (le))
# 关注:台阶式抬升(上游变更)/ 缓慢爬升(数据量自然增长)
# 台阶 = 记变更单;爬升 = 评估内存峰值预算

⚠️ 常见坑 pitfalls

坑 1 · histogram_quantile 忘 by (le) — 症状:分位数算出来是个恒定怪值。原因:所有累积桶没按 le 分组,混成一锅插值。正解:by (le) 是直方图查询的命根子,业务维度加在 le 旁边。
# 错: histogram_quantile(0.95, sum(rate(…_bucket[5m])))
# 对: histogram_quantile(0.95, sum(rate(…_bucket[5m])) by (le))
坑 2 · 把 P95 当最大值 — 症状:"P95 是 50MB,所以最大也就 50MB 出头"。原因:P95 是排队位置,剩下 5% 可以比它大得多。正解:P95 代表典型大件;要看极端件得看 P99 或 max_over_time。
# 错: 用 p95 估算内存峰值上限
# 对: 峰值预算用 p99/最大值;p95 看典型与趋势
坑 3 · b64 大 33% 当 bug 报障 — 症状:发现 xAI 响应比图片本体大一号,报"传输浪费"。原因:b64_json 把图片塞进 JSON 文本,天然膨胀 33%+,是格式代价。正解:计入基线;只有阶跃式变化(格式切换)才需要追查。
# 错: b64 比 URL 大 → 报 bug
# 对: 大 33%+ = b64 格式天然代价;阶跃才立案
坑 4 · 只看一次读数不看趋势 — 症状:"P95 30MB,正常吧"就翻篇。原因:体重的意义在和昨天/上周比——台阶式抬升是上游变更的第一信号。正解:周巡检看趋势,配 offset 1d/1w 对比。
# 错: 只看当前 p95 绝对值
# 对: …bucket[5m] offset 1d  # → 和昨天同期比
坑 5 · 对 bucket 直接 sum 不配 rate — 症状:sum(…_bytes_bucket) 得到一条只涨不落的线。原因:bucket 是累积 Counter,直方图必须 rate 之后再 sum。正解:永远是 rate(…_bucket[5m]) → sum by (le) → histogram_quantile。
# 错: histogram_quantile(0.95, sum(…_bucket) by (le))
# 对: histogram_quantile(0.95, sum(rate(…_bucket[5m])) by (le))
坑 6 · 只看分布不看在途 — 症状:看到"视频转存 p95 200MB"就断定冲高正常。原因:体重只说明"箱子多重",还要在途线证明"当时真在搬"且搬完放下了。正解:在途(m106/m107)× 体重(本课)× RSS 三角定案。
# 错: p95 够大 → 冲高合理,结案
# 对: 在途同时跳起且事后归零 → 才算正常搬运
坑 7 · 忘写 _bucket 后缀 — 系统症状:查询报错或查空。原因:直方图暴露的是 …_bytes_bucket(带 le),不带后缀的指标不存在。正解:照抄主图谱指标名,_bucket 一个字都不能少。
# 错: siliang_tts_audio_bytes[5m]
# 对: siliang_tts_audio_bytes_bucket[5m]

🎓 费曼自测 合上书,能讲出来才算懂

Q1 · 直方图为什么不直接存"每件的重量"?P95 是怎么算出来的?

参考答案:每件都存单值成本高且没法聚合,所以存一叠累积水桶(bucket),每桶带上限标签 le:不超过 X 的有几件。查询时用 histogram_quantile 在桶之间插值:把所有件按轻重排队,站在 95% 位置那件的体重就是 P95——它代表"典型大件",比最大值稳定。写查询的命根子是 rate → sum by (le) → histogram_quantile,漏了 by (le) 全盘作废。

Q2 · 处理 200MB 视频时内存涨 200MB+,为什么这不是泄漏?

参考答案:这是搬运的物理代价——程序必须把视频读进内存才能转存,内存先涨 200MB+ 是必然。判断是不是泄漏看三点:在途线(m106)当时是否跳起、干完后是否归零;RSS 冲高后是否回落或进入平台期(deriv≈0);冲高量级是否 ≈ 该类活典型体重(m109 的 P95)。三者在途、归零、量级都对得上,就是正常搬运。

Q3 · m123 的 p50 和 p95 同时阶跃上涨约 33%,最可能发生了什么?

参考答案:所有响应一起变大、且涨幅恰好是 b64 的编码膨胀率——最可能是返回格式从 URL 切到了 b64_json(b64 把图片塞进 JSON 文本,天然比 URL 形态大 33%+)。这是格式代价不是 bug,但要计入内存峰值基线。若只有 p95 阶跃而 p50 不动,则是出现了一批大件,更可能是分辨率变化,要单独追查。

← 上一课:🚨 泄漏哨兵:防回退警报器 📚 课程目录 下一课:🏁 结束原因:活干完了没 →