Akke 评论抓取时效 · 深度调查

从一条视频案例挖到全链路根因 · 2026-06-15 · 数据均为当日实查生产库
📊 实时数据快照(每日重算) · 本页是一次性深挖;时延分布 / 空跑率 / pri 队列积压等会随时间变的数字,看自动生成的数据快照页 → scrape-latency 数据快照 (口径同此页,judgment 段仍以本页为准)
一句话结论。 高意向评论从「用户发出」到「进我们库」,慢不在抖音、不在抓取动作本身,而在两段等待:Leg B 等到有人来抓这条视频(冷视频几十分钟到几小时)+ Leg C 活派出去后在队列里排队(中位 ~4.6 分钟)。再往上追,近 4 成高意向评论走 ≥30 分钟慢路,其中 97% 是「冷视频」;而冷视频之所以多,是因为 tier-1 优质号「只升不降」攒到 176 个、其中 74% 是不产高意向的僵尸,挤爆抓取产能、把 1019 个 tier-0 号饿死。最值钱的一刀:给 tier-1 加降级。

导读: 起点案例 · 全链路五段拆解(含机制+成因,最详细)· 拐点实证 · 慢路损失多大 · 总根因(僵尸 tier-1)· 提升点 · 数据快照(含 cron 3→2 复核)· 队列三问(Leg B 入队 / BC 极限 / 快车道 100 条)· ⑨ 2026-06-16 B/C 落地成效 + 真尾巴 + pri 体系(最新)

🍚🍎 看不懂术语?文档里有两张「大白话比方」,配着看:② 段中部「食堂打饭」讲五段各自在哪堵(菜做好→等人端→排队等阿姨打饭);⑨ 段开头「果园摘果」讲机制怎么转(视频=果树/高意向=熟果/快车道=重点照看名单/tick=巡一圈/退避=空手就少去/去重=别每轮重派同一棵)。所有黑话(冷视频 / 单视频快活 / 整号活 / 60min 门槛 / tick / 注入去重 / 退避)都在第一次出现处就地有具体定义

① 起点:从你发来的一条视频挖起

输入是一条抖音视频链接 douyin.com/video/7648553920499141739。反查得到:

账号歆笛的装修日记(知识号)
分层Tier-1 优质号
累计评论 / 高意向184 / 15 (整号 29 条视频累计;非单条视频)
近 7 天高意向11 条(全压在近 7 天,是个正在爆量的号)
高意向触达状态15 条里 13 条还没触达(analyzed)
口径说明(2026-06-16 复核):184 / 15 / 13 是账号「歆笛的装修日记」整号口径(现查 206 / 15 / 13,184→206 是评论自然增长)。链接那条单视频本身只有 108 评论 / 11 高意向。下表 15 条高意向 = 整号 29 条视频的累计,不是单条视频的。

逐条看这 15 条的「发言→抓到」延迟,发现一个反常:同样是 Tier-1,有的 1 分钟抓到,有的 29–48 分钟,还有的拖到 几天。这就是整条调查的起点。

#用户发言抓到延迟评论摘要(意向分)
1大海05-19 07:3205-21 13:382.3 天帮忙发一份吧 (85)
2@~#~?05-19 09:3905-21 13:382.2 天要一份 (85)
3小肥猫05-19 09:4705-21 13:382.2 天我要一份 (85)
4风少05-20 20:5005-21 13:3816.8 时100平15万预算求方案 (93)
5,,,06-09 22:3206-14 07:494.4 天下个月准备装修,真的需要 (85)
6🍒奶黄包06-12 00:2906-14 01:242.0 天我也需要,准备装修 (85)
7英儿06-12 08:3306-14 01:2440.9 时来一套资料 (85)
8安安06-12 11:3506-14 01:2437.8 时258平大概多少钱 (93)
9燕飞狐06-13 11:0506-14 04:3717.5 时最近准备装修,求份资料 (85)
10双子娃娃06-13 23:5206-14 01:241.5 时71平学区房,求资料 (93)
11龍凤呈祥06-14 08:0806-14 08:3729 分一百平小三室,求资料 (100)
12用户508006-14 09:4406-14 10:1430 分下月要装修了 (85)
13古悠然06-14 15:4906-14 15:511 分准备装修,求全套资料 (85)
14信仰 已触达06-14 16:4906-14 16:577 分多少平米的定制 (85)
15二宝妈 已触达06-15 00:3506-15 00:372 分求资料,毛坯房 (85)

汇总:中位 17.5 时 · 最快 1 分 · 最慢 4.4 天。趋势——越往后(6-14 下午起)越快;5 月那几条 2 天级、6-14 凌晨 01:24 那批是「断档恢复一次性补抓」的积压。这个「越来越快」的拐点,③ 坐实。

② 全链路五段拆解(含机制 + 成因,最详细)

一条评论 T0 用户发出 → 进我们库,走完五道关。先看每段实测耗时,再把最关键的 B、C 两段彻底讲透(抓取机制、冷视频成因都在这一节,不用往后翻):

Leg A
抖音侧上架(评论发出 → 抖音 API 能查到) · 不可控
~几秒
Leg B
入队等待(评论可见 → 有人来抓这条视频) · 大头之一
数分钟~数小时
Leg C
队列排队(活派出去 → worker 领走) · 大头之一
≈ 4.6 分
Leg D
执行抓取(领走 → 抓完写库) · 抖音新→旧返回,抓首页即命中、早停,1 个请求
4.9 秒
Leg E
写库
亚秒
B 和 C 都是大头,A/D/E 全是秒级。 哪个主导取决于这视频「热不热」:热视频(已在快车道)→ B 较小、C 的 4.6 分排队主导;冷视频(97% 的慢路属于此)→ B 直接是几十分钟到几小时,C 反而次要。
🍚 嫌五段绕?用「食堂打饭」打个比方(一条评论 = 一份菜,从做好到端你嘴边): (这张比方管"五段各自在哪堵";后面 ⑨ 开头那张「🍎 果园摘果」管"怎么选视频、怎么排队、怎么退避去重"——两张互补,配着看。)

Leg A · 抖音侧上架(~几秒,不可控)

用户点「发送」后,这条评论要先在抖音自己的系统里能被 API 查到。我们管不着,但很快——用最快的端到端样本(1 分钟)倒推,这段只占几秒。

Leg B · 入队等待 =「等到有人来抓这条视频」(大头)

"抓这条视频" = worker(干抓取活的程序)去这条视频底下把评论捞一遍。它什么时候被抓到,完全由我们的抓取机制决定 —— 有两条路,下面讲全。

B-1 · 两条路概览

怎么触发覆盖谁节奏
🐢 整号慢路这条视频所在的「号」被整体抓一遍时,顺带把它主页前 20 条视频都抓了所有号(按 tier 分快慢)tier-1 号 ≥30 分 / tier-0 号 ≥7 时
视频快车道不看号,直接挑「正在冒新评论的 top 100 视频」单独抓够格的热视频每 2 分钟挑一批

B-2 · 号源轮抓机制(整号慢路)= 三个频率别混

号分两层,走两条 cron(cron = 定时任务,到点自动触发的"闹钟")。最容易被绕晕的是「闹钟频率 ≠ 每个号实际多久被抓一遍」:

tier-0 普通号tier-1 优质号
闹钟多久响(cron)每 1 小时(原 4h)每 2 分钟(原 5min→3min→今天 2min)
每次派多少挑 150 个最久没抓的号所有 tier-1(受下面节流)
同一个号多久能再抓无硬闸,靠 150 名额轮≥30 分钟(节流闸,今天从 45 改 30)
队列优先级pri=5 慢道pri=1 快道
每个号实际间隔≥7 小时(1019 个号挤)≥30 分钟(且超产能更长)
"5 分钟 / 1 小时"是老黄历:那是闹钟频率,不是每个号实际节奏。tier-1 早期没节流时确实 5 分钟一轮,但 6-11 那次「tier-1 太勤把 tier-0 全饿死」的 P1 事故后加了「同号 30 分钟内不重抓」的闸——所以现在 tier-1 名义 30 分钟、tier-0 ≥7 小时。一个「整号活」= 去主页捞 ≤20 条视频、逐条抓(同号 2 条并发、30 分钟内抓过的跳过)。

B-3 · 视频快车道机制(快路)

