Skip to content

obs(billing): dedupe anomaly alerts across instances - #366

Merged
oratis merged 1 commit into
mainfrom
claude/meter-anomaly-shared-dedup
Aug 21, 2026
Merged

obs(billing): dedupe anomaly alerts across instances#366
oratis merged 1 commit into
mainfrom
claude/meter-anomaly-shared-dedup

Conversation

@oratis

@oratis oratis commented Aug 21, 2026

Copy link
Copy Markdown
Owner

GCP 打点收尾的最后一个代码项(#363#364 → 本 PR)。

问题

>$10/天/用户 的异常消费告警靠一个进程内 Set 去重:

const alerted = new Set<string>();   // 每个实例各一份

MAX_INSTANCES=3 时同一天会告警最多 3 次,而且冷启动就重新武装 —— 一次流量波动导致的多实例扩容,运维会收到一串重复推送。

做法

Set 保留为进程内快速路径;跨实例的仲裁改成 lisa-anomaly-alerts/{uid}_{day} 上的 create-only 写setDoc(..., { exists: false }))——第二个写入者的 precondition 失败就是去重信号。这个模式直接沿用 iap.ts 已有的全局 transactionId 索引(同一个 firestore.ts 原语)。

刻意 fail-open:只有 409/412 才判定为"别人已认领"。其它任何失败(网络、权限、Firestore 挂了)一律照常告警 —— 重复告警只是噪音,漏告警是一笔没人注意到的持续烧钱。

Firestore 关闭或不在 per-uid scope 内(Mac 版、共享 token demo)时 claim 是 no-op,行为完全不变 —— 有两个测试断言这两种情况下网络一次都不会被碰。

顺带收尾本文件的日志债

告警不会因此失效(已核实):策略匹配的是 [billing] ⚠ anomaly 前缀,未改动;且该策略的过滤串已是双写法——

(textPayload:"[billing] ⚠ anomaly" OR jsonPayload.message:"[billing] ⚠ anomaly")

所以从 textPayload 转到 jsonPayload 两边都命中。

验证

  • npm test 1643 pass / 0 fail(新增 6 个 claim 测试);typecheck、api-contract 干净。
  • 测试覆盖全部五条路径:Firestore 关闭、无 uid scope、create 成功、409/412 撞车、以及 503 与抛异常两种 fail-open 情形。

运维说明

lisa-anomaly-alerts 是新集合,每个触发阈值的用户每天一个小文档。正常情况下几乎不产生(超阈值本就是异常事件),无需清理策略;真要收也可以按 day 字段配 Firestore TTL。

🤖 Generated with Claude Code

The >$10/day/user anomaly alert deduped through a process-local Set, so
with MAX_INSTANCES>1 the same day paged the operator once per instance,
and a cold start re-armed it. The Set stays as the in-process fast path;
the cross-instance arbiter is now a create-only Firestore write on
lisa-anomaly-alerts/{uid}_{day} — the second writer fails its
precondition, and that failure is the dedup signal.

Deliberately fail-open: only 409/412 counts as "someone else has it".
Any other failure (network, permission, Firestore down) still alerts,
because a duplicate alert is a nuisance while a missed one is an
unnoticed burn. With Firestore off or outside a per-uid scope (Mac
edition, shared-token demo) the claim is a no-op and behavior is
unchanged — verified by two tests that assert the network is never hit.

Also finishes this file's share of #363/#364: both console.error sites
become logError, and the alert text names a cloud tenant by redacted uid
instead of embedding the home path (which contains the raw uid). The
"[billing] ⚠ anomaly" prefix the alert policy matches is untouched, and
that policy already matches both textPayload and jsonPayload, so the
move to structured output does not break it.

npm test 1643 pass / 0 fail; typecheck + api-contract clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oratis
oratis merged commit 6c210d7 into main Aug 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant