📦 大对象在途:谁攥着大文件(内存盘 2 图)

ZIP 打包、快照合成、skill 整包、图片分割、画布裁剪——这些活每个都攥着大块内存,忙完必须归零。「稳态应归零」四个字就是它们的健康定义。覆盖内存盘 📦 大对象在途组 2 张面板(m106 m107)· 仪表盘 siliang-memory。

忙完就还 只进不清 大对象在途 —— 稳态应归零:谁的手里正攥着大块内存 m106 · 文件/打包模块在途(稳态应归零) ZIP 打包 file_batch_archive_active 在途 = 正在手里 快照合成 snapshot_compose_active 在途 = 正在手里 skill 整包 openapi_skill_package_active 在途 = 正在手里 m107 · 图像处理在途(稳态应归零) 图片分割 image_segmentation_active 解码后体积暴涨 画布裁剪 workspace_crop_active 解码后体积暴涨 大对象在途台账(active,Gauge) 每个活都攥着大块内存 几 MB 的 PNG 解码后占几十 MB 健康路:干完放下(归零) 在途 → 0,RSS 随之回落(锯齿) 事故路:干完不撒手(滞留) 在途挂着 > 0,RSS 抬升不回落 事故现场 · 排查方法论 有活一直挂着不归零 = 大对象滞留 = RSS 抬升的直接嫌疑人 —— RSS 冲高时来这里对时间戳找搬运工 在途(本课)回答「谁在搬」,字节分布(下一课)回答「搬多大」——两张图一对照,凶手现形

💡 一句话理解

把 backend 想成一家搬运公司:ZIP 打包、快照合成、skill 整包、图片分割、画布裁剪是五个搬运工,每人干活时都要抱着一口大箱子(大块内存)。健康的标准简单粗暴——「稳态应归零」:活干完,箱子放回仓库,台账上"正在搬运"的计数回落到 0。

这两个面板就是搬运台账:m106 盯文件/打包三个工,m107 盯图像处理两个工。哪条线忙完不归零,哪个工就攥着箱子不撒手——这就是 RSS 抬升的直接嫌疑人。而且箱子比看上去重得多:一张几 MB 的 PNG,解码后在内存里要占几十 MB。

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

先认识五个搬运工。有人下单"帮我把整个画布打包成 ZIP"——打包工接过活,把几百张图装进一个巨型袋子(内存里的字节数组),压好、传走、放袋下班。快照工、整包工、分割工、裁剪工同理:接活 → 抱箱子 → 干完 → 放下。所以它们的"在途计数"(active)天然就该是锯齿:忙时往上跳几格,闲时稳稳归零。「稳态应归零」不是要求,是这类活的物理本性——箱子没有理由一直抱着。

再看箱子有多重。磁盘上的 PNG 是压缩过的,几 MB;程序要处理它,必须先"解码"摊开成原始像素——一张几 MB 的 PNG 摊开后能占几十 MB 内存,放大 10 倍不止。所以图像工(m107)抱的箱子格外沉:只要有一个裁剪活挂着不结束,几十 MB 就一直扣在 worker 头上;几个同时挂着,RSS 立刻冲高一截。

最后是排查姿势。内存冲高了怎么办?别翻代码猜——拿 m101 的 RSS 冲高时刻,来 m106/m107 对时间戳:某个工的在途线在同一时刻跳起来、然后一直没归零?嫌疑人就是他。在途回答"谁在搬",下一课的字节分布回答"搬的多大",两图一夹,凶手现形。

类比里的东西系统里对应的东西
搬运工手里的箱子在途计数 active(Gauge):此刻有几个活正在处理,每个活攥着大块内存
干完放回仓库「稳态应归零」:闲时读数回 0 是这类活的物理本性,不是高标准严要求
超重的箱子图像解码放大:磁盘上几 MB 的 PNG,解码摊开后在内存里占几十 MB
攥着箱子不撒手在途挂着不归零 = 对象没释放 = RSS 抬升的直接嫌疑人
快递公司分班组台账m106 三条线(ZIP/快照/skill)+ m107 两条线(分割/裁剪),各归各的零,分开看

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

  1. 打开 m106,拉 6h——三条线(ZIP/快照/skill)应该都是锯齿:忙时跳起、闲时归零。
  2. 看 m107 两条线——分割/裁剪同理;记住它们的箱子更沉(解码放大十倍)。
  3. 翻回 m101 找最近一次 RSS 冲高——记下冲高时刻,回来对 m106/m107 的时间戳:谁在那一刻跳起来?
  4. 找"不归零"的线——任何一条在业务空闲时段仍 > 0 的线,记下 instance 和时段,这就是滞留证据。

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