由同事 tuan19960216 在 2026-06-14 11:03 上线(PR #340)。藏在 tier-1 的闹钟里,每 2 分钟独立跑一轮下文把「闹钟响一次 / 跑一轮」叫「1 个 tick」,现 cron */3 = 每 3 分钟一个 tick):

B-4 · 成因:慢路 97% 是「冷视频鸡生蛋」

先把「冷视频」讲具体(本报告操作口径):判定法=一条评论走慢路时,看它所在的那条视频「前 24h 有没有被快车道抓过」——没上过 = 冷视频。通俗说=这条视频不在「快车道高频复扫名单」里,它底下的新评论只能等「整号轮抓」几十分钟~几小时扫一遍才被发现,永远够不上「评论冒出 ≤60min 内被新鲜抓到」的上车门槛(= 鸡生蛋)。
会冷的视频有三类:老视频(发布几天/几周,掉出号主页最近 ≤20 条,整号活也不再抓它);② 不活跃号底下的视频(号本身轮抓间隔长);③ tier-0 号的视频(每小时才轮一次、还常被饿死到 ≥7h)。
反面「热视频」=正在被快车道高频复扫的视频(近 48h 有过「≤60min 新鲜抓到」的评论、在名单上)。

近 2.5 天 274 条走慢路(>30 分)的高/中意向评论,按「评论来时它那条视频前 24h 有没有上过快车道」分类:

成因条数占比
🥶 冷视频鸡生蛋(从没上过快车道)26597%
📉 掉车(上过又没赶上)93%
其中深夜掉车1
死循环(鸡生蛋):一条视频要「被 ≤60 分钟新鲜抓到过一次」才够格进快车道(B-3 的门槛);可冷视频只走慢路、永远赶不上 60 分钟 → 永远进不了快车道 → 它上面的评论第一条几乎必然慢。这些冷视频很分散(262 条散在 133 条不同视频上,top10 仅占 38%),治它得撒广网、不能盯几条。这就是 Leg B 对冷视频长达几十分钟~几小时的根因。

Leg C · 队列排队 = 活派出去后,为什么还要排 4.6 分钟(大头)

就算 Leg B 把活派进了队列,worker 也不是立刻抓。实测中位 276 秒、p99 327 秒。机制如下:

已坐实(2026-06-15 23:00 实查 worker 活体):不是 slot 闲置。/queue/stats 报 pending=2 是假象(只数最近 1000 条窗口),真实 pending=640(pri1 103 / pri5 537);8 槽真忙(claim 78/分、running 常态 8 个无僵尸)。Leg C 的真因 = pri=1 队列常年积压 ~100 条,新活按"同级先到先得"排在后面——实测一个 23:06:53 的 tick 灌的批次,全等到 ~23:11:35 才被领走(285 秒)。排不空因为:① 快车道昼夜不停每 2 分钟灌一批(深夜 313s ≈ 午间 276s,无低峰);② 慢整号活(2+ 分钟)混在同条 pri=1 道占槽;③ 还有 poison 活(一条 38 小时前的 pri1 整号活重试到顶卡死、永远 pending)。

Leg D / E · 抓取 + 写库(秒级)

轮到后,worker 调抖音评论 API:抖音新评论排最前,增量抓只取首页、整页无新即早停,通常 1 个请求 ~5 秒搞定;写库亚秒。所以抓取本身不慢,慢的全在 B、C 两段等待。

③ 拐点实证:歆笛从慢路爬上快车道

B-3 说快车道 6-14 11:03 上线。歆笛这条视频的延迟正好在那之后断崖式变好——这就是 B 两条路切换的铁证:

发言抓到等待走哪条路
06-14 08:0808:3729 分🐢 整号慢路龍凤呈祥 ⭐
06-14 09:4410:1430 分🐢 整号慢路用户508 ⭐
───── 11:07 这视频首次挤上快车道 ↓ ─────
06-14 15:4915:511 分⚡ 快车道古悠然 ⭐
06-14 16:4916:577 分⚡ 快车道信仰 ⭐
06-15 00:3500:372 分⚡ 快车道二宝妈 ⭐
06-14 23:2906-15 14:2514.9 时🐢 掉回慢路🐟ᐝ
这就回答了「Tier-1 说好高频、为什么 48 分钟」:早上还没上快车道,只能走整号慢路(B-2);中午上车后全是 1–7 分钟。但它还会掉车——深夜没新评论就被踢出快车道名单,新评论又回慢路(🐟ᐝ 等了近 15 小时)。

④ 慢路损失有多大

全库高/中意向评论按延迟分桶(口径分三层,避免被历史积压误导):

口径样本中位延迟≤5 分>30 分(走慢路)
全部历史13,8422344 分4.5%86% (陈年积压污染,忽略)
近 7 天2,830261 分13.7%70% (含 6-14 断档尾巴)
近 2 天(真常态)44410 分41%37%
看「近 2 天」:快车道已把理想情况做到分钟级(41% 在 5 分钟内抓到),但仍有 37% 的高/中意向评论走慢路、>30 分钟才抓到,其中 ~22% 拖到 2–24 小时。每 10 条就有近 4 条漏出快车道——而这 4 条里 97% 是 B-4 的冷视频。

⑤ 总根因:只升不降的 tier-1 撑爆了产能 🎯

顺着 B-2/B-4 往上追:冷视频之所以这么多,是因为大量 tier-0 号被饿死、长期不被抓。而 tier-0 被饿死,又是因为 tier-1 太多——

auto-tier1 每天把「近 3 天高意向 ≥5」的活跃号升 tier-1,但系统没有任何降级逻辑——只升不降。于是 tier-1 越攒越多:

活跃不活跃
tier-1 优质号17610
tier-0 普通号10191828

176 个 tier-1 里,74% 是僵尸(近 7 天高意向口径)

近 7 天高意向个数
🟢 仍达标(≥5)46
🟡 不够格(1–4)68
🔴 挂零(0)62
完整根因链:只升不降 → tier-1 攒到 176(其中 130 个僵尸,74%,含一堆同行/工厂号)→ 176 个每 30 分钟要抓一遍 = 需要 ~352 整号活/小时,但 worker 产能才 ~270/小时 → tier-1 自己吃爆产能 → pri=5 的 1019 个 tier-0 被饿死(每号 ≥7 小时)→ tier-0 视频长期是「冷」的 → 评论落上去只能走慢路、又进不了快车道(鸡生蛋)→ 就是 ④ 那 37% 慢路、B-4 那 97% 冷视频的总源头
「总根因」≠「唯一原因」——别误读这节:tier-1 只升不降是上游放大器(决定"冷视频有多少"),但 B、C 的机制本身也各有独立的洞、且今天 #363/#366 改的正是它们:C 队列=慢快混 pri=1 道(#363 分道)+ 空跑洪水(#366 退避);B 抓取=60min 门槛锁死冷视频(#363 破鸡生蛋)+ 老视频覆盖盲区(那条 38% 真尾巴,方案② 待做)。打比方:B/C 是管道本身有堵点(已疏通大半),tier-1 是上游一直往管道灌脏水(量太大)——两头都得治。叫它"总根因"只因它是最大的单一杠杆 / 性价比最高的一刀(P0-a),不是说别处不用改。
「74% 僵尸」要分两个口径看(2026-06-16 复核):上表按「近7天高意向 <5」砍 = 132 个(75%);但这 132 里 ~106 个仍在产中意向或 1–4 条高意向 lead,是「不够 tier-1 的格」、不是「死号」。真正近7天连一条高/中都没产的只有 27 个(15%)降级该砍哪批是策略选择:砍 27(保守,只清真死号)/砍 132(激进,腾产能最多但会误降还在产 mid lead 的号)。6-16 已先砍保守那 27 个(见 ⑦),active tier-1 176→149;要不要进一步砍到 ~46 看 mid lead 价值再定。

⑥ 提升点(按性价比排)

2026-06-16 状态:P0-a/b/c/d 全部落地(详细成效见 ⑨)。P0-a 手动砍 27 + #365 自动降级;P0-b/c = #363 破鸡生蛋 + 快慢分道;#366 空跑退避;P0-d 已清 poison。Leg C 把典型延迟(median)砍了 2/3(全天 −67%)、但极端尾巴(p99)被注入洪水拖坏(见 ⑨-D 全天复核);另端到端 38% 慢尾真因是老视频覆盖盲区,下一刀见 ⑨-F。
原因(为什么要做这刀)现有内容(做了什么 · 状态)
P0-a 断总根因:tier-1 只升不降攒到 176、74% 僵尸,挤爆抓取产能、把 1019 个 tier-0 饿死 → 冷视频多、慢尾多。 ✅ 已落地:6-16 手动砍 27 个真死号(高+中=0)176→149;#365 对称 auto-demote(自动降级、断只升不降)已合并部署。下一步可按 mid lead 价值再砍到 ~46。单这一刀解大半。
P0-b 治 Leg B 冷视频:97% 慢路是「冷视频鸡生蛋」——冷视频赶不上 60min 新鲜门槛、永远进不了快车道(冷视频定义见 B-4)。 ✅ 已落地#363 破鸡生蛋——任一视频近 6h 一冒高/中意向评论,立刻临时拉进快车道(pri=0 插最前),不等它够 60min 门槛。(注入空扫待补「去重」,见 ⑨-D)
P0-c 治 Leg C 排队:慢整号活(23–211s)和快活(5s)挤同一条 pri=1 道 → 排队 4.6 分;且快车道空跑洪水(多数复扫 found=0)白占位。 ✅ 已落地#363 快慢分道把整号活降 pri=3 让快活先走(尾延迟 p90 −61%/p99 −75%);#366 空跑退避(连 3 次 0 新 → 9→18→30 分钟退避)。直接压那 4.6 分钟。
P0-d 清 poison + pri=1 减负:38h 前重试到顶仍卡 pending 的僵尸活白占;慢整号活也还堵在 pri=1 道。 ✅ 已落地:6-16 已删那个 59h poison 墓碑活;并随 P0-c 把慢整号活挪出 pri=1。(「自动清 poison」还可补成常态 job)
P1 怀疑「并发不够、要扩机」。 有效并发实测 ~8–11 槽(非瓶颈),无需盲目扩机;真瓶颈是 pri=1 队列结构(已由 P0-c/d 治)。
不要做 想靠「提 cron 频率」提速(闹钟 2min→1min)。 别做——会让快道空跑翻倍、Leg C 更糟。(#346 已验证 cron 不是瓶颈;甚至 */2→*/3 回退反而更好,见 ⑦)

理论地板:治好 B、C 两段后,整条链可压到 10–30 秒。今天那几条 1 分钟抓到的,就是低峰不拥堵时跑出来的——目标是把「1 分钟内」从偶发变常态。

⑦ 今日已动 + 数据快照

今日改动(PR #346,已上线):scrape-hot 闹钟 */3 → */2 + 源节流 45 → 30 分。但实测发现 cron 不是瓶颈(真瓶颈是 Leg B/C),所以原计划第二步 */1 已取消。

「3 分改 2 分有用吗 / 要不要改回去」(2026-06-16 实测复核)用处很小,可以改回 */3,甚至略有好处。实查近 24h scrape_video 快活 88,455 条,其中 77% 复扫 0 新评论(空跑);快车道每 2 分钟灌 ~67 条(dedup 后)。*/3→*/2 把这股空跑洪水抬高了 50%,而它灌的全是 pri=1 活——正是堵 Leg C 的那条队。所以:改回 */3 反而少 33% 空跑、减轻 pri=1 积压,只换来热视频复扫间隔从 ~2 分变 ~3 分(次要)。真正起作用的是同批的源节流 45→30(让 tier-1 更早可重抓),不是 cron tick。结论:tick 是二阶旋钮,回 */3 没坏处;真问题仍是 P0-a 降级 + P0-c 队列双层优先级。

cron tick=2分 ✓Leg C 中位 276s / p99 327sLeg D 中位 4.9s快车道命中率 18%worker 实测有效并发 ~11 · 总吞吐 ~3000 活/htier-1 149(6-16降27真死号)tier-0 1046慢路 37%(近2天)冷视频 97%

2026-06-16 实测刷新 + 已动刀:① 机器就 2 台(已用 fly status 实查坐实,不是推测)——akke-worker-prod 现 2 台 started、都在东京(nrt),auto_stop=off 常驻不缩,不会因为 pri=1 洪水自动多起第三台(Fly 没有"按负载自动扩容"这回事;早先写「瞬时 >2 台 / deploy token 读不到机器数」是错的,现已实查更正)。要加机器只能手动 fly scale count=N。② "槽"分两层,别混(这就是之前 8 / 11 打架的根源)工位(job slot)= 2 台 × WORKER_CONCURRENCY=3 = 6 个,是"同一时刻最多领几个活"的硬上限;但一个"整号活"内部还能同时拉 2 条视频MAX_VIDEOS_PER_SOURCE_CONCURRENT=2)→ 真正"同时在飞的抓取请求"能到 ~6–12 条。Little's law 反推的 ~11,量的是后者(在飞的请求数),不是工位数;running 行 14 里还含 ~3 个 zombie 虚高。总吞吐 ~3000 活/h。③ 已执行 P0-a 第一刀:把「近7天 高+中意向产出=0」的 27 个真死号降回 tier-0,active tier-1 176→149(回滚清单存盘)。
两台机器怎么配合 · 要不要扩 / 怎么优化(2026-06-16 实查后补)
配合方式 = 不用配合,数据库的"行锁"就是调度员。两台都是一模一样的无状态 poller,各自独立去同一张 scrape_jobs 表抢活(调 claim_pending_scrape_jobs_batch,靠 Postgres FOR UPDATE SKIP LOCKED)。这把锁保证同一条活绝不会被两台同时领——谁手头空了谁去抢下一条,天然就负载均衡,没有主从、不用分工、不会撞车。所以"两台如何配合"的答案是:它们根本不需要互相知道对方存在。
要不要加到三台?暂时不要。 横向 fly scale count=2→3 能线性加工位(每台 +3),但 ⑥-P1 已实测并发不是瓶颈——这 6 个工位有近 9 成时间在空跑(复扫 0 条新评论)。盲目加机器只会让"空跑"跑得更快,治不了 Leg B/C 的结构病。纵向把单台 WORKER_CONCURRENCY 3→5 也不行,会撞 shared-cpu-1x 的 CPU 墙(代码注释明写 5+ hits CPU wall)。
真正的优化 = 让现有这 6 个工位别空转:① 破鸡生蛋补"注入去重"(同一视频别每个 tick 重注一遍)把空跑率从 88% 砍下来,等于免费释放产能;② pri 防饿死,让 tier-0 别被堵 12 小时。这两刀做完,2 台绰绰有余;做不完,加到 3 台也只是"更快地空跑"。

⑧ 队列三问:Leg B 入的什么队 / BC 推到极限 / 快车道 100 条(2026-06-16 补,均查代码 / 生产库坐实)

Q0 · 先澄清「Leg B 入的是什么队」

Leg B 的"入队" = 把这条视频 / 号写成一行 scrape_jobsPostgres 表,不是 Redis / 内存队列)。两类行:

job_type是什么活priority
scrape_source整号活:去主页捞 ≤20 条视频逐条抓tier-0 = 5 / tier-1 = 1
scrape_video单视频快活:快车道增量复扫1
B 和 C 围着同一张 scrape_jobs 表。 worker 的 8 个 slot 各自轮询,调 claim_pending_scrape_jobs_batch RPC(Postgres SKIP LOCKED + ORDER BY priority ASC)领活。Leg B = 等 cron 把活写进表("何时轮到抓它");Leg C = 活已表里、按 priority 排队等某个 slot 领走。

Q1 · BC 推演到极限:能压到多久、怎么做、good/bad

现状极限地板怎么做
Leg B冷视频几十分~几小时≤2 分(常态 1–2 分)P0-b 破鸡生蛋:任一视频一冒高意向评论立刻强行注入快车道,不等它自己够 60min 门槛 → 压到 ≤1 个 tick(2 分)
Leg C≈ 4.6 分亚秒~数秒P0-c 双层优先级(带新评论的活提 pri=0 插队、慢整号活移出 pri=1)+ P0-d 清 poison → 活到达空队列 + 有空 slot 立即领
B+C 合计≈ 10–30 秒= ⑥ 末尾「理论地板」。今天偶发「1 分钟抓到」就是低峰不拥堵时已摸到这个地板
物理墙(压不穿):B 想再往秒级压需要"评论事件触发即扫",但撞 抖音反爬 ~67 req/min/号 红线——这是硬上限,不是代码问题。再快就伤生产号(滑块 / SMS / 封号升级链)。
GOODBAD / 风险
① 高意向触达从分钟~小时 → 1–2 分,抢客户热度窗、转化↑
② tier-0 解放、冷视频减少、漏抓减少
③ 队列不积压、poison 自清、系统更稳
① P0-b 强注入若不 capped 会重蹈 6-13「洪水偷产能饿死 tier-0」——必须配 P0-a 先释放产能 + 集合 capped
② 提扫描频率 / req-min 触发反爬、伤号(硬上限)
③ P0-a 降级可能误杀暂时低产的优质号(需回升机制)
④ 工程复杂度↑、更多队列状态要监控

Q2 · 快车道 100 条过完要多久

实测
受 pri=1 常年积压 ~100 条 + 慢整号活(23–211s)混在同条 pri=1 道占槽拖累。实测一个 23:06:53 灌的批次全等到 23:11:35 才被领走
≈ 5 分钟 / 批
理论地板
独占 slot、纯单视频快活:100 ÷ (并发 / 活长)。按 6-16 实测有效并发 ~11 槽、活时长中位 5.8s 算:100 × 5.8 ÷ 11
≈ 53 秒
治理后
P0-c/d 把慢整号活移出 pri=1、给带新评论的活插队,100 条快活不再被整号活堵
逼近 ~50 秒
口径更新(2026-06-16):原写「8 槽 → 理论 1 分钟」是 6-15 单次快照。6-16 直查生产库(Little's law:吞吐 ~50 活/分 × 活长均值 13.3s)实测有效并发 ~11 槽(running 行 14、约 3 个 zombie 虚高)、总吞吐 ~3000 活/h——比 8 高,故地板从 ~1 分缩到 ~53s。:slot 数只影响"一批 100 条多久跑完"(吞吐),不改单条评论的端到端地板(Q1 的 10–30s 由 tick + Leg D 5s 决定,与槽数无关)。
另两问搬家了:「云电脑 DM/RC 数据如何回传」「PR #360 能否自动回传」「关注流抓 day-0 新视频两探针」属于触达/潜在触达管线、不属于评论抓取时效,已移到 用户旅程 · 潜在触达页journeys-fanny#pt)的「技术机制」区。

⑨ 2026-06-16 实施进展 · 成效复盘 · 新发现的真尾巴(B/C 落地后)

⑥ 列的 P0 提升点今天全部落地(多个 PR 已合并部署)。但实测下来(全天 24h 复核):Leg C 把典型延迟砍了 2/3、但极端尾巴被注入洪水拖坏;Leg B 喜忧参半;而"评论拉取还是很久"的真尾巴另有其因——不是排队、是老视频覆盖盲区。逐项说清。

A0 · 本轮所有改动一览(每个 PR 一句话 + 比喻)

⑨ 段提到的每个 #编号 改动,先在这张表里一句话说清「它是什么 / 治什么病 / 现在什么状态」,下面 B、C 再展开。比喻统一用「果园摘果」:视频 = 果树,评论 = 果子,高意向评论 = 熟透值钱的果(lead),抓评论 = 派人去摘

改动一句话(果园比喻)治什么病状态
#363 · Leg B 破鸡生蛋看到偏远老树刚冒熟果,破例先抱上"重点照看名单"、还排最前,不等它够格冷视频上的高意向评论,第一条几乎必慢已上线 6-16
#363 · Leg C 快慢分道"摘单棵树"的快活和"巡整片园子"的慢活分开排队,让快的先走慢整号活堵在快活前面,快活干等已上线 6-16
#366 · 空跑退避一棵树连去 3 次都空手就少去(间隔 9→18→30 分钟),一冒新果立刻恢复勤去视频被反复复扫却抓 0 新、白占工位已上线 6-16
#365 · tier-1 自动降级不再活跃的优质号自动降回普通档(对称于自动升档)tier-1 只升不降、撑爆产能、饿死 tier-0已上线 6-16
#377 · cold-intent 回扫近 14 天还在冒高意向的老视频,每 20 分钟低频回扫一遍常青老视频上的新高意向,没人定期扫(老视频盲区)已上线 6-16
#381 · 注入去重破鸡生蛋的注入活也纳入退避(去掉原本豁免),别每 tick 重派同一棵树pri=0 注入洪水:同一视频 80 次/6h、100% 抓 0 新、拖坏 p99已上线 6-17
#413 · 早停修复增量抓评论别"一页全老就停",要连续 N 页全老才停(抖音按热度排、新评论埋后面)快道判"空"里 33% 是假空、漏了真评论(含 lead)已部署 6-18 · 终判:假空 33%→13% 治住 ✓,但快道量 +59% 把低优先级挤死、claim-wait 尾巴反恶化(见 D 段 🔬)
B 和 C 怎么关联?一前一后两道关:
· Leg B 管"这条视频该不该被抓" —— 把该趁鲜抓的(含冷视频、刚冒高意向的)塞进快车道这个队列。
· Leg C 管"塞进来的活排不排得动" —— 队列里谁先跑(快慢分道)、别反复空扫白占位(退避)。
一句话:B 把活送进队列,C 决定它在队列里跑得顺不顺。B 一旦送太多(如注入洪水),就会把 C 的队列堵了 —— 这正是 #381 要补的接缝。

A · 两块地基:队列里有哪几类「活」+ 它们怎么排队跑

先说清两个容易混的事,免得往下读拧巴:

1)「Leg」是什么?=一条评论从"被发出"到"我们抓进库",中间分好几段路(② 段把它拆成五段)。本段只关心其中相邻两段:

· Leg B = 「派活」那段:评论已经存在了,但系统还没派出一个活去抓它("菜做好了,没人来端")。

· Leg C = 「排队执行」那段:活已经派进队列了,等 worker 领走真正去抓("菜端到打饭窗口,等阿姨打饭")。

2)「地基①②」不等于「Leg B / C」。地基是底盘(系统本来就这么转);Leg B / C 是在底盘上做的改动招式。下面先讲底盘,再讲招式。

