跳转至

ISSUE-0074:KPI/sidebar 计数 0 邮箱 — 50k 窗口家族 bug 复发

补建说明:本 ISSUE v0.10.112 时已撰写但 commit 时被「批量删 untracked 中文文件」脚本误删(详见 ISSUE-0079 元 bug)。v0.10.117 补建。

现象

用户截图 v0.10.109 主面板:

KPI 显示值 实际值(导出日志验证)
商家总数 187.1k 187.1k ✓
官网采集进度 0 / 50.0k ContactPool 14,812 条;mstage:success 1105 次
邮箱总数 0 pageLog 660 个 success 含 emails;877 独立邮箱
手机总数 18.7k 18.7k ✓(地图首页字段,本来就准)

抓取真在跑 7.3 小时,660 个网站采到了邮箱 — 但 KPI 显示 0,用户以为产品坏了。

根因

src/utils/data-counts.ts:46 (v0.10.111 前):

const ROWS_CAP = 50000;
const rows = await selectByQuery('MapTaskData', {
  limit: ROWS_CAP,
  order: { by: 'id', type: 'desc' },          // ← 按 id 倒序
});
for (const r of rows) {
  for (const e of r.emails || []) emailSeen.add(e);  // 只统计窗口内
  if (r.scrape_status === 2) scrapeDone++;
}

用户场景:187k 商家中: - 最新 50k 行:用户最近创建任务时一次性 push 入的,scrape_status=0 + emails=[](待采集) - 真采过 emails 的 660 行:id 较小,在 50k 窗口之外

按 id desc 取 top 50k → 窗口被新建任务挤满 → 已采数据全被剔除 → 邮箱去重 0、scrapeDone 0。

src/sections/data/data-view.tsx:62 (CAP = 50000) 同款 bug —— sidebar 邮箱/电话/官网去重列表也都被截。同时这个 5w 行/30s polling 是 187k 商家时商家列表卡顿主因之一

历史

v0.9.35 当时把窗口从 2000 提到 50000,注释写明:"50000 可覆盖到中量级用户全量数据"。 用户 187k 已远超中量级,窗口被穿透。

同类历史: - ISSUE-0018:hasEmailOnly 全表 50k 上限静默截断 - ISSUE-0050:商家列表 chip count 截断 50k - ISSUE-0051:商家列表卡顿 — rows polling 5s

修复(v0.10.112)

data-counts.ts

  1. 三个总数改用 countByQuery(瞬间精确,不受 limit 限制):

    merchant = countByQuery('MapTaskData', {})
    scrapeDone = countByQuery('MapTaskData', { scrape_status: 2 })
    scrapePending = countByQuery('MapTaskData', { scrape_status: 0 })
    

  2. 去重数改"按 status 分窗扫"

  3. where: { scrape_status: 2 } 限 50k —— 这些行才有 emails,且实际产出 ≤ 5w
  4. where: { scrape_status: 0 } 限 50k —— 补充 phone(地图带的,status=0 时也有)

base.ts scrape_status: enableSearch:true 有索引,where 查询走索引快。

data-view.tsx

顶层 useRequest 同样改成"status=2 + status=0" 分窗拼接(最多 10w 行),不再被新建任务挤丢已采数据。

通用模式(写进 rule v0.10.113)

任何 select limit + order by id desc 派生计数都是 50k 窗口家族的隐性 bug: - 新行 push 时窗口被占满 → 老数据被无声丢弃 - 解法:能用 countByQuery 走 count 就走;必须扫行的,先 where { 关键状态字段 } 缩小集合

详见 count-vs-scan-window,v0.10.113 起 pre-commit hook 强制扫描。

验证

  • KPI 邮箱/scrapeDone 立即恢复真实值
  • 商家列表 30s polling 仍拉 10w 行(status=2 + status=0),但分两次 5w,去掉了"全表 desc 排序"的 IO 放大
  • 邮箱列表 / 电话列表去重数恢复

教训

  • 每个新引入的 limit N 注释里要写"超出 N 后会发生什么"
  • v0.9.35 注释只说"50000 可覆盖中量级",但没说"超出时会丢什么、怎么发现"
  • 同类问题在 ISSUE-0018/0050/0051 都修过一次但不彻底
  • 加 docs/rules:扫描类计数优先用 countByQuery + where 索引字段
  • 本 ISSUE 文件 v0.10.112 时漏 commit(详见 ISSUE-0079)—— 用户审计才发现,进一步触发 meta-bug rule 沉淀