active 是温度计
在途计数可上可下,是 Gauge:读数=手里有几个活。直接读,别配 rate。
# m106 口径:三条线各画一条(主图谱以 / 简写并列)
max_over_time(siliang_file_batch_archive_active[1m])
max_over_time(siliang_snapshot_compose_active[1m])
max_over_time(siliang_openapi_skill_package_active[1m])
稳态应归零
这组面板的健康定义就四个字:闲时读数应回 0。不归零不是"忙",是"没释放"。
# 滞留判据(通用 PromQL):30 分钟内始终 > 0
min_over_time(siliang_file_batch_archive_active[30m]) > 0
解码放大
磁盘体积 ≠ 内存体积:PNG/JPEG 是压缩格式,解码摊开成原始像素后暴涨,几 MB 文件占几十 MB 内存。
# 估算口诀:内存 ≈ 宽 × 高 × 4 字节(RGBA)
# 4000×3000 的图 ≈ 48MB —— 与磁盘上几 MB 完全两码事
时间戳方法论
RSS 冲高时刻 × 在途跳起时刻,两个时间戳一对,嫌疑人现形——监控版"调监控录像"。
# 同一 Grafana 时间轴上叠三张图:
# m101(RSS 冲高)× m106/m107(谁跳起)× m109/m110(箱子多重)
分线看,别求和
五个工各归各的零:求和会把"一个工滞留"稀释成"平均略微偏高",线索全无。
# 错:sum 五条线;对:分线各自归零各自看
max_over_time(siliang_image_segmentation_active[1m])
在途 vs 字节分布
在途(本课)回答"谁在搬";字节分布 p95(下一课 m109/m110/m123)回答"搬的典型多大"。
# 一问在途,二问体重,三对 RSS——三连击
histogram_quantile(0.95, …)  # 下一课的主角
在途归零 ≠ RSS 立降
活干完、箱子放下后 RSS 可能停在高位走平台——那是 allocator 不归还(常态),别误判成滞留。
# 分流判据:
# 在途=0 且 deriv(RSS[1h])≈0  → 平台期,常态
# 在途>0 且 RSS 抬升          → 滞留实锤

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

📦 m106 · 文件/打包模块在途(稳态应归零)

它是什么:三条线 = ZIP 打包 / 快照合成 / skill 整包,各有多少个"正在手里的活"(Gauge)。这些活每个都攥着大块内存——打包要把大量资产装进一个巨型字节数组,快照合成要同时摊开多张图层。

回答什么问题:现在谁的手里正攥着大内存?忙完之后放没放?

✅ 忙完就归零(所以叫"稳态应归零"):三条线都是锯齿,闲时段稳稳贴 0

🚨 有活一直挂着不归零 = 大对象滞留内存 = RSS 抬升的直接嫌疑人。动作:记下 instance 与时刻,下钻代码找没释放的打包/合成任务

# 面板 PromQL(照抄主图谱口径:三线并列)
max_over_time(siliang_file_batch_archive_active[1m])
max_over_time(siliang_snapshot_compose_active[1m])
max_over_time(siliang_openapi_skill_package_active[1m])

联动:主图谱 m106 ↗ · 相关课程:内存全局水位(RSS 冲高源头) · 结束原因(这些活干成了没)

🖼 m107 · 图像处理在途(稳态应归零)

它是什么:图片分割 / 画布裁剪正在处理几个(Gauge,两条线)。图像是典型的大对象来源:磁盘上几 MB 的 PNG 是压缩格式,解码摊开成原始像素后要占几十 MB 内存——箱子比标签重十倍。

回答什么问题:RSS 冲高的那一刻,是不是有图像活在处理?这些图像对象用完放没放?

✅ 忙时跳起、闲时归零的锯齿形态;与图像类请求量同步起伏

🚨 挂着不归零 = 图片对象没释放——分割/裁剪的结果图被引用拽着收不走。动作:对照 m101 冲高时刻定位,并检查结果图是否被闭包/注册表拽住

# 面板 PromQL(照抄主图谱口径:两线并列)
max_over_time(siliang_image_segmentation_active[1m])
max_over_time(siliang_workspace_crop_active[1m])

联动:主图谱 m107 ↗ · 相关课程:字节分布(箱子到底多重) · 泄漏判定心法

🏭 生产实战 real world

场景 1 · RSS 冲高找嫌疑人 SOP(对时间戳三连击)

内存冲高不要猜,三张图同一时间轴一摆,搬运工自己现形。

# 第 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 步:翻字节分布确认箱子多重(m110 口径,下一课详解)
histogram_quantile(0.95, sum(rate(siliang_snapshot_compose_source_bytes_bucket[5m])) by (le))
# 判读:冲高时刻 × 在途跳起 × 箱子体量 = 闭环证据链

场景 2 · 滞留告警:闲时不归零就报(YAML 可直接抄)

「稳态应归零」可以直接翻译成告警:业务空闲时段读数仍大于 0,就是滞留。

# 告警规则:大对象滞留(稳态应归零面板通用写法)
groups:
- name: siliang-bigobject-retention
  rules:
  - alert: BigObjectStuckInFlight
    expr: min_over_time(siliang_file_batch_archive_active{job="siliang_backend_worker"}[30m]) > 0
    for: 0m          # min_over_time 已含"持续 30 分钟"语义,不用再 for
    labels:
      severity: warning
  # 同款复制给 snapshot_compose / skill_package / segmentation / crop
  # 注意:深夜低峰触发可信度最高;白天要结合业务量排除"真在忙"