以下「地基」是纯现状(系统现在怎么转,不含本轮改动);改动一律见上方 A0 一览表。

🍎 嫌底下黑话多?先用「果园摘果」一个比方把整套 B/C 讲通,后面的词都能对上号(这张讲"机制怎么转";想看"五段各自在哪堵"翻 ② 段「🍚 食堂打饭」,两张互补):

地基① · 队列里就三类「活」(下面反复提到)

活的类型抓什么快慢
单视频快活
scrape_video
只抓一条具体视频(快车道按规则点名的那条,job 里带它的 url——通常是「正在冒新评论的热视频」或「近 6h 冒过高意向的视频」)底下的评论,且是增量(只取比上次新的)快 · ~5 秒/条
慢整号活
scrape_source
不点名具体视频,给的是一个号:去它主页捞最近 ≤20 条视频,再逐条把每条的评论都抓一遍(抓哪几条由号当下决定)慢 · 中位 23 秒、最慢 211 秒
高意向注入活本质也是单视频快活,但因「这视频近期冒过高意向」被特批插队(近期 = 近 6 小时,为什么是 6h 见 B 段;机制见 B)快 · ~5 秒/条
📐 本段【地基】= 纯"现状·系统现在怎么转",不带改动编号。上面三类活、下面 pri 排队,都是当下事实。「这些插队/降档分别是哪个措施加的(#363 / #366 / #381 时间线)」全部挪到下方 B、C 段讲——读地基时先别管编号,只看"现在怎么转"。
跟一下两个最容易绕的点:
1. "指定视频"到底怎么指定?=按抖音给每条视频的唯一编号 aweme_id 点名抓那一条。谁来点名 = 快车道 / 高意向注入 / UI「抓这条」,它们把一个 aweme_id 写成一行 scrape_video 活。所以"单视频快活"永远只盯一条视频,不碰这个号底下别的视频。
2. tier-0 / tier-1 是"号"的分层,不是"活"的分层。单视频快活、高意向注入活都不分 tier(它们只认一条视频);只有"慢整号活"才看 tier——这个是 tier-1(优质、抓得勤)还是 tier-0(普通、抓得稀)。tier-1 是怎么来的?auto-tier1 cron 每天把"近 3 天高意向 ≥5 条"的活跃号自动升上来(对称的自动降级见下方 ⑤/⑦ #365);tier-0 是默认档、绝大多数号都在这。

地基② · 这些活怎么排队跑(pri 优先级 = 号码牌)

每个活贴一张「号码牌」priority,worker 严格从小号往大号领,同号牌内先到先得。越急、越快、越该趁鲜的,贴越小号:

号码牌谁贴这号(入队来源)含义
0高意向注入 · vip-rescan · UI「抓这条」(运营在后台仪表盘上手动点「立即抓取这条」的按钮)最优先,最该趁鲜抢
1常规单视频快活(fresh-catch 快车道)快,先放走
3tier-1 慢整号活 · rescan-recent慢,让到快活后面
5tier-0 每小时轮抓 · lead 资料(给某个潜在客户建档时顺带触发的抓取,scrape_lead_profile,没指定 pri 就吃默认 5)最次,量大不急
这四档落到"实际多久轮到"(具体快慢时间):
· pri=0 高意向注入:插最前,空队列时秒级 ~ 1 个 tick(2–3 分)就被领走。
· pri=1 常规快车道单视频:执行~5 秒/条;排队中位 0、尾巴(p90) ~2 分。
· pri=3 tier-1 慢整号:执行中位 23 秒、最慢 211 秒/个;让到快活后面,实测 pending 最老 ~9 分。
· pri=5 tier-0 每小时 + lead 资料:只在上面三档全空时才轮到 → 实测能积压到 12 小时没人抓(就是 ⑤ 总根因里被饿死那批)。
("lead 资料" = 上表 scrape_lead_profile:识别出一个潜在客户后,系统顺手排一条活去抓这个用户的主页/视频补全画像,它没单独设优先级,就吃了默认 pri=5。)
这套 pri 是 6-16 改动前就有的吗?—— 机制本就有,但 #363 动了两处(这就是 Leg B / C 落在 pri 上的地方):
号码牌6-16 改动前现在(#363 后)
0vip-rescan、UI 手动+ 高意向注入 ← 新增
1常规快活 + tier-1 整号活(挤同一道)只剩常规快活
3基本空着tier-1 整号活(从 1 降来)、rescan-recent
5tier-0 轮抓、lead 资料不变

就两处变化:tier-1 整号活:pri 1 → 3(原来和快活挤 pri=1 同一道、堵快活,这就是 Leg C 快慢分道);② 新增 pri=0 高意向注入(原来没有,这就是 Leg B 破鸡生蛋)。其余不变。

📊 pri 排队泳道(worker 严格按号码牌从小到大领,号越小越先被抓)
pri=0 注入
秒级被领
pri=1 快活
~5 秒
pri=3 整号
让到快活后
pri=5 tier-0
饿死·积压 12h

↑ worker 永远先把 pri=0 领光、再 1、再 3,上面三档全空了才轮到 pri=5 → tier-0 被饿死(⑤ 总根因的机制)。

怎么定的 / 怎么跑的:没有配置表、运营不可调——priority 是散在 7 个入队点的硬编码(列 DEFAULT 5),要改得改代码走 PR。worker(~30s/轮)调认领 RPC,核心 = WHERE pending AND scheduled_at≤now AND attempts<3 ORDER BY priority ASC, scheduled_at ASC FOR UPDATE SKIP LOCKED(严格 pri 小→大、同级先到先得、并发安全)。领到即开抓;卡死(>8min)被扫回重排;attempts 到 3 被排除=永久 pending 墓碑(P0-d 清的就是它)。
⚠️ 关键副作用:纯抢占、pri 之间没有防饿死——pri=5 只在 0/1/3 全空时才轮到 → 能积压 12 小时;tier-1 一胀大就把低 pri 饿死(这是 ⑤ 总根因的机制)。

B · Leg B 破鸡生蛋(#363):治「冷视频上的高意向评论,第一条几乎必慢」

先回顾:Leg B = 「派活」那段路 —— 评论已经存在了,但系统迟迟没派出一个活去抓它。本段治的就是冷视频上这个"迟迟没人派活"。

【问题】一条视频要先进「快车道」才会被频繁单独复扫;而进快车道的门槛是——它的评论得在 60 分钟内被我们新鲜抓到过一次。可冷视频(老视频 / 不活跃号底下的)只能等慢整号活几十分钟~几小时扫一遍,永远赶不上 60 分钟 → 永远进不了快车道 → 它上面新冒的高意向评论第一条几乎必慢。死循环:要被快抓得先进快车道,要进快车道得先被快抓(「鸡生蛋」)

↑ 拗口的词跟一下:「快车道」=每 2~3 分钟挑一批「正在冒新评论的 top 100 视频」单独复扫的机制(详见 B-3);「60 分钟门槛」=只有被我们在评论冒出 1 小时内抓到过的视频才算"够格"上这条快车道。

【改动】不再死等门槛。只要一条视频近 6 小时内冒过高/中意向评论,就直接把它塞进快车道、还插在最前面(pri=0)优先抓——「破鸡生蛋」= 直接抱它上车,不等它自己够 60 分钟的格。

这个「6 小时」是怎么定的、要不要改?

6h 的由来:注入要圈"刚冒过高/中意向的视频",6h 是个"近期"窗口——够宽,半夜冒的意向到白天还能补上。

实测发现它的毛病是"太长":高意向视频天生是老·常青视频(实查"曾产高意向、还活着"的视频全部 >4 天龄)。一条视频 6h 内只要有过一条高意向评论,就被每个 tick 重选注入一次 → 6h ≈ 120–180 个 tick → 同一条被反复抓 80 次、100% 抓 0 新。

但核心病不在 6h 长短,在"每个 tick 都重注一遍"。缩到 2h 只是按比例砍空跑(治标);根治靠"注入去重"(同一视频注入过就别每 tick 重派)——这已由 #381 落地(A0 清单里那条)。所以 6h 维持不动。

去重会不会把我们关注的视频也抓慢?不会——退避是"自适应",不是"抓一次就停"。退避按【每条视频】记账(empty_streak):连抓 3 次都 0 新才开始拉长间隔(9→18→30 分钟),一冒新评论立刻清零、恢复每 tick 勤抓。所以真在不断冒评论的活跃视频永远不空手、永不退避、频率不降;被降频的只有"冒过一波后安静下来"的(实测那反复 80 次正是这批,100% 空手)——对它们降频一条 lead 不少抓、只省白跑。后续旋钮:可给高意向视频设更短的退避上限(如封顶 10 分钟)。
▸ 工程精确版(改了哪个文件 / 参数 / 阈值)

C · Leg C 快慢分道 + 空跑退避(#363 + #366):治「活派进队列后排太久 + 反复空扫」

先回顾:Leg C = 「排队执行」那段路 —— 活已经派进队列了,等 worker 领走真正去抓。本段治的就是这个排队:别让慢活堵快活、别反复空扫白占位。

【问题①·排队】活派进队列后 worker 一个个领。原来慢整号活(23~211 秒)和单视频快活(5 秒)挤在同一条号牌道(都 pri=1——前面卡一个慢整号,后面快活全得干等,实测排队中位 ~4.6 分钟。

【改动① · #363 快慢分道】把慢整号活降一档(pri 1→3,让 5 秒的快活先走、别被慢活堵。实测典型延迟 median −67%(见 D);但极端尾巴 p99 反被注入洪水拖坏 → 已由 #381「注入去重」补上(见下方改动③)。

【问题②·空扫】很多视频被反复复扫却抓不到新评论(实测近 24h 复扫 88,455 条、77% 抓 0 新),白占 pri=1 的位置、加重排队。

「空扫」到底算 Leg B 还是 Leg C 的问题?—— 两个来源,分开治:
· 来源一(C 自己的):常规快道把"早冒过评论、现在安静了"的视频反复复扫 → 抓 0 新。这是本段 #366 退避治的。
· 来源二(B 制造的):破鸡生蛋的注入洪水每 tick 重派同一批视频(见 D + A0 #381)→ 也是空扫,但根在 Leg B。
所以"空扫"是症状、根有两个:C 的常规复扫(#366 治)+ B 的注入重复(#381 治)。

【改动② · #366 空跑退避】一条视频连抓 3 次都 0 条新评论,就把它下次复扫往后推(9→18→30 分钟封顶);一旦又冒新评论立刻清零、恢复勤抓

↑ 拗口的词跟一下:「退避」=越扫越没货就越少去扫它、省得空跑;靠 videos 表新加两列记账:empty_streak=连续抓 0 新几次、next_fast_lane_at=推到几点才允许再进快车道。
9→18→30 这三个数怎么来的?= 指数退避(exponential backoff,标准重试算法)。每多空手一次、间隔翻一倍(9×2ⁿ:9→18→30,第三步本该 36 被 30 封顶)。
· 9 分钟起步:比 3 个 tick(≈9 分)略长——给视频留点时间真冒出新评论再去,不浪费。
· 30 分钟封顶:再久就交给"巡整片园子"的全量轮兜底,快道不必死磕一棵安静的树。
⚠️ 诚实说:指数退避是标准模式,但 9 / 30 这俩具体值是工程"按 tick 节奏 + 全量轮兜底周期"拍的默认值,不是理论推导的最优解(env 可调 FAST_LANE_BACKOFF_BASE_MIN=9 / _CAP_MIN=30,嫌慢调小、嫌费调大)。
▸ 工程精确版(文件 / 参数 / 阈值)

改动③ · #381 注入去重(2026-06-17):把 pri=0 高意向注入也纳入退避 —— 治 D 段那座「注入洪水」

【为什么要补】#366 退避当初故意豁免 pri=0 注入(怕压住破鸡生蛋该抢的活)。全天复核坐实这豁免错了——注入洪水(80 次/6h、0 新、把 p99 拖到 80 分钟)的实测拆解见上方 D 段,此处不重复。结论:豁免要去掉。

【改动】去掉那条豁免,让高意向注入跟普通快活一样退避——注意是「自适应」,不是粗暴的「摘过一次就再也不去」。

↑ 果园比方接上 B 段:原来"这棵树冒过高意向果"就每 3 分钟去摘一趟、不管摘不摘得到;现在它跟普通树同一个规矩——连去 3 趟都空手才拉长间隔(9→18→30 分钟封顶),一旦又结新果立刻清零、恢复勤摘。关键:退避记在每棵树上videos.empty_streak),任何一条路摘到这树的新果都清零 → 真在不断结果的好树永不空手、永不退避、频率一点不降;被拉长的只有"冒过一波后安静下来"的树(注入空跑的正是这批)。所以它不会把"我们关注的活跃视频"抓慢

📊 数据依据 · 上线后头 25 分钟时间线(部署 2026-06-17 11:48 北京时间,直查 scrape_jobs / videos

下表时间都是 2026-06-17 当天;Z = UTC(0 时区),+8 小时 = 北京时间,已在每行标出北京时间。

测量方法 · 诚实说明:下面是上线即时的 leading indicator(注入创建率 + worker 槽占用 + tier-0 积压),不是 D 段那种全天 24h 的 p99 终判——p99 是 24h 滚动尾巴指标,25 分钟动不了,要等下一个完整全天窗口再复核。(自查纠错:首测因分页查询没加稳定排序,一度误读成"创建率瞬间归零";给查询加 .order 重测后修正为下面的渐进衰减——这条曲线才和"连 3 次空扫才起退避≈9 分钟"的机制自洽。)
时刻pri=0 注入创建率(每 10min)worker 运行槽(瞬时)tier-0(pri=5)积压
部署前 · 11:43 (03:43Z)~180(≈18/min 持续灌,对得上全天 26995)pri=0 霸占 6/11 槽353 个 · 最老 13.0h
+12min · 12:00 (04:00Z)65(−64%)352 · 12.8h
+22min · 12:10 (04:10Z)~22(−87%,仍在降)pri=0 = 0;pri=5 拿 10/16 槽(62%)350 · 12.7h
📊 #381 前后一眼对比(部署 03:48Z → +22min)
红 = 前(坏)绿 = 后(好);每项都附一句"为什么这是坏 / 好"。
注入创建率(每 10min)· −87%
180
22
tier-0 最老积压 · −60%
13.0h
5.2h
pri=0 注入活占了多少 worker 工位 · 清零
6/11 槽
为什么坏:这 6 个工位全在跑「100% 空跑」的注入活 = 一多半产能空转,把别的活(尤其 tier-0)饿死。
0/16 槽
为什么好:注入洪水没了,腾出的工位给了之前被饿死的 tier-0,它终于能排积压(这也是 tier-0 积压从 13h 降到 5.2h 的原因)。
⚠️ 这是上线即时先行指标,非 p99 终判(p99 要等下个全天窗口)。明细见下表 + 诚实边界。
大白话读这张表:① 注入洪水正按设计退潮——创建率从 ~180/10min 渐进降到 ~22(−87%)、还在往下(每棵高意向树陆续连摘 3 次空手后进退避)。② worker 产能腾出来了——部署前 pri=0 霸占 6/11 个运行槽全耗在空扫上,现在 pri=0 清零、被饿的 tier-0(pri=5)拿到 10/16 槽(62%)。③ 注入也退避坐实——退避中视频从 +12min 的 425 涨到 +25min 的 438,新增的正是高意向视频(empty_streak=3/4 那两档在涨)。
诚实边界 · 别过度解读tier-0 积压"深度"还没排空(仍 350 个、最老 12.7h)——这是存量在被啃(最老龄 13.0→12.7h 已掉头往下),不是 25 分钟能清的;它现在拿到多数 worker 槽,预计未来几小时积压曲线才看得出降p99 改善要等下一个全天窗口复核。这一步只证明机制按设计在转、方向对,没证明 p99 已修好。
🔬 上线后追加验证(06-17):「判空」里有 33% 是假的,漏了真评论(含中意向)

起因:运营反馈中高 leads 变少。循线去查——"快道判 0 新的视频,后来全量轮有没有补到、抓那一刻就已经存在的评论"。

结果(近 12h 抽 120 个判空视频):

· 67% 是真空:确实没新评论可抓(结论不变)。

· 33% 是假空:抓时评论已存在、却被快道漏掉,晚 50~553 分钟才由最慢的全量轮补到,漏的里含中意向 lead

根因:快道增量抓取「整页全老就停 + 只看前 50 条」,假设抖音按时间倒序返回;但抖音 web 评论接口默认按热度排,新评论被埋在后面够不到。

要更正前文一处口径:退避省的是浪费没错,但"0 新 = 纯空跑、不丢 lead"只对那 67%;剩下 33% 的真评论被推给最慢的全量轮 —— 这才是"中高 leads 变少/变慢"的直接根因

退避只是治标,这个早停 bug 要单独修 —— 就是 A0 清单里的 #413已于 6-18 部署;这些视频抓得到东西、empty_streak 清零、自然不再退避)。验证脚本 _verify-fastlane-earlystop-bug.ts
(6-19 终判:早停半治住假空 33%→13% ✓,但「不再退避」让快道量 +59%、反把低优先级活挤到队尾饿死,claim-wait 尾巴恶化 —— 详见 D 段 🔬 终判框。)

↑「真空 / 假空」大白话啥意思:快道抓完报"0 条新评论"就判这视频"空"。判空有两种真相——
· 真空=真没新评论可抓(早抓过了 / 压根没人新评论),说"空"是对的、不亏 lead。
· 假空=其实有新评论、抓的那一刻就在树上,但快道只扫了"最显眼的前几枝 + 前 50 条"(抖音默认按热度排),没绕到后面那枝新结的,就误报"空"——果子在的、被漏了,过 50~553 分钟才由"绕整棵树的全量队"补摘走,里面有中意向客户
怎么分辨的:对每个判空视频查——有没有评论满足「发表时间早于快道这次抓取(抓时已在)」且「入库时间晚于快道抓取(却是后来别的扫补的)」。有=假空(当时够得着却漏了),没有=真空。
📊 判空真假占比(近 12h 抽 120 个判空视频)
真空 67%
假空 33%
漏真评论·含 lead
部署锚点:squash 16bcfc233 / Fly worker v306(已 /health 探活)。改动:scrape-hot/route.ts(hiIds 纳入 next_fast_lane_at 退避过滤)+ worker/queue.py(去掉 high_intent 豁免)。无 migration。急停开关 HOT_FAST_LANE_BACKOFF=0(全关退避)。

D · 成效实测(全天 24h 对比:部署前 06-15 04:00→06-16 04:00 vs 部署后 06-16 04:00→06-17 04:00)

⚠️ 口径已升级:从"8.5h 低峰窗口"换成"全天 24h"——更诚实,但结论也跟着变了。原先用 04:00–12:25 那 8.5 小时对比,毛病是那段正好是低峰(北京下午),把前后都美化成"median 0 秒、p90/p99 全绿"。换成完整 24h(含高峰)后:median 改善依旧实锤,但尾巴 p90/p99 的故事翻了。两边各 24h、都按 scheduled_at 落在窗口内、started_at 非空(已认领)统计。
先看懂这三个数(median / p90 / p99 怎么读):把所有活的等待时间从快到慢排成一队——
· median(中位数)=站正中间那个,一半比他快、一半比他慢 =「典型情况」。
· p90=排在 90% 位置那个,只有最慢的 10% 比他还慢 =「尾巴(较差)」。
· p99=排在 99% 位置,只有最惨的 1% 比他更慢 =「极端最差」。
为啥不用平均?平均会被个别超长的极值拉偏;分位数才看得清真实分布。

Leg C 快慢分道:中位大赢、尾巴没赢(全天口径下的翻车点)

scrape_video 入队→被认领等待medianp90(最慢 10%)p99(最惨 1%)
① 06-15 部署前 (n=26544)233.7s454s1151s
② 06-16 注入洪水峰·#381 前 (n=51170)75.5s486s4596s
③ 06-17→18 #381 后 ~21h (n=27269)57.8s1138s2191s
④ 06-18→19 #413 后·全天 24h (n=35507)84.2s1877s6202s
大白话读这张表典型情况(median)从 3.8 分压到 1.2 分(−67%)——快慢分道确实让大多数快活先走了。优先级分布坐实:部署前有 6355 个整号活混在 pri=1 跟快视频抢道,部署后整号活搬到 pri=3(3160)、pri=1 里的整号只剩 112但 p90 基本没动、p99 反而从 20 分钟炸到 80 分钟。为啥?看同一份数据的另一个数:pri=0 高意向注入活从全天 905 个暴涨到 26995 个(30 倍)——破鸡生蛋的注入洪水(每 tick 重注,见下)在 pri=1 前面又垒了一座 pri=0 大山,典型活照样快,但挤在洪水里的那 1% 倒霉蛋等得比以前更久。
(注:p99 里可能掺了少量 zombie 活被重排、started_at 取最后一次认领造成的虚高;但方向——"尾巴没改善、注入洪水是主因"——跟全部其它证据一致。)
三窗连看(含 #381 后的全天复核,诚实版)

· ①→②(#363 快慢分道):median −67%(典型大赢);但注入洪水把 p99 从 ~19 分钟炸到 ~77 分钟(尾巴翻车)。

· ②→③(#381 注入去重,6-17 上线):洪水退了(活数 51170→27269)、median 史上最好(57.8s)、p99 从洪水峰 4596 砍到 2191(−52%)、高+中 leads 回到 446(占比 8.3%)。

⚠️ 但尾巴还没回到基线:p99 仍 ~2× 基线(2191 vs 1151)、p90 反而 454→1138 变差。最可能的残余原因 = 早停 bug——#381 退避更狠了,而那 33%「假空」(真评论被漏,见下方 🔬)被退避推给最慢的全量轮、拖长了尾巴。

→ #413 早停修复 2026-06-18 部署,本想治这条尾巴;④ 全天窗口终判见下方 🔬 —— 结论反转:完整性治好了,尾巴反而更糟。

🔬 #413 终判(2026-06-19,④ 全天 24h 窗口闭合后)—— 结论反转

先把两个指标分开(容易混):「假空率」=快道每次抓有没有把已存在的评论漏掉(抓取完整性);「claim-wait 尾巴」=活在队列里排多久才被领走(队列吞吐)。#413 只动前者,但人们以为它能顺带治后者——终判证明这个假设错了。

① 早停 bug(直接靶子)✅ 治住了:假空率 33%→13%(post-fix 10h、抽 120 视频);videos.empty_streak≥3 从 313 崩到 17——视频几乎不再被误判空。

② 但 claim-wait 尾巴没回基线、反而更糟(见上表 ④):p90 454→1877s(基线 4.3×)、p99 1151→6202s(基线 5.3×,比注入洪水峰 ② 的 4596 还惨 = 史上最差)。

为什么修了完整性、尾巴反炸?(证据链坐实):#413 让快道几乎不再判空 → 视频不再累积空跑 → 不再被退避 → 快道 pri=1+59%(13105→20858,100% 是快车道)。这股高优先级洪水把低优先级 scrape_video(cold-intent 老视频回扫 pri=3 等)挤到队尾饿死——尾巴那 1% 的活全是 pri≥2pri=0/1 一个都没有),得等数小时靠 aging 才浮上来。时段上也对得上:尾巴全集中在北京白天/晚上(高负载),深夜零负载零尾巴。结论:尾巴是被快道挤死的低优先级活,不是快道本身。

所以 B 方案(max_comments 50→150)坐死不做:它治的是「截断型」完整性(老评论被埋到前 50 名外),却会让每个快道活抓更多页、更慢、排空更慢 → 低优先级饿得更死 → 尾巴更糟。拿错药治错病。

真正的旋钮 = pri 防饿死(每批 worker 槽给 pri≥3 留配额 / 强化 aging)。讽刺的是:cold-intent 老视频回扫(专门救完整性慢尾的②方案)自己卡在队尾,被③的快道洪水在排队层面抵消了。这条要动 worker 领单逻辑(高危),单开 PR 专门做、不糊。

✅ 2026-06-19 复诊(pri 防饿死降级 · 留观,不开 PR)

上面 #413 终判把 6202s 尾巴归到「cold-intent pri=3 被快道洪水挤死、真解=pri 防饿死配额」。6-19 复跑 36h(覆盖 6-18→19、按 pri 段拆 scrape_video)把这个判断收窄了

· cold-intent pri=3:全部认领,claim-wait p50 27m / p99 1.9h / max 2.0h、0 条超 6h、pending 仅 15 条且都没等满 1h。那条 6202s≈1.7h 的尾巴是真的(本窗口 p99 1.9h 正印证它),但封顶在 2.0h,正好踩在 cold-intent 自己「激进追平 ≤2h」的设计容差里,没有超出 2h 的长尾

· tier-0 pri=5:已被 aging 压到 p99 1.1h / max 13.4h(仅 8 条,0.01%)、pending 最老 6.8h——不再是早先的 12h / 27h 积压。aging(migration 20260615144043)在起作用。

· leads 没掉:高+中意向入库近 7 天稳在 370–463/天,所谓「446→380」落在日间方差内,不是这条尾巴的连带伤。

结论:pri 防饿死配额(动 worker 领单逻辑、高危 PR)当前没有一个真实坏掉的指标支撑,从「该上了」降级为「留观」。真要再压这条 1.7h 尾,对症且便宜的杠杆是 env COLD_INTENT_RESCAN_INTERVAL_MIN(120→60) / COLD_INTENT_RESCAN_MAX(纯 fly secret、不改码、不开 PR),而不是先写 RPC。

这座注入洪水到底是什么(部署后 8.5h 切片实测,06-16 04:00–12:25)

这段窗口里,pri=0 高意向注入活跑完 9039 个(= scrape_jobs 表中 priority=0、标 high_intent、在此窗口完成的单视频活条数;全天口径为 26995 个)。但这 9039 个活只摊在 112 个不同视频上 —— 平均每条被重复抓 80.7 次9039 ÷ 112 ≈ 80.7)。三点说清这洪水的性质:

定性:这不是 #366 退避坏了(#366 按设计放过 pri=0),而是 Leg B 破鸡生蛋自己制造的新空跑 —— 它把"延迟问题"换成了"空跑问题"解药 = #381 注入去重(已上线 6-17;机制 + 上线 25 分钟实测见上方 C 段「改动③」,此处不重复)。

📥 机制前后 · 实际拉到的高/中意向评论数(全天 24h 对比)

窗口入库评论总数高意向中意向高+中高+中占比
① 06-15 全天 (部署前)4080742653398.3%
② 06-16 全天 (#381前)5291932803737.0%
③ 06-17 全天 (#381后)5361973494468.3%
①→③ 变化+31%+31%+32%+32%8.3%→8.3%
诚实边界(全天口径更冷静):高+中从 339→373(+10%)、高意向 74→93(+26%)。但当天入库评论总量本身涨了 30%,而高+中占比反而从 8.3% 掉到 7.0%——说明这 +10% 基本是"评论盘子变大"顺带带上来的,机制本身没把"高意向命中率"提上去。⚠️ 这同时修正了原版的过度解读:原先 8.5h 窗口看到"高意向 +89%、近翻倍",全天一摊只剩 +26% 且占比下降——那个"翻倍"是低峰小样本的噪声、不成立。要看 B/C 机制真效果,得多日累积 + 剔除评论总量波动。
更新(③ 06-17 / #381 后):高+中 回到 446、占比也回到 8.3%(vs 基线 +32%)——06-16 占比掉到 7.0% 是注入洪水期的暂时现象,#381 退潮后就回来了。但仍需多日看稳态。

E · 新发现:评论拉取还是很久的真尾巴 —— 不是排队,是「老视频覆盖盲区」

端到端「高意向评论 comment_time → 入库」实测(近 36h,n=124):median 34 分(典型还行),但 >1h 占 47%、>6h 占 42%、>12h 占 38%——这条重尾就是日报里那批标 expired_at_claim 被跳过没触达的 lead。按 source tier 拆开,真因和直觉相反

source tiern>6h 慢尾占比median 时延
tier-0(pri=5 每小时)617%4 分 ← 居然最快
tier-1(pri≤3 快车道)11042%34 分
无源(孤儿视频)863%99 时
样本量诚实说明:这是「近 36h」滑动窗口快照(隔几小时重测会 ±2分/±2% 自然漂移,结构稳定)。⚠️ tier-0(n=6)、无源(n=8) 样本太小,那两行的百分比只能当方向参考、别当硬结论;统计上扎实的只有 tier-1(n=110) 的 40–42% >6h 慢尾。即「慢尾主要压在 tier-1 老视频」这个结论可靠,但「tier-0 比 tier-1 快」需更大样本再坐实(方向上成立)。
慢尾压根不在 pri=5 那条 12h 积压道上(tier-0 中位才 4 分)。42% 的慢尾全在 tier-1 的老视频(龄 6–11 天、时延 6h~7 天)。根因:tier-1 整号活只重扫该号最近 ≤20 条视频,一条视频过几天就被挤出"最近 20"、从此不再被定期重扫;但它底下还在冒新评论,这些评论进不了任何快车道(快车道要求"该视频 48h 内已有评论入库",老视频没有)→ 鸡生蛋升级版。直到某次慢节奏重扫终于扫到,增量一次性把积压几小时~几天的评论全捞回。漏洞在"过去那段没扫",不在排队、也不在窗口往回看多久。

F · 解决方案:4 条路,现在各跑到哪了

E 段那条"38% 老视频慢尾"怎么治?验证过 4 个方向,下面逐个说人话、再标现在的真实状态(不是计划、是查过库的实况):

把快车道的 48 小时窗口拉大到 72 / 96 小时  →  不做(已否决)

什么意思:快车道只盯"近 48 小时内冒过新评论"的视频。直觉会想:那把 48 拉到 96,不就把老视频也圈进来了?
为什么不做:模拟过——窗口拉到 96h,候选池涨 46%,但多出来的全是已经冷下去的老视频(大半还被退避挡在外面),真正救回的慢尾只有 0–1 条。窗口管的是"往回看多久",可这些视频的病是"过去那段压根没人去扫",看得再远也没用。方向错,不做。

给"老的高意向视频"单开一条低频回扫  →  ✅ 已上线在跑,最对症

什么意思:专门挑那些"以前出过高/中意向、但现在掉出了 48h 快车道"的老视频(数量不多,是 Pareto 钓点),单独给它们排一条慢班车——每条视频最多 2 小时扫一次,把"几小时~7 天才被扫到"的盲区压成"保底 2 小时一扫"。
现在跑得怎么样:已上线(PR #377,cron /api/cron/cold-intent-rescan,每 20 分钟一轮,走 pri=3)。6-17 首份快照实测:它的空跑率 65%,是所有回扫通道里最低的——反过来说 35% 的轮次真抓到了新评论,是 4 条通道里唯一"打中"的,对症。代价:它走 pri=3,把 pri3 积压从 96 顶到了 384(所以下面 ④ 变急)。

修"孤儿视频"  →  暂没单独做(② 可能已顺手覆盖)

什么意思:库里有一批视频认不出是谁发的source_account_id 是空的),任何"按号抓"的活都覆盖不到它们,只能烂在那——E 段那 8 个孤儿视频中位慢到 99 小时就是这种。
现在状态:没单独动手。因为 ② 是"按视频"撒网、不按号,孤儿视频天然就被 ② 网进去了 → 先看 ② 跑一段够不够,不够再单独按作者把来源回填上。

pri 防饿死(治另一个病:tier-0 被饿死、积压 12 小时)  →  6-19 复诊降级 · 留观(暂不做)

什么意思:worker 严格"小号牌先领",pri=5 的 tier-0 活只在前面全空时才轮到 → 能积压 12 小时没人碰。两个治法:aging(一个活等太久就自动升号牌往前插)或配额(每批留 X% 名额给 pri≥5,不让它彻底饿死)。
为什么变急:② 那条 cold-intent 走 pri=3 上线后,又往 tier-0 头上压了一层——6-17 快照实锤 pri3 积压 96→384、tier-0 最老活仍卡 12 小时。所以 ④ 从"以后再说"提到了"该上了";要是 ④ 一时上不了,就得先把 ② 的 COLD_INTENT_RESCAN_MAX 调小、给 tier-0 泄压。
✅ 6-19 复诊更正:以上是 6-17 快照。6-19 复跑 36h 实测——tier-0 已被 aging 压到 p99 1.1h / max 13.4h(8 条)、cold-intent pri=3 max 2.0h / 0 条超 6h、pending 仅 15 条,积压基本治住。④ 从「该上了」降级为「留观」,pri RPC 暂不做——详见上方 D 段 6-19 复诊框。

一句话推进结论:Leg C 把典型延迟砍了 2/3(median −67%)、但极端尾巴(p99)被破鸡生蛋注入洪水拖坏 → 头号补丁是注入去重;真尾巴的下一刀 = ② 已上线在跑(65% 空跑率、最对症)+ ④ pri 防饿死经 6-19 复诊降级为留观(尾巴在 ≤2h 容差内、tier-0 已不卡 12h、leads 没掉),而不是去改那个 48h 窗口、也不急着写 pri RPC。

⑩ 5 天回顾:架构订正 + 每刀 PR 复盘(2026-06-20)

这一节干三件事。 ⓪ 先给一张「五步现状速览」——把 ② 段那张 6-15 的五段表,按 5 天后各段实际走到哪逐段更新一遍(最快看清"现在还堵在哪");① 把一份在内部流传的「抓取流程梳理」里的一处硬错纠回来(它把已淘汰的旧 worker 架构当成现状,会带歪"堵在哪"的判断);② 把 6-15 → 6-19 这 5 天每一刀改动按「为什么动 → 动了什么 → 效果」串成时间线——这 5 天基本就是一条主线:「评论抓得越快越好」→ 加速 → 加速踩到副作用(饿死 / 注入洪水 / 漏抓)→ 一刀刀回补

⓪ 五步现状速览:② 段那张五段表,5 天后各自走到哪了

② 段是 6-15 的拍照,当时 Leg B / Leg C 是两个大头。这 5 天的 7 刀 PR 基本都砸在这两段。下表把每一段的「原本(6-15) → 现在(6-20)」摆一起对比,再标出每段的物理地板——理论上还能压到多快、靠什么压。B、C 怎么联动、各自现在排多久,见下方 B↔C 详解。

段 · 这一段在干嘛(人话)原本 6-15现在 6-20物理地板:还能压到多少 · 怎么压
Leg A 上架
评论在抖音侧能被查到
~几秒 ~几秒 · 稳 就是抖音侧那几秒延迟,我们碰不着。已在地板,无可压。
Leg B 入队
等系统派个活去抓这条视频
冷视频
几十分~几小时
中位 29min
热视频几分钟
35% 仍 >6h
地板 ≈ 1 个扫描周期(2 分钟)——再密就撞抖音反爬红线(每号约 67 次请求/分)。热视频已到地板;要把冷/老视频也压到分钟级,靠的是扩覆盖(#377 老钓点回扫 + 收 cold-intent 口径让更多活钓点被选中),不是再调快闹钟。
Leg C 排队
活派进队列、等 worker 领走
中位 4.6 分 快道 ≤3min
(秒清)
tier-0 13h
地板 = 亚秒~数秒(队列空 + 有空工位时)。高/中意向走的快道已到地板;tier-0 那 13h 是产能不够(aging 只把它兜在 <24h、不凭空造产能),不是排队算法的锅,要压只能加工位。
Leg D 抓取
worker 去视频底下把评论捞回来
~5 秒 ~5 秒
(#413 修了 33% 漏抓)
1 个请求 + 抓到首页即早停,~5 秒已是地板
Leg E 写库
写进我们库
亚秒 亚秒 · 稳 从不是瓶颈,已在地板。
整条链路的物理地板 ≈ 3 分钟。 一条已经在快车道上的热视频:上架几秒 + 入队 1 个 2 分钟周期 + 排队秒级 + 抓取 5 秒 + 写库亚秒。再快就撞抖音反爬红线(每号约 67 次请求/分)——这是硬墙,不是工程没做到位。 现在高/中意向中位 29 分钟、离这 3 分钟地板的差距,几乎全卡在 Leg B 的「冷/老视频覆盖盲区」:这些视频不是被抓得慢,是压根没被任何触发器选中去派活。所以下一刀方向是「扩覆盖」(让更多老钓点被选中去抓),不是「再提速」。

B 和 C 怎么联动:B 发"几号牌",C 决定"这号牌排多久"

这俩老被混在一起,其实是一前一后两件事

Leg B 家族:8 个「入队触发器」(不止快照②那 4 个)

Leg B =「决定何时把哪条视频/号丢进队列」的所有触发器,分三支(均查代码 + vercel.json 坐实)。看表前先花 30 秒把术语换成人话:

这一节会用到的词,先讲人话:
支系成员抓谁 · 怎么触发进 pri在快照②
① 整号轮抓
scrape_source
去主页捞 ≤20 条视频逐条抓
tier-0 整号/scrape:每 1h 挑 150 个最久没抓的普通号5
tier-1 整号/scrape-hot:每 2-3min 扫优质号(#363 从 pri=1 降到 3)3
② 单视频复扫
scrape_video
只抓一条视频的增量
fast-lane 常规/scrape-hot:top100 热视频(近 48h 有新鲜评论)1
fast-lane 高意向注入/scrape-hot:近 6h 冒高/中意向的视频强行插队(#363 破鸡生蛋)0
vip-rescan/vip-video-rescan:VIP 视频(发布 ≤4d 的高价值视频)每小时增量复扫 ⚠️ 它的"货源"VIP 名单由 hot-video-rotation 物化,而后者近乎停摆(见下)→ vip-rescan 实际也在断粮0
cold-intent-rescan/cold-intent-rescan:近 14d 还冒意向的老钓点(>4d,#377)3
rescan-recent-videos/rescan-recent-videos:发布 <7d 的视频按 7d 全窗口复扫 ⚠️ 已确认没开(2026-06-21 查生产库):env 未设 =1,cron 到点即返回 disabled,近 7d 入队 0 条3
hot-video-rotation/hot-video-rotation:>7d 但近 14d 还活的历史爆款,7d 全窗口 ⚠️ 近乎停摆(2026-06-21 查):最近一条 6-17 且 failed、近 7d 入队 0 条5
③ lead 资料
scrape_lead_profile
scrape_lead_profile/analyze-once:给高意向用户抓主页资料5
为什么快照②只剩 4 个(人话):那张表只统计"单视频复扫"这一支,而且只给身上贴了来源小标签的 4 类单独列行。rescan-recent/hot-video-rotation 虽然也是单视频复扫、但没贴这种标签,整号轮抓和 lead 资料又是另一种活——所以都没进那张表。一句话:快照只看到 4 个 ≠ Leg B 只有 4 个。

Leg C 家族:没有独立触发器,就是上面 8 个活按 pri 排成 4 条道

Leg C 是一条队列,它没有自己的"触发器"——"家族"就是 Leg B 那 8 个活落进来后、按号牌(pri)分成的 4 条道。worker 严格按号牌从小到大领(同号牌先到先得)。各道现在排多久=6-19 自动快照:

pri 档这道里装的活(全来自 Leg B)C 段现在排多久谁卡这档
pri=0
最急
高意向注入 + vip-rescan + 手动秒清抢救冷视频上刚冒的高意向新评论
pri=1
快道
fast-lane 常规(单视频快活,~5 秒)pending 39 / 最老 3 分钟热视频(基本不卡)
pri=3
中道
tier-1 整号 + cold-intent + rescan-recent400 条 / 最老 57 分钟tier-1 优质号 + 老钓点 + <7d 新视频
pri=5
慢道
tier-0 整号 + hot-video-rotation(7d 全窗) + lead 资料278 条 / 最老 13 小时tier-0 冷号(被 #357 aging 按在 <24h、没饿死)
外加 Leg C 的领活机制本身(也属这族):worker 8 槽 task-per-slot(每槽独立"抢 1→干完→再抢 1")+ #357 aging 安全网(活等久了自动提权、封顶追平 tier-1,防 tier-0 饿死)。
关键一刀 #363:把又慢又重的 tier-1 整号活从 pri=1 降到 pri=3——pri=1 快道只剩 5 秒级单视频快活,才从原来积压上百条/排 4.6 分压到最老只排 3 分钟。代价是 tier-1 整号挪到 pri=3 排 ~57 分钟,但整号活本不赶时间(高意向新评论已被 pri=0 抢救走)。
一句话收口两族关系Leg B 发牌(8 个触发器决定谁进队、贴几号牌)→ Leg C 叫号(同一套 pri 排成 4 条道领走)。同一套 pri,B 是发牌、C 是排队兑现。
B、C 现状各一句(带具体时间): 以上会随时间漂,最新数字以 自动数据快照页为准。

这些 pri 各级,现在要不要调?(2026-06-21 评估)

结论先行:现在不必调 pri 各级——因为当前卡点不在"排队优先级",调 pri 治不了真问题。
关键认知:pri 只在队列堵的时候才起作用。 现在 pri=0/1 秒清、pri=3 排 57min、pri=5 排 13h 且被 aging 兜在 <24h——队列没失控。而唯一还明显开口的 35% >6h 慢尾,根因是「老视频覆盖盲区」=这些视频压根没被任何触发器选中去派活(cold-intent 代码注释和 ⑨-E 都写死了),不是"派了活排太久"。「没派活」是 Leg B 覆盖问题,调 Leg C 的 pri 改变不了「没派活」。
现状要不要调为什么 / 若硬调的预期
tier-0 整号
pri=5
排 13h不调aging 已兜底(13h<24h、符合设计);tier-0 本就少产高意向。提 pri → 抽走产能稀释高意向源、回吐 #346/#363 加速,高意向 median 反升。
tier-1 整号
pri=3
排 57min不调#363 刚把它 1→3 才让快道秒清;整号活不赶时间(高意向已被 pri=0 抢走)。提回 pri=1 → 慢整号活重新堵 fast-lane,退回 #363 之前的病。
fast-lane 常规/注入
pri=1 / 0
秒清不调已是最优、无积压,没有可改善空间。
cold-intent-rescan
pri=3
排 57min、99% 空跑不调 pri
(但要收口径)
它是治慢尾的独苗,但慢尾瓶颈是「14d 回扫周期 + 120min/视频限频 + 钓点枯」,不是那 57min 排队。且 99% 空跑——提 pri = 让一堆空跑活插队挤掉有用活,纯负收益
vip / hot-rotation / rescan-recent断粮 / 停摆 / 没开pri 无意义它们根本不产活,pri 是"僵尸数字"。真问题是要不要复活,不是改它排第几。
真正该动的不是 pri,是这两件(按性价比排):
对账一份内部报告(2026-06-21):有一份本地梳理称「整号扫描被快视频100% 饿死、近 6h 完成 0」、据此要给 claim 加保底配额当急症修。实测推翻了那个「0」——直查生产库 scrape_source 整号 completed 近 1h=140 / 近 6h≈789(~131/h) / 近 24h=3522,最近一条几秒前才完成,整号在持续出活、没归零。那个「0」是 count 全表扫间歇超时返回空(即本页 ⑩-C 口径修正同款坑,重试即得真值)。真相=整号有 ~9.9h 积压尾巴、但在 tier-0 普通号(pri=5)上、被 #357 aging 兜在 <24h,tier-1 优质号整号(pri=3)是新鲜的——是已知长尾、非急症。所以"保底配额"解的是被夸大的问题;更对症的先手仍是收 cold-intent 口径(顺带给同档 tier-1 整号让路)。
预期效果对比(一句话)
· 维持现状不调 pri → 队列继续健康(秒清 + aging 兜底),慢尾不变(慢尾本就不归 pri 管)。
· 错误地调 pri(提 cold-intent / tier-0) → 大概率让高意向 median 回升、慢尾几乎不动,净负
· 改去收 cold-intent 口径 + 复活 hot-rotation → 直击慢尾根因,预期能把 35% 的 >6h 慢尾往下压(幅度要灰度后看,不敢拍数)。

A · 一处硬错:worker 早就不是「一批一起回」了

错的说法:「_run_jobasyncio.gather 一次并发抢 3 个活,一批一起返回;只要 3 个执行槽被慢整号活占满,新抢回来的快活就在槽外干等」。
实际代码(worker/services/queue.py:早已改成 task-per-slot(每槽独立循环)——_poller_loop 起 N 个互不相干的 _slot_worker,每个槽自己跑「抢 1 个 → 干完 → 再抢 1 个」,没有"一批一起回"的栅栏_run_job 只跑一个活,外面套 asyncio.wait_for(deadline=1500s) 兜死活。gather 只出现在一个整号活内部并发抓它那几条视频时(信号量=2)。

为什么这个订正重要:旧 gather 架构下,"一个慢活拖死整批"是真的(p99 曾到 1253s)——这正是当时改架构的原因。但现在不成立了:一个慢整号活只占它自己那一个槽,兄弟槽照样不停抢快活。真正会堵,只发生在「所有槽同时被慢整号活占满」那一刻,新来的快活才得等空槽。

所以「堵在 Leg C」的真因要精确说:不是"慢活冻结整批",而是 ① pri=1 快道里常年积压上百条(快车道每 tick 灌一大批,新活按同级先到先得排后面)+ ② 慢整号活混在同条道里、零星占满槽#363 的「快慢分道」(tier-1 整号 pri 1→3)就是对②的根治——把慢活降到快活之下,保证只要有槽空出来永远先领快活,把"全槽被慢活占满"的概率压到最低。订正后结论不变(仍该分道),但机制叙事得对,别再说"gather 一批一起冻住"。
常被说错实际(现状)
poller 架构gather 一批抢、一起回task-per-slot:每槽独立 claim-run 循环
名义并发36fly scale count=2 × WORKER_CONCURRENCY=3,代码默认值才是 3;洪水下 Fly 还会临时多起机器)
单活兜底(没提)wall-clock 25 分(1500s) 超时强杀 + 重启浏览器;僵尸 8 分打回 pending
claim 排序priority ASCpriority 减 aging 提权(见 B)

B · claim 已经不是「纯小号牌先领」——6-15 上了 aging 安全网(#357)

另一处常被漏掉的现状:worker 抢活的 claim_pending_scrape_jobs_batch 早已不是严格 priority ASC。6-15 的 #357 给它加了一层 aging 公平性安全网,专治 ⑤/⑨ 反复出现的 tier-0 被饿死:

机制(人话):一个活等得越久,"有效号牌"越往前提,但封顶只追平 tier-1(pri=1)、不越级抢热源新鲜度。公式 effective = priority − LEAST(等待小时 / 6, priority−1),env QUEUE_CLAIM_AGING_HOURS=6 可热调免发 migration。
刻意设保守:tier-0(pri=5)要饿满 ~24 小时(4×6h)才追平 tier-1,所以它只兜底"饿超一天的长尾号",不和同事当天的加速决策(#346/#363)对冲
6-19 实测它在按预期工作:tier-0 最老 pending 压到 13h(<24h,安全网还没满档触发,符合设计)、pending 仅 278 条——对比 5 月那次 52k / 11.5k 积压、#357 上线前"150 条最老等 17-19h、每号 4.7 天才转一圈",已经健康得多。这就是 ⑨-F 那条「④ pri 防饿死从『该上了』降级为『留观』」的底气:aging 安全网先把急性饿死按住了,pri RPC 暂不写。

C · 6-19 数据快照对账(数字看自动快照页,这里只给结论)

会随时间漂的数字(时延分布 / 空跑率 / pri 积压)以自动数据快照页为准。三个 headline 结论:

6-19 快照怎么读
① 时延(评论→入库,高/中意向)全部中位 29 分,但均值 34h、>6h 慢尾 35%(最慢 165h)一半 ~29 分内抓到(健康);尾巴被「老·常青钓点」拖爆——正是 ⑨-E 那条 38% 真尾巴,#377 在追但没追平。
② 空跑率fast-lane 常规 95% / 高意向注入 88% / cold-intent 99% / vip-rescan 99%退避(#366/#381)省的是"再入次数",绝对空跑率仍极高——lookback 窗口里大量"冒过一波就安静"的视频。这是当前最大、尚未根治的浪费(B 方案待灰度)。⚠️ cold-intent 99% 比 6-17 的 65% 命中恶化,钓点在枯,口径该收。
③ pri 积压(合计 717)pri=1 pending 39 / 最老 3min;pri=3 400 / 57min;pri=5(tier-0)278 / 最老 13h;pri=0 秒清高优先档(0/1)基本秒清;唯一真积压在 tier-0,正是 aging 安全网兜底那条尾巴(见 B)。
口径修正记一笔(#328…6-19):② 段空跑率之前会被 scrape_video_comments 自插的「台账行」(result={}、计数记在恒 0 的 comments_found 列)撑成假 100%。6-19 已剔除台账行 + 改行拉取避开 count 全表扫超时——所以现在 88–99% 是真实空跑率,不是口径假象。

D · 最近 5 天每一刀 PR:为什么动 · 动了什么 · 效果

#346
提速第一刀:把闹钟敲密、把冷却缩短(6-15)· 为什么:运营要「评论越快抓到越好」,最先想到两个提速口子——抓取闹钟太疏、同一个号重抓同一条视频要等太久。
动了什么:① 热门视频的扫描闹钟从「每 3 分钟一次」提到「每 2 分钟一次」;② 同号重抓同视频的冷却从 45 分钟缩到 30 分钟。两项都压在抖音反爬红线(每号约 67 次请求/分)以下,这步实际只用到约 40 次/分,安全。
延迟下压
失败率 ~0%
#357
给「排太久」的活加一条自动插队(防普通号被饿死)(6-15)· 为什么:worker 原本「谁优先级数字小谁先抓」,结果优质号(tier-1)把抓取产能全占了——1019 个普通号(tier-0)24 小时只抓完 227 个、有 150 个排得最久的等了 17-19 小时、每个普通号平均 4.7 天才轮到一次,等于被饿死。
动了什么:抢活规则加一条「排得越久、优先级自动越往上抬」(aging)——一个活在队列里等得越久就越提权,等满约 24 小时就追平优质号。提权速度随时可调。
普通号防归零
不抢热门号
#363
冷视频强行插队 + 快活慢活分开排(6-16)· 为什么:① 一条视频要「曾在 60 分钟内被新鲜抓到过一次」才够格进快车道,可冷视频永远走慢路、永远赶不上这 60 分钟门槛 → 永远进不了快车道 → 它底下的高意向评论第一条几乎必慢(鸡生蛋死循环);② 抓「整个号」的慢活(中位 23 秒)和抓「单条视频」的快活(5.8 秒)挤在同一条队列,前面卡一个慢的、后面快的全得干等。
动了什么:① 只要一条视频近 6 小时冒出高/中意向评论,就强行插到队列最前,不等它自己够 60 分钟门槛;② 把「抓整个号」的优先级往后挪一档给快活让道;③ 扫描闹钟从每 2 分钟调回每 3 分钟、砍掉空跑。
典型延迟 −67%
(226→75 秒)
#366
没新评论的视频别再反复白扫(6-16)· 为什么:近 24 小时 88,455 次重扫,77% 一条新评论都没抓到(空跑)。安静的视频被每 3 分钟反复白扫一遍,白占 worker 工位、堵住队列。
动了什么:一条视频连着 3 次抓到 0 新评论,就把它的重扫间隔翻倍(9→18→30 分钟封顶);一旦抓到新评论立刻清零、恢复高频。
省 ~77%
无效复扫
#377
专门回扫「老钓点」(曾产意向但太久没扫的老视频)(6-16)· 为什么:真正拖慢的尾巴是「老视频覆盖盲区」——2874 个曾产过高意向评论、且仍存活的视频,全都超过 4 天没被扫过,快车道和 VIP 复扫都覆盖不到,可它们底下还在冒新评论。
动了什么:新开一个闹钟(每 20 分钟跑一次)专挑「近 14 天还在冒意向的活钓点」回扫,每条视频最多 120 分钟扫一次、每轮最多 300 条。
6-17 命中 35%
(最对症)
#381
给「强行插队」也套上退避刹车(之前放太猛成了洪水)(6-17)· 为什么:#363 那个「冒高意向就强行插队」放太猛、变成洪水——112 个老视频每 6 小时被重复插队约 80 次、可这些插队抓到新评论的概率是 0.0%,把最慢那批(p99)从 20 分钟拖到 80 分钟(翻 4 倍)。
动了什么:取消 #366 给「插队活」开的退避豁免——让这些空跑的插队活也照样记账、也照样被退避挡住。
插队量大降
最慢批回落
#413
修「翻评论提前收手、漏掉深处新 lead」(6-18)· 为什么:运营反馈「中高意向 leads 变少了」。抖音网页版评论接口默认按热度排(不是按时间倒序),一堆又热又老的评论挤满第一页,让我们「翻到全是老评论就停」的逻辑提前收手、漏掉藏在深处的新 lead,要晚 50~553 分钟才补抓到
动了什么:把停止条件从「翻到 1 页全老就停」改成「连续 2 页全老才停」,代价只是每次多翻约 1 页。
找回被漏
的 lead
#328…
修报告自己的统计口径(数字算错了)(6-19)· 为什么:② 段算出的「空跑率」被一类恒为 0 的台账行撑成了假 100%,加上全表 count 扫描超时,数字不可信。
动了什么:算的时候剔掉这些台账行、并改用逐行拉取避开超时。(配套把报告生成器自动化 + 做成 /update-scrape-report 一键命令。)
②段数字
变可信
建议对账(给那份内部梳理):① 升 f2 仍有效——但 f2==0.0.1.7 硬 pin httpx==0.27.2,升级必须协调 httpx 兼容,且只剩 3 个采集号逼近 24h 封号红线,dev 灰度务必盯每号 API 量。② 「解耦 pri 与 tier / 新建 pri=2」大半已落地:快慢分道(#363,tier-1 整号已 pri=3)+ 老视频视频级回扫(#377,cold-intent pri=3)都做了,现有 0/1/3/5 四档已把快慢分开,不必再新建 pri=2。③ 查 RPC 装没装不必连库:③ 段能跑出按 pri 分桶就是 RPC 在线的证据(否则 fallback legacy 单活路径、跑不出分桶)。
一句话现状(6-20):5 天把「加速 → 副作用 → 回补」这条主线基本跑完——median 砍 2/3、p99 注入洪水已治、漏抓 lead 已修、tier-0 饿死已被 aging 按住仍开口的两件事:① ② 段 88–99% 的绝对空跑率(退避只省再入次数、没降绝对值,B 方案待灰度);② ① 段被老钓点拖出的 35% >6h 慢尾(#377 在追,但 cold-intent 6-19 已 99% 空跑、命中从 65% 掉到 1%,钓点在枯、口径要收)。

E · 「收 cold-intent 口径」深挖:数据 → 人话 → 追问 → 方案(2026-06-22)

承接 ⑩-C/D 那条「cold-intent 99% 空跑、钓点在枯、口径要收」。这一节把它挖到能动手:先用数据证明空跑长什么样,再用钓鱼比方把机制讲透,回答一个关键追问——这么改会不会把高/中意向抓漏 or 抓慢,最后给带护栏的退避方案(已出交接、待开 PR)。
读这章前先立 4 个词(之前没定义、最容易把人绕晕):
· 钓点 = 一条视频。cold-intent 选谁回扫,只看"这条视频近 14 天还冒过高/中意向评论"——不是号源,也跟 tier0 / tier1 无关(tier 是号源的属性,是另一条轴)。
· 「个」= 视频数,「次」= 扫描动作数。一条视频(1 个)会被反复扫很多次:1070 钓点一共被甩了 13074 。下表左右单位不同就是这个意思。
· 退避 ≠ cold-intent。cold-intent 是"低频回扫老视频"这件事本身(早就有);退避是 2026-06-22 才加在它上面的一层省钱规则——专门拉长"老是空手"的钓点的回扫间隔。所以下文「退避前」= 还没装这层时、所有钓点死活都固定 2h 扫一遍。
· 名字坑:cold-intent 的 cold 指"视频凉了"(老视频、掉出 48h 热窗口),不是"意向凉"——它盯的恰恰是冒过高/中意向的视频。英文名跟它干的事不透明对应,知道这点才不会读反。

① 数据:97% 空跑不是均匀的,是一小撮死钓点在被反复空甩

直查生产 scrape_jobs(近 48h,2026-06-21):

指标数值读法
cold-intent 完成回扫13074 次落在 1070 个钓点(实际量 ~6500/天;前一查被 PostgREST 默认 1000 行截断过,已分页拉全)
零命中钓点(48h 一条新评论没抓到)947 / 1070(89%)它们白扫了 11874 次 = 91% 的活
 其中被反复扫 10+ 次的683 个浪费高度集中在这批死钓点 ← 退避的靶子
真正有产出的活钓点123 个自身命中率也才 28%(每 4 竿中 1 条),但"抓到即清零"能保它们高频
退避可省(连续 2 次空跑起退)77% 的活K=3 起退省 70%
一句话:97% 空跑 = 89% 的死钓点 × 每个被扫 10+ 次。退避能精准干掉这 77%、同时一根不碰那 123 个活钓点。

② 人话:用「钓鱼」把机制讲透(苏格拉底式自问)

③ 追问:现在的方法,会不会让高/中意向抓不到 or 抓得慢?

抓不到?抓得慢?
现在(退避前)对它管的钓点基本不会——1378 钓点每 2h 该扫一遍 = 需 689 竿/h,产能 900 竿/h,够用、不会被 300 上限漏掉~2h 地板:每钓点 2h 才回去一次,最坏等 ~2h(+ 排队 57min)
真正"抓不到"的另一类一条全新冷视频还没冒过意向、没进 14d 清单 → cold-intent 不认识它,只能等「整号轮抓」扫到。但这是整号的活、非 cold-intent 的锅——它天生是"你先冒过一次鱼、我才把你列进钓点"的事后补扫。
退避后的新风险会让"先死后活"的钓点第一条新鱼更慢:一个退避到"8h 一去"的钓点突然来高意向,最坏等 8h 才被发现——比现在的 2h 还慢;若封顶设 24h 更糟,等于亲手拉长慢尾、跟目标反着来
所以这一追问直接逼出两道护栏(保高/中意向不被退避误伤):
· 退避封顶压到 6h(不是 24h):最坏一个复活钓点等 6h,正好卡慢尾边界、不会比现状更糟。
· 高意向钓点给地板 2h:曾冒过高意向(非仅中意向)的钓点封顶压到 ~2h(≈现状、几乎不受退避影响);只有"光中意向"的钓点才允许退避到 6h。
净效果:高意向几乎不受影响(地板 2h≈现状),中意向最坏 6h(≤现有慢尾),同时砍掉大头浪费。退避治的是"在死钓点上空耗",不拿"误了真鱼"去换。

④ 方案:带护栏的退避(2 改动面、worker 不动;已出交接、待开 PR)

改动面动什么
migrationvideoscold_intent_empty_streak + next_cold_intent_at 两列(与 #366 的 empty_streak/next_fast_lane_at 分开,语义隔离)
cron cold-intent-rescan/route.ts每 tick 先结算上轮:0 新→streak++、间隔×2(封顶 6h,高意向钓点地板 2h);有新→清零、复位 120min。选 due 钓点改用 next_cold_intent_atworker 不碰(cron 自结算,比 #366 由 worker 写更省)。
预期效果(按数据推):cold-intent 入队 ~6500/天 → 砍 ~77% → ~1500/天;pri=3 松绑、tier-1 整号排更快;活钓点回扫更密 → 慢尾改善。诚实边界:慢尾下压幅度要灰度后看、不敢拍死数(退避治浪费、非凭空加速)。
实施约束supabase/migrations/** + src/app/api/cron/** 都是高危路径,必须开分支 + CI + claude-review,禁直推 main。交接定稿见 repo docs/handoffs/2026-06-22-cold-intent-backoff.md
方法注记:所有数字为 2026-06-15 当日直查生产库(comments / scrape_jobs / source_accounts)+ worker 活体 /queue/stats + Lark 采集快照卡。诚实边界:① 每日 tier 数变化库里无历史表、不可严格重建,本页用当前快照近似;② Leg C 的「8 槽悖论」已于 23:00 实查坐实=不是闲置,是 pri=1 队列常年积压 ~100 条 + 慢整号活占槽 + poison 活(见 Leg C);fly machines API 因 deploy token 无读权限未取到机器数,但 running 常态 8 + 78/分吞吐,有效并发 ~8;③ 僵尸判定用 7 天 / 高意向≥5(对齐升级线)。
⑩ 段(架构订正 + 5 天 PR 复盘)为 2026-06-20 追加,数字取自 6-19 自动数据快照 + worker/services/queue.py 现状代码 + 各 PR body。
⑩-E(收 cold-intent 口径深挖)为 2026-06-22 追加,数字取自当日直查生产 scrape_jobs 近 48h 聚合;退避方案为设计稿、尚未上线(交接见 repo docs/handoffs/2026-06-22-cold-intent-backoff.md)。
2026-06-23 加开篇 4 词词典(钓点=视频 / 个=视频数·次=扫描数 / 退避≠cold-intent / cold=视频凉非意向凉)+ tier 那处加"是排队轴非扫描轴"澄清;仅补释义、数据未改
Akke 内部调查 · Claude Code 协助 · 2026-06-15(⑩ 2026-06-20 续 · ⑩-E 2026-06-22 续)