判读收尾:告警触发后先看该线此前是否锯齿归零——从"会归零"变成"不归零",就是行为变更实锤。

场景 3 · 忙闲形态验证:先确认基线再谈异常

新面板上手第一步,是知道它健康时长什么样。

# 拉 24h 看完整业务周期(m107 口径)
max_over_time(siliang_workspace_crop_active[1m])
# 对照请求量确认"忙时"(有活干 >0 是正常的)
sum(rate(siliang_http_requests_total[5m]))
# 基线结论应长这样:忙时 1~几个在途,闲时归 0,全天锯齿
# 偏离基线的两种坏形态:闲时不归零(滞留)/ 忙时冲到异常高的并发

场景 4 · 解码放大互证:图像工的箱子到底多重

怀疑图像滞留时,用在途线与字节分布互证,估算内存扣了多少。

# 在途几个活(m107 口径)
max_over_time(siliang_image_segmentation_active[1m])
# 源图典型体量(m110 口径:快照源图 p95)
histogram_quantile(0.95, sum(rate(siliang_snapshot_compose_source_bytes_bucket[5m])) by (le))
# 心算:p95 若 5MB(压缩态),解码后 ≈ 数十 MB/张;在途 3 个 = 上百 MB 被攥着
# 与 RSS 冲高量级一对照:对得上 = 定案

⚠️ 常见坑 pitfalls

坑 1 · 在途大于 0 就告警 — 症状:白天在途 2、3 个就报警"泄漏"。原因:忙时有活在手是正常经营,"稳态应归零"说的是闲时。正解:用 min_over_time(…[30m]) > 0 判"持续不归零",并结合业务量。
# 错: alert: archive_active > 0
# 对: alert: min_over_time(archive_active[30m]) > 0
坑 2 · 五条线求和看 — 症状:把 m106+m107 五个 active 加成一条线。原因:一个工滞留 1 个活,被总和的日常波动淹没,线索全无。正解:分线各自看各自的零。
# 错: sum(archive_active, compose_active, …)
# 对: 五条线并列,谁不归零谁是嫌疑人
坑 3 · 按磁盘大小估内存 — 症状:"PNG 才 3MB,能占多少?"原因:磁盘上是压缩格式,解码摊开后按 宽×高×4 字节 计,暴涨十倍不止。正解:图像内存按解码后估,别按文件大小。
# 错: 3MB 文件 → 内存占用 ≈ 3MB
# 对: 4000×3000 × 4B ≈ 48MB,几 MB PNG 解码后占几十 MB
坑 4 · 在途归零但 RSS 没降,误判滞留 — 症状:活干完了、内存还挂着,直接定罪"没释放"。原因:那是 allocator 留着复用的平台期,不是泄漏。正解:滞留看在途线,平台期看 deriv(RSS)≈0。
# 错: active=0 且 RSS 高 → 泄漏
# 对: active=0 + deriv(RSS[1h])≈0 → 平台期,常态
坑 5 · 对 active 求 rate — 症状:想看"搬运速度"就 rate(active)。原因:active 是 Gauge,rate 只配 Counter。正解:速率看结束原因面板(m111/m112/m122,Counter 配 rate)。
# 错: rate(siliang_workspace_crop_active[5m])
# 对: sum by (outcome) (rate(siliang_workspace_crop_outcome_total[5m]))
坑 6 · 只盯 m106 不看 m107 — 症状:文件类三条线都归零就结案。原因:图像工的箱子最沉(解码放大),却最常被漏看。正解:两个面板五条线一起过。
# 错: 只查 file_batch / snapshot / skill_package
# 对: 加上 image_segmentation / workspace_crop 再结案

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

Q1 · "稳态应归零"是什么意思?为什么这组面板可以要求归零而 RSS 不行?

参考答案:在途 active 数的是"此刻手里攥着几个大活"。活有明确的开始和结束,结束就该放箱子,所以闲时读数必须回 0——不归零说明有活没释放。而 RSS 是全进程的总账,干完活后 allocator 会把内存留着复用(平台期),天然可以停在高位;一个要求"闲时归零",一个允许"平台期",两者不矛盾。

Q2 · 为什么处理一张 3MB 的 PNG 可能让内存涨几十 MB?

参考答案:磁盘上的 PNG/JPEG 是压缩格式,程序要处理必须先解码摊开成原始像素——内存占用约等于 宽 × 高 × 4 字节(RGBA),和文件大小无关。一张 4000×3000 的图解码后约 48MB。所以图像工(m107)抱的箱子比文件工更沉,在途滞留时 RSS 反应也最猛。

Q3 · RSS 冲高了,本课给你的排查三连击是什么?

参考答案:① m101 锁定冲高时刻;② 同一时间轴叠 m106/m107 的在途线,看谁在那一刻跳起、之后是否归零——在途回答"谁在搬";③ 叠下一课 m109/m110/m123 的字节分布 p95,确认箱子典型体量——回答"搬多大"。冲高时刻 × 在途跳起 × 箱子体量,三点连成证据链,凶手现形。

← 上一课:🧵 进程资源四件套 📚 课程目录 下一课:🚨 泄漏哨兵:防回退警报器 →