payment-kit 1.27.1 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__blocklet__.js +37 -0
- package/api/ocap-1.30-subpath-shims.d.ts +35 -0
- package/api/src/crons/index.ts +10 -0
- package/api/src/crons/metering-subscription-detection.ts +12 -14
- package/api/src/crons/overdue-detection.ts +51 -74
- package/api/src/integrations/arcblock/nft.ts +6 -2
- package/api/src/integrations/arcblock/stake.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +4 -4
- package/api/src/integrations/blocklet/notification.ts +1 -1
- package/api/src/integrations/ethereum/tx.ts +29 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +70 -53
- package/api/src/integrations/stripe/handlers/payment-intent.ts +8 -1
- package/api/src/integrations/stripe/resource.ts +8 -0
- package/api/src/libs/audit.ts +32 -16
- package/api/src/libs/auth.ts +49 -2
- package/api/src/libs/chain-error.ts +31 -0
- package/api/src/libs/error.ts +15 -0
- package/api/src/libs/event.ts +42 -1
- package/api/src/libs/invoice.ts +69 -34
- package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +1 -3
- package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +1 -3
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -3
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-refund-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-warning.ts +1 -3
- package/api/src/libs/notification/template/subscription-succeeded.ts +1 -1
- package/api/src/libs/pagination.ts +14 -9
- package/api/src/libs/payment.ts +25 -10
- package/api/src/libs/session.ts +1 -1
- package/api/src/libs/timing.ts +35 -0
- package/api/src/libs/util.ts +16 -15
- package/api/src/libs/wallet-migration.ts +72 -53
- package/api/src/queues/auto-recharge.ts +1 -1
- package/api/src/queues/credit-consume.ts +94 -12
- package/api/src/queues/credit-grant.ts +4 -0
- package/api/src/queues/event.ts +14 -2
- package/api/src/queues/invoice.ts +1 -0
- package/api/src/queues/payment.ts +83 -15
- package/api/src/queues/refund.ts +84 -71
- package/api/src/queues/subscription.ts +1 -0
- package/api/src/routes/checkout-sessions.ts +82 -43
- package/api/src/routes/connect/change-payment.ts +2 -0
- package/api/src/routes/connect/change-plan.ts +2 -0
- package/api/src/routes/connect/pay.ts +12 -3
- package/api/src/routes/connect/setup.ts +3 -1
- package/api/src/routes/connect/shared.ts +52 -39
- package/api/src/routes/connect/subscribe.ts +4 -1
- package/api/src/routes/credit-grants.ts +25 -17
- package/api/src/routes/donations.ts +2 -2
- package/api/src/routes/meter-events.ts +16 -6
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +1 -1
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/tax-rates.ts +1 -1
- package/api/src/store/models/customer.ts +23 -1
- package/api/src/store/models/payment-method.ts +4 -0
- package/api/src/store/models/price.ts +23 -14
- package/api/tests/libs/wallet-migration.spec.ts +4 -4
- package/api/tests/queues/credit-consume-batch.spec.ts +5 -2
- package/api/tests/queues/credit-consume.spec.ts +8 -4
- package/api/tests/routes/credit-grants.spec.ts +1 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-CHALLENGES.md +676 -0
- package/cloudflare/MIGRATION-RUNBOOK.md +777 -0
- package/cloudflare/README.md +499 -0
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +602 -0
- package/cloudflare/build.ts +151 -0
- package/cloudflare/did-connect-auth.ts +527 -0
- package/cloudflare/docs/2026-04-22-sdk-1.30.9-upgrade-retro.md +324 -0
- package/cloudflare/docs/2026-04-24-queue-ops-followup.md +218 -0
- package/cloudflare/docs/cf-queues-ops-alert-analysis.md +663 -0
- package/cloudflare/docs/cf-workers-local-dev-and-fixes.md +284 -0
- package/cloudflare/docs/cleanup-tasks-2026-05.md +62 -0
- package/cloudflare/docs/payment-kit-platform-analysis-2026-04-20.md +354 -0
- package/cloudflare/frontend-shims/buffer-polyfill.ts +9 -0
- package/cloudflare/frontend-shims/js-sdk.ts +43 -0
- package/cloudflare/frontend-shims/mime-types.ts +46 -0
- package/cloudflare/frontend-shims/session.ts +24 -0
- package/cloudflare/frontend-shims/vite-plugin-noop.ts +6 -0
- package/cloudflare/index.html +40 -0
- package/cloudflare/migrate-to-d1.js +252 -0
- package/cloudflare/migrations/0001_initial_schema.sql +82 -0
- package/cloudflare/migrations/0002_indexes.sql +75 -0
- package/cloudflare/migrations/0003_locks_and_constraints.sql +18 -0
- package/cloudflare/run-build.js +390 -0
- package/cloudflare/scripts/test-decrypt.js +102 -0
- package/cloudflare/shims/arcblock-ws.ts +20 -0
- package/cloudflare/shims/axios-http-adapter.ts +4 -0
- package/cloudflare/shims/axios-lite.ts +117 -0
- package/cloudflare/shims/blocklet-sdk/auth-service.ts +33 -0
- package/cloudflare/shims/blocklet-sdk/cdn.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/component-api.ts +35 -0
- package/cloudflare/shims/blocklet-sdk/component.ts +18 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/did.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/env.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/eventbus.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/fallback.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/index.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/logger.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/middlewares.ts +15 -0
- package/cloudflare/shims/blocklet-sdk/notification.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/security.ts +53 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/verify-sign.ts +38 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +6 -0
- package/cloudflare/shims/blocklet-sdk/wallet.ts +103 -0
- package/cloudflare/shims/cookie-parser.ts +3 -0
- package/cloudflare/shims/cors.ts +21 -0
- package/cloudflare/shims/cron.ts +189 -0
- package/cloudflare/shims/crypto-js-warn.ts +7 -0
- package/cloudflare/shims/did-space-js.ts +17 -0
- package/cloudflare/shims/did-space.ts +11 -0
- package/cloudflare/shims/error.ts +18 -0
- package/cloudflare/shims/express-compat/index.ts +80 -0
- package/cloudflare/shims/express-compat/types.ts +41 -0
- package/cloudflare/shims/fastq.ts +105 -0
- package/cloudflare/shims/lock.ts +115 -0
- package/cloudflare/shims/mime-types.ts +56 -0
- package/cloudflare/shims/nedb-storage.ts +9 -0
- package/cloudflare/shims/node-child-process.ts +9 -0
- package/cloudflare/shims/node-fs.ts +20 -0
- package/cloudflare/shims/node-http.ts +13 -0
- package/cloudflare/shims/node-https.ts +4 -0
- package/cloudflare/shims/node-misc.ts +15 -0
- package/cloudflare/shims/node-net.ts +8 -0
- package/cloudflare/shims/node-os.ts +14 -0
- package/cloudflare/shims/node-tty.ts +8 -0
- package/cloudflare/shims/node-zlib.ts +17 -0
- package/cloudflare/shims/noop.ts +26 -0
- package/cloudflare/shims/payment-vendor.ts +14 -0
- package/cloudflare/shims/querystring.ts +12 -0
- package/cloudflare/shims/queue.ts +585 -0
- package/cloudflare/shims/rolldown-runtime.ts +43 -0
- package/cloudflare/shims/sequelize-d1/datatypes.ts +24 -0
- package/cloudflare/shims/sequelize-d1/helpers.ts +46 -0
- package/cloudflare/shims/sequelize-d1/index.ts +34 -0
- package/cloudflare/shims/sequelize-d1/model.ts +1157 -0
- package/cloudflare/shims/sequelize-d1/operators.ts +293 -0
- package/cloudflare/shims/sequelize-d1/retry.ts +85 -0
- package/cloudflare/shims/sequelize-d1/sequelize-class.ts +119 -0
- package/cloudflare/shims/sequelize-d1/timing.ts +81 -0
- package/cloudflare/shims/sequelize-d1/types.ts +35 -0
- package/cloudflare/shims/stripe-cf.ts +29 -0
- package/cloudflare/shims/ws-lite.ts +103 -0
- package/cloudflare/shims/xss.ts +3 -0
- package/cloudflare/tests/shims/cron.spec.ts +210 -0
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +186 -0
- package/cloudflare/vite.config.ts +162 -0
- package/cloudflare/worker.ts +1553 -0
- package/cloudflare/wrangler.json +63 -0
- package/cloudflare/wrangler.jsonc +69 -0
- package/cloudflare/wrangler.staging.json +66 -0
- package/cloudflare/wrangler.toml +28 -0
- package/jest.config.js +4 -12
- package/package.json +26 -22
- package/src/app.tsx +62 -4
- package/src/components/customer/link.tsx +9 -13
- package/src/components/customer/notification-preference.tsx +3 -2
- package/src/components/filter-toolbar.tsx +4 -0
- package/src/components/invoice/list.tsx +9 -1
- package/src/components/invoice-pdf/utils.ts +2 -1
- package/src/components/layout/admin.tsx +39 -5
- package/src/components/layout/user-cf.tsx +77 -0
- package/src/components/payment-intent/actions.tsx +23 -3
- package/src/components/safe-did-address.tsx +75 -0
- package/src/libs/patch-user-card.ts +25 -0
- package/src/libs/util.ts +5 -7
- package/src/pages/admin/billing/meter-events/index.tsx +4 -0
- package/src/pages/admin/customers/customers/detail.tsx +2 -2
- package/src/pages/admin/customers/customers/index.tsx +2 -2
- package/src/pages/admin/overview.tsx +3 -1
- package/src/pages/customer/subscription/detail.tsx +4 -4
- package/tsconfig.api.json +1 -6
- package/tsconfig.json +3 -4
- package/tsconfig.types.json +2 -1
- package/vite.config.ts +6 -1
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
# CF Queues 95% Daily Usage Alert — 根因分析与修复方案
|
|
2
|
+
|
|
3
|
+
> **[2026-04-24 更新]** 本文档 §1.1 / §1.4 / §8.2 / §17.2 / §18.3 / §21 中部分预期值与承诺阈值已被实际线上数据证伪(日度免费额度是 10k 不是月度 1M;PR #1341 降幅仅 ~15% 而非 80%;`exceededResources` 恶化而非归零)。请先阅读 **`2026-04-24-queue-ops-followup.md`** 获取勘误对照和最新部署记录(Plan 8 + Plan 13),再回到本文了解历史根因分析。
|
|
4
|
+
|
|
5
|
+
| 字段 | 值 |
|
|
6
|
+
|---|---|
|
|
7
|
+
| 状态 | 待修复(已完成根因分析、待提 PR) |
|
|
8
|
+
| 首次告警 | 2026-04-17(Cloudflare 邮件:"95% of daily usage limit for Cloudflare Queues Operations reached")|
|
|
9
|
+
| 受影响环境 | ArcBlock 主账号(`983482dc52a0aee5dbd7f986840a6190`)`staging-aigne-hub-payment-kit` Worker |
|
|
10
|
+
| 受影响队列 | `staging-aigne-hub-payment-kit-jobs`(producer=1, consumer=1)|
|
|
11
|
+
| 分析负责人 | Pengfei(代码走读 + Cloudflare GraphQL Analytics 取数)|
|
|
12
|
+
| 生产环境影响 | 暂无(仅 staging 告警;但同一套代码上 prod 后会复现)|
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 第一部分:简要总览(决策者视角)
|
|
17
|
+
|
|
18
|
+
### 1. 一句话结论
|
|
19
|
+
|
|
20
|
+
`shims/cron.ts` 的 5 分钟匹配窗口 + `wrangler.staging.json` 的每分钟 cron 触发器两者叠加,导致所有 `*/N` 定时任务按 **5 倍设计频率**触发,每天产生约 13,000 条无谓的 CF Queue 操作,17 天累计触达免费额度(1,000,000 ops / month)的 95%。
|
|
21
|
+
|
|
22
|
+
### 2. 对在线数据与业务的影响底线
|
|
23
|
+
|
|
24
|
+
**本次修复只动"调度节奏",不动任何涉及金额的代码。**
|
|
25
|
+
|
|
26
|
+
| 数据类型 | 是否变化 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| 用户余额、Credit 扣减金额 | ❌ 不变 |
|
|
29
|
+
| 订阅下次续费时间、金额 | ❌ 不变 |
|
|
30
|
+
| 退款金额与次数 | ❌ 不变 |
|
|
31
|
+
| 链上转账次数(归集、stake 退还) | ❌ 不变 |
|
|
32
|
+
| Stripe / chain API 调用次数 | ⬇️ 减少约 80% |
|
|
33
|
+
| D1 查询次数 | ⬇️ 减少约 80% |
|
|
34
|
+
| CF Queue 日 ops | ⬇️ 13k → 2-4k |
|
|
35
|
+
| 延迟任务执行时机漂移 | ⚠️ 最多 1-5 分钟 |
|
|
36
|
+
|
|
37
|
+
**计费正确性、历史记录、对账数据不会因本次修复而产生任何变化。** 完整承诺阈值见 §18。
|
|
38
|
+
|
|
39
|
+
### 3. 改动清单与状态
|
|
40
|
+
|
|
41
|
+
> ⚠️ 本小节已经历过两次迭代。改动 A 是 2026-04-17 告警当天的**历史应急动作**,已通过 CF API 执行,**不应在 PR #1341 合入后再次执行**。最终目标状态见 §3.1。
|
|
42
|
+
|
|
43
|
+
| # | 改动 | 文件 | 当前状态 |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| A(历史应急)| cron `* * * * *` → `*/5 * * * *`(CF API 改,不重部署代码)| `wrangler.staging.json` | ✅ 已于 2026-04-17 13:13 UTC 执行;**PR #1341 部署后会由 wrangler 同步回 `* * * * *`,不再重复** |
|
|
46
|
+
| B(根治)| 匹配窗口 5min → 1min,改用 `event.scheduledTime` | `shims/cron.ts` | ✅ 在 PR #1341 `8a030209` + `fc0e4da4`(含 P2-1 的 scheduledTime fix)|
|
|
47
|
+
| C(节流)| `startRefundQueue` 迁移为 cron | `worker.ts` + `crons/index.ts` | ✅ 在 PR #1341 `e12b302b` |
|
|
48
|
+
| D(可选)| 合并 `runAllScheduledJobs` D1 查询 | `shims/queue.ts` | ⏳ 独立 PR,条件触发(Plan 12 若 exceededResources 未降则启动)|
|
|
49
|
+
|
|
50
|
+
### 3.1 PR #1341 部署后的目标终态
|
|
51
|
+
|
|
52
|
+
- `wrangler.staging.json` cron = **`* * * * *`**(由 `fc0e4da4` 恢复)
|
|
53
|
+
- CF 线上 cron schedule = `* * * * *`(由 wrangler deploy 同步写入)
|
|
54
|
+
- `shims/cron.ts` 的 `shouldRunInWindow` 窗口 = **1 分钟**
|
|
55
|
+
- `cronInstance.runAll()` 接受 `event.scheduledTime` 参数(由 `worker.ts` 传入)
|
|
56
|
+
- `refund.recovery` cron 表达式 = `0 */5 * * * *`
|
|
57
|
+
|
|
58
|
+
**invariant**(锁在测试中):shim 窗口大小 ≥ CF 触发间隔;本次两者都是 1 分钟。**若将来任何人把 CF cron 再改成 `*/5 * * * *`,必须同步把 shim 窗口恢复到 5 分钟,否则非 5 对齐的 cron(如 `'0 1 * * * *'`)会永不运行。**
|
|
59
|
+
|
|
60
|
+
### 4. 预期最终结果
|
|
61
|
+
|
|
62
|
+
| 指标 | bug 满频基线(04-16)| Plan 1 应急态(04-18/19) | PR #1341 完整部署后(预期)|
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| CF Queue 日 ops | 13,617 | 10,007-10,483 | 2-4k |
|
|
65
|
+
| 占 1M/month 免费额度 | 17 天 95% | 20 天 10% | 30 天 10-15% |
|
|
66
|
+
| 延迟任务调度精度 | 1 min(但 5× 重复)| 5 min | 1 min(严格,无重复)|
|
|
67
|
+
| Worker `exceededResources` | 1,022/day | 300/day(每次都超限)| 预期趋近 0 |
|
|
68
|
+
| Stripe API 日调用量 | 基线 | ~40% | ~20% |
|
|
69
|
+
| 告警状态 | 每日触发 | 稳定消失 | 稳定消失 |
|
|
70
|
+
|
|
71
|
+
### 5. 执行步骤(摘要,已按实际进度更新)
|
|
72
|
+
|
|
73
|
+
1. ✅ **2026-04-17 13:13 UTC** — 通过 CF API 把线上 cron 改为 `*/5`(改动 A,应急止血)。
|
|
74
|
+
2. ✅ **2026-04-18 ~ 04-20** — Plan 2 观察:ops 降 23-27%,发现 scheduled handler 100% 超限(每次 exceededResources)。
|
|
75
|
+
3. ✅ **2026-04-18 ~ 04-20** — PR #1341 提交,含改动 B + C + 文档;经过两轮 review 修复 P1-1、P2-1、P2-2。
|
|
76
|
+
4. ⏳ **PR review 通过后** — `wrangler deploy --config wrangler.staging.json`;部署会**同步**把 CF cron 从 `*/5`(应急态)恢复到 `* * * * *`(最终态),同时上线 shim 1min 窗口代码。这是一次部署完成改动 B+C+Plan 6。
|
|
77
|
+
5. ⏳ **部署后 2h** — 验证:小时 WriteMessage ≤ 40,`exceededResources` 每小时 ≤ 2(当前基线 ~12/h)。
|
|
78
|
+
6. ⏳ **部署后 24h** — 验证:日 ops ≤ 5,000,`exceededResources` 日合计 ≤ 10。
|
|
79
|
+
7. ⏳ **部署后 7 天** — 稳态观察,若 `exceededResources` 未降到阈值说明 `runAllScheduledJobs` 仍是瓶颈,启动改动 D 独立 PR。
|
|
80
|
+
8. ⏳ **后续** — 四份 wrangler 配置统一(Plan 9);接入 Slack 每日 ops 报告(Plan 7)。
|
|
81
|
+
|
|
82
|
+
> 完整分步可行性与每步风险/回滚细节见 **§17 详细执行顺序与可行性分析**(§17 的文字描述保留,但执行者以本节 §3.1 的目标终态 + §5 步骤为准)。
|
|
83
|
+
|
|
84
|
+
### 6. 回滚策略
|
|
85
|
+
|
|
86
|
+
- 改动 A 回滚:`wrangler rollback --name staging-aigne-hub-payment-kit --version <prev_id>`,1 分钟。
|
|
87
|
+
- 改动 B/C 回滚:revert PR + redeploy,15 分钟。
|
|
88
|
+
- 所有改动都不改 D1 schema,无需数据迁移/数据回滚。
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 第二部分:详细分析档案
|
|
93
|
+
|
|
94
|
+
### 7. 背景与分析目标
|
|
95
|
+
|
|
96
|
+
#### 7.1 触发事件
|
|
97
|
+
|
|
98
|
+
`noreply@notify.cloudflare.com` 于 2026-04-17 发邮件告警:"Your (ArcBlock) account has used 95% of the daily Cloudflare Queues free tier limit. If you do not take any action and exceed the daily limit, attempts to send a message to a queue will fail until the limit resets at 2026-04-18 at 00:00:00 UTC."
|
|
99
|
+
|
|
100
|
+
#### 7.2 分析目标
|
|
101
|
+
|
|
102
|
+
1. 确认告警是否真实(对应是哪个队列、哪个 Worker、哪条代码路径)。
|
|
103
|
+
2. 给出根因而不是表面上的症状修复。
|
|
104
|
+
3. 评估每种修复方案对**在线业务与数据**的影响,确保不会造成计费错误或数据丢失。
|
|
105
|
+
4. 给出可立即执行的止血路径与长期根治路径。
|
|
106
|
+
|
|
107
|
+
#### 7.3 数据来源
|
|
108
|
+
|
|
109
|
+
| 来源 | 用途 |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `/Users/zac/work/arcblock/payment-kit/blocklets/core/.env.development.local` | 提供 `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID`(仅本地使用,**此文档不记录 token 值**)|
|
|
112
|
+
| `blocklets/core/cloudflare/wrangler.*.{json,jsonc,toml}` | Worker 部署配置 |
|
|
113
|
+
| `blocklets/core/cloudflare/shims/cron.ts` | CF 下 @abtnode/cron 的替身实现 |
|
|
114
|
+
| `blocklets/core/cloudflare/shims/queue.ts` | CF 下队列 push/consume shim |
|
|
115
|
+
| `blocklets/core/cloudflare/worker.ts` | CF Worker 入口(fetch/scheduled/queue handlers)|
|
|
116
|
+
| `blocklets/core/api/src/queues/*.ts` | 30+ 个业务队列定义 |
|
|
117
|
+
| `blocklets/core/api/src/crons/index.ts` | cron job 注册清单 |
|
|
118
|
+
| Cloudflare REST API `/accounts/:id/queues`、`/workers/scripts`、`/workers/services` | 取部署态、绑定、schedules |
|
|
119
|
+
| Cloudflare GraphQL Analytics API `queueMessageOperationsAdaptiveGroups`、`workersInvocationsAdaptive` | 取队列 ops 与 Worker 调用曲线 |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### 8. 在线数据全景
|
|
124
|
+
|
|
125
|
+
#### 8.1 账号与 Worker 盘点
|
|
126
|
+
|
|
127
|
+
**ArcBlock 主账号**(`983482dc52a0aee5dbd7f986840a6190`,告警来源):
|
|
128
|
+
|
|
129
|
+
| Worker | Cron 实际部署 | Queue 绑定 | 备注 |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| `staging-aigne-hub-payment-kit` | ✅ `* * * * *`(2026-04-10 起)| ✅ producer + consumer | 告警源头 |
|
|
132
|
+
| `aigne-hub-staging` | ✅ `* * * * *` | ❌(调用 payment-kit 用 service binding)| 上游调用方 |
|
|
133
|
+
| `staging-aigne-hub-app`、`staging-aigne-hub-media-kit`、`blocklet-service-staging` | 其它 | 其它 | 不相关 |
|
|
134
|
+
|
|
135
|
+
**Pengfei 个人账号**(`7790d6810b003f5dd01c4a62db02f391`,用作私人 staging 试验):
|
|
136
|
+
|
|
137
|
+
| Worker | 状态 |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `payment-kit-staging`(04-17 部署) | 无 cron、无 queue binding;用 `wrangler.local.toml` |
|
|
140
|
+
| `staging-aigne-hub-payment-kit`(04-14 旧版) | 孤儿 |
|
|
141
|
+
| 孤儿队列 `staging-aigne-hub-payment-kit-jobs` | 10 天零操作(不是本次告警对象) |
|
|
142
|
+
|
|
143
|
+
**本次修复只涉及 ArcBlock 主账号上的部署,与 Pengfei 账号资源无关。**
|
|
144
|
+
|
|
145
|
+
#### 8.2 队列 ops 近 10 天日度分布(ArcBlock 账号)
|
|
146
|
+
|
|
147
|
+
| 日期 | WriteMessage | ReadMessage | DeleteMessage | 合计 ops |
|
|
148
|
+
|---|---|---|---|---|
|
|
149
|
+
| 2026-04-10(首次部署当天)| 222 | 222 | 222 | 666 |
|
|
150
|
+
| 2026-04-11 | 4,078 | 4,459 | 3,956 | 12,496 |
|
|
151
|
+
| 2026-04-12 | 3,229 | 3,586 | 3,196 | 10,013 |
|
|
152
|
+
| 2026-04-13 | 3,284 | 3,542 | 3,186 | 10,016 |
|
|
153
|
+
| 2026-04-14 | 3,866 | 4,301 | 3,863 | 12,030 |
|
|
154
|
+
| 2026-04-15 | 4,480 | 5,096 | 4,515 | 14,100 |
|
|
155
|
+
| 2026-04-16 | 4,488 | 4,609 | 4,520 | 13,617 |
|
|
156
|
+
| 2026-04-17(至 UTC 09:00)| 4,015 | 4,501 | 4,111 | 12,644 |
|
|
157
|
+
|
|
158
|
+
**17 天累计 ≈ 90,000 ops → 对 1,000,000 ops/month 免费额度占用 ≈ 9%**;**由于 CF 告警使用"按月剩余天数反推日均阈值"计算,达到当日 95% 告警即触发**。
|
|
159
|
+
|
|
160
|
+
#### 8.3 2026-04-16 小时级写入分布(payment-kit-jobs 队列)
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
00:00 610 writes
|
|
164
|
+
01:00 2,123 writes ← 峰值,相当于 35 writes/min
|
|
165
|
+
02:00 143 writes
|
|
166
|
+
03:00 1,612 writes
|
|
167
|
+
04:00–23:00 0
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**分布稀疏,非均匀**——说明不是"每分钟匀速 push",而是**某些窗口集中爆推、多数时间静默**。对应业务场景:cron 运行时发现 D1 里有待处理的 Job 行(如未完成的 refund、subscription 重试、deposit vault 扫描),触发 N 倍 push。
|
|
171
|
+
|
|
172
|
+
#### 8.4 同队列 DLQ(`staging-aigne-hub-payment-kit-jobs-dlq`)
|
|
173
|
+
|
|
174
|
+
- 04-11 ~ 04-17 累计 < 30 条 write。说明**业务层大量任务执行成功,少量确实失败进 DLQ**,DLQ 不是 ops 放大主因。
|
|
175
|
+
|
|
176
|
+
#### 8.5 `payment-kit-staging`(Pengfei 账号)请求分布旁证
|
|
177
|
+
|
|
178
|
+
- 空闲期**每小时恰好 60 次**调用 = 每分钟 1 次 = `aigne-hub-staging` 的 cron 通过 service binding 穿透过来。
|
|
179
|
+
- 04-15 16:00-19:00 见到 3,300/h 业务峰值(无关告警)。
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### 9. 根因技术分析
|
|
184
|
+
|
|
185
|
+
#### 9.1 Cron shim 5 分钟窗口来历
|
|
186
|
+
|
|
187
|
+
文件 `blocklets/core/cloudflare/shims/cron.ts:107-113`:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
function shouldRunInWindow(cronExpr: string, date: Date): boolean {
|
|
191
|
+
for (let i = 0; i < 5; i++) {
|
|
192
|
+
const check = new Date(date.getTime() + i * 60000);
|
|
193
|
+
if (matchesCron(cronExpr, check)) return true;
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
原设计假设:CF Scheduled Triggers 最细粒度曾经是每 5 分钟一次,所以一次触发要覆盖未来 5 分钟里任何匹配分钟的 cron。
|
|
200
|
+
|
|
201
|
+
**现状问题**:`wrangler.staging.json:64`(和 `wrangler.jsonc`)已经把 CF 触发改为 `* * * * *`(每分钟),**但窗口没有同步缩到 1 分钟**。
|
|
202
|
+
|
|
203
|
+
#### 9.2 5 倍放大效果量化
|
|
204
|
+
|
|
205
|
+
| cron 表达式 | 原意频率 | 现实频率(bug 中)| 放大 |
|
|
206
|
+
|---|---|---|---|
|
|
207
|
+
| `0 */5 * * * *`(`depositVault`、`revokeStake`)| 每 5 min | **每 min** | 5× |
|
|
208
|
+
| `0 */10 * * * *`(`creditConsume`、`vendorStatusCheck`、`vendorReturnScan`)| 每 10 min | 10 min 里 5 次 | 5× |
|
|
209
|
+
| `0 */20 * * * *`(`stripePayment`)| 每 20 min | 20 min 里 5 次 | 5× |
|
|
210
|
+
| `0 */30 * * * *`(`subscription`、`stripeInvoice`)| 每 30 min | 30 min 里 5 次 | 5× |
|
|
211
|
+
| `0 5 */6 * * *`(`notification`)| 每 6 h 一次 | 6 h 里连续 5 min 内跑 | 5× |
|
|
212
|
+
|
|
213
|
+
每个 `start*Queue` 函数(`startDepositVaultQueue`、`startCreditConsumeQueue`、`startSubscriptionQueue` 等)会全表扫目标表并针对每行 `push` 到 CF Queue,push 内部通过 D1 的 `UNIQUE(id)` 约束去重(同 id 重推在 addJob 阶段被阻断),但一旦 consumer 处理完删行,下一分钟的新 push 成功到达 CF Queue。
|
|
214
|
+
|
|
215
|
+
#### 9.3 `startRefundQueue` 无条件每分钟执行
|
|
216
|
+
|
|
217
|
+
`blocklets/core/cloudflare/worker.ts:1402-1403`(2026-04-17 新增):
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
const { startRefundQueue } = await import('../api/src/queues/refund');
|
|
221
|
+
await startRefundQueue();
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
该函数本意是"deploy 过程中 isolate 被杀导致 refund 任务丢在内存未持久化"的兜底,正常情况下无需每分钟跑。目前每次 scheduled handler 都执行 = 每分钟一次全表扫 `Refund.findAll({status:'pending'})`。
|
|
225
|
+
|
|
226
|
+
#### 9.4 `runAllScheduledJobs` 每分钟扫描 30+ 队列
|
|
227
|
+
|
|
228
|
+
`blocklets/core/cloudflare/shims/queue.ts:197-212` 每分钟对每个注册队列做 2 次 D1 查询(`getScheduledJobs` + `getJobs`)。30 个队列 × 2 = 60 次 D1 查询/分钟,即使空闲。这主要吃 D1 额度,次要影响队列 ops(仅在扫到 due job 时会 push)。
|
|
229
|
+
|
|
230
|
+
#### 9.5 算术自洽验证
|
|
231
|
+
|
|
232
|
+
- 2026-04-16 写入 4,488 条,3 ops/msg × 4,488 ≈ 13,464 ≈ 实际 13,617(误差来自 DLQ + retry)。
|
|
233
|
+
- 1440 次 cron × 3 push/次 ≈ 4,320 msg/day ≈ 观测值。
|
|
234
|
+
|
|
235
|
+
**结论自洽**:平均每次 cron 推 3 条消息,峰值 35/min 对应业务事件(批量 refund 恢复或 subscription 批量续费)。
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### 10. 修复方案详细说明
|
|
240
|
+
|
|
241
|
+
#### 改动 A(立即执行,配置级回滚)
|
|
242
|
+
|
|
243
|
+
```diff
|
|
244
|
+
// wrangler.staging.json:64
|
|
245
|
+
"triggers": {
|
|
246
|
+
- "crons": ["* * * * *"]
|
|
247
|
+
+ "crons": ["*/5 * * * *"]
|
|
248
|
+
},
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
- 原理:CF Scheduled 5 min 触发 + shim 5 min 窗口 → 所有 `*/N` 任务回到原设计频率(等价于根因修复的效果)。
|
|
252
|
+
- 代价:延迟任务调度精度从 1 min 降到 5 min。
|
|
253
|
+
- 部署命令:`cd blocklets/core/cloudflare && npx wrangler deploy --config wrangler.staging.json`(需在设置了 `CLOUDFLARE_ACCOUNT_ID` / `CLOUDFLARE_API_TOKEN` 的 shell 里执行)。
|
|
254
|
+
|
|
255
|
+
#### 改动 B(根治)
|
|
256
|
+
|
|
257
|
+
```diff
|
|
258
|
+
// blocklets/core/cloudflare/shims/cron.ts:107-113
|
|
259
|
+
function shouldRunInWindow(cronExpr: string, date: Date): boolean {
|
|
260
|
+
- for (let i = 0; i < 5; i++) {
|
|
261
|
+
- const check = new Date(date.getTime() + i * 60000);
|
|
262
|
+
- if (matchesCron(cronExpr, check)) return true;
|
|
263
|
+
- }
|
|
264
|
+
- return false;
|
|
265
|
+
+ return matchesCron(cronExpr, date);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
- 原理:CF Scheduled 每分钟触发就判断当前这一分钟是否匹配 cron 表达式。
|
|
270
|
+
- 同时补单测:`shouldRunInWindow('0 */5 * * * *', date)` 在连续 60 分钟里恰好 true 12 次。
|
|
271
|
+
|
|
272
|
+
#### 改动 C(节流)
|
|
273
|
+
|
|
274
|
+
```diff
|
|
275
|
+
// blocklets/core/cloudflare/worker.ts:1402-1403
|
|
276
|
+
-// Recover pending refunds that were missed (e.g. after deploy or failed dispatch)
|
|
277
|
+
-const { startRefundQueue } = await import('../api/src/queues/refund');
|
|
278
|
+
-await startRefundQueue();
|
|
279
|
+
+// Recover pending refunds every 5 minutes to avoid wasting CF Queue ops
|
|
280
|
+
+if (new Date().getUTCMinutes() % 5 === 0) {
|
|
281
|
+
+ const { startRefundQueue } = await import('../api/src/queues/refund');
|
|
282
|
+
+ await startRefundQueue();
|
|
283
|
+
+}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
- 或者更干净的做法:把 `startRefundQueue` 移到 `blocklets/core/api/src/crons/index.ts` 注册为正式 cron job(`time: '0 */5 * * * *'`),删掉 worker.ts 里的硬编码调用。
|
|
287
|
+
|
|
288
|
+
#### 改动 D(可选,暂缓)
|
|
289
|
+
|
|
290
|
+
合并 `runAllScheduledJobs` 的 per-queue 循环为一次 `Op.or` 聚合查询:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
const allRows = await Job.findAll({
|
|
294
|
+
where: {
|
|
295
|
+
cancelled: false,
|
|
296
|
+
[Op.or]: [
|
|
297
|
+
{ will_run_at: { [Op.lte]: Date.now() }, delay: { [Op.not]: -1 } },
|
|
298
|
+
{ delay: -1, created_at: { [Op.lte]: new Date(twoMinAgo) } },
|
|
299
|
+
],
|
|
300
|
+
},
|
|
301
|
+
order: [['created_at', 'ASC']],
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
本地按 `row.queue` 分组 dispatch。
|
|
306
|
+
|
|
307
|
+
**暂缓原因**:30 个队列共享逻辑,`WHERE` 条件写错可能误派发或乱序派发,风险中等;而且 A+B+C 已足以解告警。
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### 11. 业务影响分析(具体例子)
|
|
312
|
+
|
|
313
|
+
#### 例 1 — AI 用量扣费(`credit.consumption`)
|
|
314
|
+
|
|
315
|
+
- **修复前**:每分钟扫 pending meter events,每条单独 push + 单独 D1 事务。极端情况下 6 次 AI 调用在 10 分钟内产生 6 次独立处理。
|
|
316
|
+
- **修复后**:每 10 分钟批量扫一次,一次性 batch 处理 6 条。
|
|
317
|
+
- **用户感知**:UI 上余额更新从"近实时"变为"10 分钟窗口"——**AI 用量计费本身就是计量计费,10 分钟延迟是设计值**,并非退化。
|
|
318
|
+
|
|
319
|
+
#### 例 2 — 自动金库归集(`deposit.vault`)
|
|
320
|
+
|
|
321
|
+
- **修复前**:每分钟为每个 vault-enabled 币种 push 一次余额检查。3 种币 × 1440 min ≈ 4,320 次链上 RPC/天,绝大多数余额没变。
|
|
322
|
+
- **修复后**:每 5 分钟 push,RPC 量降到 864 次/天。
|
|
323
|
+
- **用户感知**:商户 Dashboard 里"金库余额"更新滞后从 < 1 min 变为 < 5 min——**冷钱包归集业务语义完全可接受**。
|
|
324
|
+
|
|
325
|
+
#### 例 3 — 订阅续费重试(`subscription.schedule.retry`)
|
|
326
|
+
|
|
327
|
+
- **修复前**:5 分钟窗口造成 `next_attempt` 可能被提前触发,破坏指数退避。
|
|
328
|
+
- **修复后**:严格按 30 分钟跑。
|
|
329
|
+
- **金额/次数**:**完全不变**——是否重试由 `attempt_count >= MAX_RETRY_COUNT` 决定,不由频率决定。
|
|
330
|
+
|
|
331
|
+
#### 例 4 — 退款恢复(`startRefundQueue`)
|
|
332
|
+
|
|
333
|
+
- **修复前**:deploy 丢失的 pending refund 最多 1 分钟内被自愈补推。
|
|
334
|
+
- **修复后**:最多 5 分钟内自愈。
|
|
335
|
+
- **金额/次数**:**完全不变**——`handleRefund` 开头 `if (refund.status !== 'pending') return` 保证幂等。
|
|
336
|
+
|
|
337
|
+
#### 业务层幂等守卫清单(本次修复安全性的保证)
|
|
338
|
+
|
|
339
|
+
| 队列 | 幂等守卫位置 |
|
|
340
|
+
|---|---|
|
|
341
|
+
| refund | `handleRefund` 顶部 status 检查 |
|
|
342
|
+
| subscription | `handleSubscriptionJob` 读 `Subscription.status` + `next_attempt` 比较 |
|
|
343
|
+
| credit-consumption | MeterEvent `status` 字段状态机 |
|
|
344
|
+
| deposit-vault | `handleDepositVaultJob` 先查最新余额和阈值再决定是否发起转账 |
|
|
345
|
+
| webhook | 重复投递由商户 endpoint 负责幂等(签名 + timestamp)|
|
|
346
|
+
| payment | `handlePayment` 读 `PaymentIntent.status` 跳过已处理 |
|
|
347
|
+
|
|
348
|
+
因此即使重试或重派发,**不会产生重复扣款/重复退款/重复归集**。
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
### 12. 风险分级
|
|
353
|
+
|
|
354
|
+
| 风险 | 概率 | 影响 | 缓解 |
|
|
355
|
+
|---|---|---|---|
|
|
356
|
+
| 改动 A 让 `0 5 */6 * * *` 这种只在单一分钟触发的 cron 漏跑 | 低 | 中(续费提醒邮件漏发)| 5 分钟窗口原本就是为容错设计,`*/5` cron 刚好覆盖 `minute=5/10/…/55`,正常会触发 |
|
|
357
|
+
| CF Scheduled 触发本身丢失 | 低(CF SLA 不承诺)| 受影响 cron 周期延后 | 业务层重试与恢复任务(含 `startRefundQueue`、`startSubscriptionQueue`)能兜底 |
|
|
358
|
+
| 改动 B 回归后暴露此前被 5× 超频"掩盖"的 race condition | 低 | 视情况 | 合 PR 前在 staging 观察 24h |
|
|
359
|
+
| 改动 D 查询合并写错 WHERE 导致误派发 | 中 | 高(可能提前执行延迟任务)| **暂缓,独立 PR + 对比测试**|
|
|
360
|
+
| `wrangler.jsonc` / `wrangler.json` / `wrangler.staging.json` 多份配置 drift | 已发生 | 中 | 后续合并为 `wrangler.jsonc` + env 派生 |
|
|
361
|
+
| 升级 CF Queues 到付费计划 | 低 | 低 | 不在本次范围内 |
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
### 13. 验证步骤
|
|
366
|
+
|
|
367
|
+
#### 13.1 A 改动上线后
|
|
368
|
+
|
|
369
|
+
- 部署后立刻 tail 日志 `npx wrangler tail staging-aigne-hub-payment-kit`,确认 cron 每 5 分钟触发一次。
|
|
370
|
+
- 2 小时后拉 GraphQL:
|
|
371
|
+
```graphql
|
|
372
|
+
query($a:String!,$s:Time!,$u:Time!){
|
|
373
|
+
viewer{accounts(filter:{accountTag:$a}){
|
|
374
|
+
queueMessageOperationsAdaptiveGroups(limit:500,filter:{datetime_geq:$s,datetime_leq:$u}){
|
|
375
|
+
count dimensions{queueId datetimeHour actionType}
|
|
376
|
+
}
|
|
377
|
+
}}
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
变量 `$a` = ArcBlock account id;`$s` = 2h 前;`$u` = 现在。
|
|
381
|
+
- 预期:当小时 WriteMessage ≤ 50(= 10/min → 换算 ≤ 14.4k/day,仍超但在下降)。
|
|
382
|
+
|
|
383
|
+
#### 13.2 完整 24 小时观测
|
|
384
|
+
|
|
385
|
+
- 次日拉全天 ops,目标 ≤ 3,000/day(write+read+delete 合计)。
|
|
386
|
+
|
|
387
|
+
#### 13.3 业务巡检
|
|
388
|
+
|
|
389
|
+
- 在 staging 触发一笔退款 → 观察 < 30s 到账(immediate 路径不受影响)。
|
|
390
|
+
- 查 `Subscription` 表里 `next_attempt < now()` 的行,确认最多 5 分钟内被处理。
|
|
391
|
+
|
|
392
|
+
#### 13.4 B+C 改动合 PR 前
|
|
393
|
+
|
|
394
|
+
- 补 `shouldRunInWindow` 单测覆盖所有现有 cron 表达式频率。
|
|
395
|
+
- staging 部署 24h,重复 13.2 的数据对比,确认 cron 改回 `* * * * *` 后 ops 依然维持在 ~3-4k。
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
### 14. 附录:查询命令(可复用)
|
|
400
|
+
|
|
401
|
+
> 所有命令需在已加载 `.env.development.local` 的 shell 中执行,环境变量 `CLOUDFLARE_ACCOUNT_ID` 与 `CLOUDFLARE_API_TOKEN` 从该文件读取;**本文档不记录 token 值**。完整策略见 §19。
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
# 列出账号内所有队列
|
|
405
|
+
curl -s "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/queues" \
|
|
406
|
+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | python3 -m json.tool
|
|
407
|
+
|
|
408
|
+
# 指定队列详情(producers/consumers)
|
|
409
|
+
curl -s "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/queues/$QUEUE_ID" \
|
|
410
|
+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | python3 -m json.tool
|
|
411
|
+
|
|
412
|
+
# Worker 的 cron schedules
|
|
413
|
+
curl -s "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/scripts/$WORKER_NAME/schedules" \
|
|
414
|
+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
|
|
415
|
+
|
|
416
|
+
# Worker 的 bindings(含 queue / service / d1 / kv)
|
|
417
|
+
curl -s "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/services/$WORKER_NAME/environments/production/bindings" \
|
|
418
|
+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" | python3 -m json.tool
|
|
419
|
+
|
|
420
|
+
# GraphQL:近 N 天按队列 × 日期 × actionType 的 ops 统计
|
|
421
|
+
SINCE=$(date -u -v-10d +%Y-%m-%dT%H:%M:%SZ); UNTIL=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
422
|
+
curl -s -X POST "https://api.cloudflare.com/client/v4/graphql" \
|
|
423
|
+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" \
|
|
424
|
+
-d "{\"query\":\"query(\$a:String!,\$s:Time!,\$u:Time!){viewer{accounts(filter:{accountTag:\$a}){queueMessageOperationsAdaptiveGroups(limit:1000,filter:{datetime_geq:\$s,datetime_leq:\$u}){count dimensions{queueId date actionType}}}}}\",\"variables\":{\"a\":\"$CLOUDFLARE_ACCOUNT_ID\",\"s\":\"$SINCE\",\"u\":\"$UNTIL\"}}"
|
|
425
|
+
|
|
426
|
+
# GraphQL:Worker 每日请求量
|
|
427
|
+
curl -s -X POST "https://api.cloudflare.com/client/v4/graphql" \
|
|
428
|
+
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" -H "Content-Type: application/json" \
|
|
429
|
+
-d "{\"query\":\"query(\$a:String!,\$s:Time!,\$u:Time!){viewer{accounts(filter:{accountTag:\$a}){workersInvocationsAdaptive(limit:500,filter:{datetime_geq:\$s,datetime_leq:\$u}){dimensions{scriptName date} sum{requests errors subrequests}}}}}\",\"variables\":{\"a\":\"$CLOUDFLARE_ACCOUNT_ID\",\"s\":\"$SINCE\",\"u\":\"$UNTIL\"}}"
|
|
430
|
+
|
|
431
|
+
# 回滚 Worker 到上一个 deployment(紧急回退)
|
|
432
|
+
npx wrangler rollback --name staging-aigne-hub-payment-kit
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
### 17. 详细执行顺序与可行性分析
|
|
438
|
+
|
|
439
|
+
本节展开 §5 摘要里每一步的**技术可行性、业务安全性、回滚机制、预期耗时**,供具体执行人(运维/工程师)参考。
|
|
440
|
+
|
|
441
|
+
#### 17.1 可做(业务安全,本次推荐)
|
|
442
|
+
|
|
443
|
+
**改动 A — 紧急止血:cron 从 `* * * * *` 改成 `*/5 * * * *`**
|
|
444
|
+
|
|
445
|
+
| 属性 | 值 |
|
|
446
|
+
|---|---|
|
|
447
|
+
| 本质 | 配置变更(一行),不改业务代码 |
|
|
448
|
+
| 触发频率变化 | scheduled handler 从 1440/天 → 288/天 |
|
|
449
|
+
| 与现有代码自洽性 | ✅ 和 `shims/cron.ts` 现有 5 分钟窗口天然对齐;所有 `*/N` cron 回到原设计频率 |
|
|
450
|
+
| 对计费代码路径 | 无任何影响(数据路径不过 cron 调度) |
|
|
451
|
+
| 对延迟任务的影响 | 执行时机最多晚 4 分钟 |
|
|
452
|
+
| 可逆性 | 🟢 `wrangler rollback` 1 分钟 |
|
|
453
|
+
| 部署耗时 | 15 分钟(含构建、部署、烟测) |
|
|
454
|
+
| 前置依赖 | 加载 `.env.development.local` 中的 `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` |
|
|
455
|
+
| 风险点 | 仓库存在 4 份 wrangler 配置(`wrangler.json` / `wrangler.jsonc` / `wrangler.staging.json` / `wrangler.local.toml`),必须改 `wrangler.staging.json`;否则"改了不生效" |
|
|
456
|
+
|
|
457
|
+
**改动 B — 根治:`shouldRunInWindow` 窗口 5min → 1min**
|
|
458
|
+
|
|
459
|
+
| 属性 | 值 |
|
|
460
|
+
|---|---|
|
|
461
|
+
| 本质 | 一行逻辑变更 |
|
|
462
|
+
| 触发频率变化 | 所有 `*/N` cron 回到原 Blocklet Server 稳定运行多年的频率 |
|
|
463
|
+
| 与计费业务关系 | 无——只决定"cron 是否在本分钟执行",不决定"cron 做什么" |
|
|
464
|
+
| 对延迟任务 | 无差异(假设 cron 仍每分钟) |
|
|
465
|
+
| 可逆性 | 🟢 revert PR 即可 |
|
|
466
|
+
| 代码风险 | 若 CF Scheduled 偶发丢触发(best-effort 语义),缩窗口后该 cron 周期被跳过;业务层所有重试/恢复机制均能兜底 |
|
|
467
|
+
| 前置依赖 | 单测覆盖 `shouldRunInWindow('0 */5 * * * *')` 等表达式在 60 分钟里恰好匹配 12 次 |
|
|
468
|
+
|
|
469
|
+
**改动 C — 节流:`startRefundQueue` 每 5 分钟**
|
|
470
|
+
|
|
471
|
+
| 属性 | 值 |
|
|
472
|
+
|---|---|
|
|
473
|
+
| 本质 | 新增一个 `if (minute % 5 === 0)` 或将其移至 cron 注册 |
|
|
474
|
+
| 调用频率变化 | 从 1440/天 → 288/天 |
|
|
475
|
+
| 对 refund 金额/次数 | 无——`handleRefund` 顶部有 `status !== 'pending'` 守卫,即使误重推也只执行一次 |
|
|
476
|
+
| 对 refund 延迟 | 正常路径秒级不变;只有 deploy 丢失的 pending refund 自愈延迟从 ≤ 1min → ≤ 5min |
|
|
477
|
+
| 可逆性 | 🟢 revert PR |
|
|
478
|
+
| 代码风险 | 若与改动 A 同时生效,`*/5` cron × `% 5 === 0` → 始终命中 → 节流等于没做。**必须选一种实现**,推荐把 `startRefundQueue` 改成正式 cron job (`time: '0 */5 * * * *'`) 置于 `crons/index.ts`,与 CF 触发器解耦 |
|
|
479
|
+
|
|
480
|
+
#### 17.2 暂缓(本次不做,单独评估)
|
|
481
|
+
|
|
482
|
+
**改动 D — `runAllScheduledJobs` 合并 D1 查询**
|
|
483
|
+
|
|
484
|
+
| 属性 | 评估 |
|
|
485
|
+
|---|---|
|
|
486
|
+
| 收益 | D1 查询从 60/min → 1/min;对队列 ops 影响次要 |
|
|
487
|
+
| 风险 | 🟡 中。30 个队列共用一段 `WHERE`;若 `Op.or` 条件或 `ORDER BY` 写错,可能**提前 dispatch 未到期的延迟任务** |
|
|
488
|
+
| 业务后果 | 订阅续费提前触发、stripe sync 提前跑、notification 提前发——**可能破坏依赖"精确时间点"的业务语义** |
|
|
489
|
+
| 决策 | 告警解除后独立 PR,附对比测试:构造同样数据集,分别跑原循环查询和合并查询,断言两者返回集完全一致 |
|
|
490
|
+
|
|
491
|
+
**其它暂缓项**
|
|
492
|
+
|
|
493
|
+
| 项目 | 暂缓原因 |
|
|
494
|
+
|---|---|
|
|
495
|
+
| `wrangler queues pause-delivery` | 立竿见影降 ops,但消息积压 24h 后被 CF 自动丢弃(`message_retention_period = 86400`),可能导致真实 refund/notification 永久丢失 → **业务不可接受** |
|
|
496
|
+
| 升级 CF Queues 付费计划 | 长期方案但不解根因;根因修完后 ops 在免费额度 10× 以内,不必升级 |
|
|
497
|
+
| 删除任何生产资源(队列/Worker) | 本次告警源头是 ArcBlock 账号的活跃 Worker,不动它 |
|
|
498
|
+
| 修改 `message_retention_period` / `max_batch_size` | 不是根因,调参只是掩盖问题 |
|
|
499
|
+
|
|
500
|
+
#### 17.3 推荐执行顺序(含风险/回滚)
|
|
501
|
+
|
|
502
|
+
| 步骤 | 动作 | 耗时 | 业务风险 | 回滚路径 | 前提 |
|
|
503
|
+
|---|---|---|---|---|---|
|
|
504
|
+
| 1 | 改动 A 上线 `wrangler.staging.json` cron → `*/5` + redeploy | 15 min | 🟢 低(延迟 ≤ 5min) | `wrangler rollback`,1 min | 有 ArcBlock 账号 API Token |
|
|
505
|
+
| 2 | 观察 24h 队列 ops GraphQL 曲线 | 24h | —— | —— | 执行 §14 GraphQL 命令 |
|
|
506
|
+
| 3 | 开 PR 实现改动 B + 改动 C(合一个 PR)+ 单测 | 0.5-1 天 | 🟢 低 | revert PR | code review 通过 |
|
|
507
|
+
| 4 | staging 部署改动 B+C,cron 暂仍为 `*/5`,观察 24h | 24h | 🟢 低 | 同步骤 1 | —— |
|
|
508
|
+
| 5 | 把 cron 改回 `* * * * *`,redeploy | 15 min | 🟢 低 | 同步骤 1 | 步骤 4 观测 ops 无回升 |
|
|
509
|
+
| 6 | 观察 7 天,确认 ops 稳定 < 5k/天 | 7 天 | —— | —— | —— |
|
|
510
|
+
| 7 | (可选)评估改动 D 是否做 | —— | —— | —— | 步骤 6 数据达标后再议 |
|
|
511
|
+
| 8 | 补告警监控(Slack/Email 每日 ops 报告) | 1 天 | 🟢 低 | —— | —— |
|
|
512
|
+
|
|
513
|
+
#### 17.4 禁止事项
|
|
514
|
+
|
|
515
|
+
- ❌ **绝不**一次 PR 把 A+B+C+D 全改上线。合并风险远大于收益。
|
|
516
|
+
- ❌ **绝不**在未加载 `.env.development.local` 的情况下运行 `wrangler deploy`——可能误部署到错误的账号。
|
|
517
|
+
- ❌ **绝不**改动任何 `blocklets/core/api/src/queues/*.ts` 里 `handle*Job` 的金额/状态迁移逻辑。
|
|
518
|
+
- ❌ **绝不**调整 `handleRefund`、`handleSubscriptionJob`、`handleDepositVaultJob` 的幂等守卫(`if (status !== 'pending') return` 等)。
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
### 18. 承诺阈值(SLA Commitments)
|
|
523
|
+
|
|
524
|
+
本次修复对业务方/产品方的可量化承诺。签字生效后,代码 reviewer 必须确保实现方案不会违反这些阈值。
|
|
525
|
+
|
|
526
|
+
#### 18.1 硬约束(违反即必须拒绝合并 PR)
|
|
527
|
+
|
|
528
|
+
| # | 承诺项 | 阈值 |
|
|
529
|
+
|---|---|---|
|
|
530
|
+
| H-1 | **任何用户的扣款金额、扣款次数** | 不因本次修复而变化。0 个差异。 |
|
|
531
|
+
| H-2 | **订阅续费的目标金额、周期、币种** | 不变。 |
|
|
532
|
+
| H-3 | **退款金额** | 不变。总额与次数 0 差异。 |
|
|
533
|
+
| H-4 | **链上转账(归集、stake 退还、credit burn)总笔数** | 不因修复新增或减少任何一笔。 |
|
|
534
|
+
| H-5 | **历史 `payment_intents` / `invoices` / `refunds` / `balance_transactions` 记录** | 不触碰,不重写。 |
|
|
535
|
+
| H-6 | **DLQ 消息数** | 修复后相对修复前 **不增加**(可以减少或持平)。 |
|
|
536
|
+
| H-7 | **对账报表数据完整性** | 修复前后同一时间窗查询出的 sum(amount) 相等。 |
|
|
537
|
+
|
|
538
|
+
#### 18.2 软约束(允许范围内漂移)
|
|
539
|
+
|
|
540
|
+
| # | 指标 | 修复前 | 承诺阈值(修复后) |
|
|
541
|
+
|---|---|---|---|
|
|
542
|
+
| S-1 | 延迟任务(订阅重试、退款重试)的执行时刻漂移 | < 1 min | **≤ 5 min**(改动 A 阶段),**≤ 1 min**(完成 B 后) |
|
|
543
|
+
| S-2 | 续费提前通知邮件发送时刻偏差 | < 6 h 内准时 | **≤ 6 h ± 5 min** |
|
|
544
|
+
| S-3 | 自动金库归集检查间隔 | 1 min | **≤ 5 min** |
|
|
545
|
+
| S-4 | AI 用量扣费批处理窗口 | 被 bug 压缩到 ~1 min | 恢复到设计值 **10 min** |
|
|
546
|
+
| S-5 | Stripe 同步延迟 | 最多 `*/20` 或 `*/30` 设计值 | 同设计值,**无退化** |
|
|
547
|
+
|
|
548
|
+
#### 18.3 运维约束
|
|
549
|
+
|
|
550
|
+
| # | 指标 | 阈值 |
|
|
551
|
+
|---|---|---|
|
|
552
|
+
| O-1 | CF Queue 日 ops(稳态)| ≤ 5,000 / day |
|
|
553
|
+
| O-2 | CF Queue 月度 ops(占 1M 免费额度)| ≤ 20% |
|
|
554
|
+
| O-3 | 告警触发频次 | 修复后 30 天内 = 0 |
|
|
555
|
+
| O-4 | Worker 每次 scheduled 事件 CPU 时间 | ≤ 30s(CF 限制 30s) |
|
|
556
|
+
| O-5 | DLQ 消息 retention | 保持 1 天(不调) |
|
|
557
|
+
|
|
558
|
+
**违反任一硬约束(H-*)→ 不合并 PR;**
|
|
559
|
+
**违反任一软约束(S-*)超过 ±50% → 需 product owner 审批;**
|
|
560
|
+
**违反任一运维约束(O-*)→ 触发回滚流程。**
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
### 19. 敏感信息与环境变量约定
|
|
565
|
+
|
|
566
|
+
#### 19.1 本文档的敏感信息策略
|
|
567
|
+
|
|
568
|
+
> **文档仅引用环境变量 NAME,不落盘 VALUE。** 任何包含 token / secret / password 的值都**不得**写入本文档或任何 git 跟踪文件。
|
|
569
|
+
|
|
570
|
+
本文档中 `$CLOUDFLARE_API_TOKEN`、`$CLOUDFLARE_ACCOUNT_ID`、`$WORKER_NAME`、`$QUEUE_ID` 等以 `$NAME` 形式出现的引用,约定由读者自行在本地 shell 中加载,来源如下表:
|
|
571
|
+
|
|
572
|
+
| 变量名 | 是否敏感 | 来源 | 说明 |
|
|
573
|
+
|---|---|---|---|
|
|
574
|
+
| `CLOUDFLARE_API_TOKEN` | ✅ **高度敏感** | `blocklets/core/.env.development.local`(**本地、git-ignored**)| 具备 queue/worker/d1 写权限,泄露等于账号被接管。绝不落盘。 |
|
|
575
|
+
| `CLOUDFLARE_ACCOUNT_ID` | ⚠️ 非敏感但区分环境 | 同上,或 CF Dashboard URL | ArcBlock 主账号 ID 在本文档 §7/§8 已明示(属 Worker 配置的一部分,出现在 wrangler.json)|
|
|
576
|
+
| `WORKER_NAME` | ⚠️ 非敏感 | `wrangler.staging.json` 中 `name` 字段 | 如 `staging-aigne-hub-payment-kit` |
|
|
577
|
+
| `QUEUE_ID` | ⚠️ 非敏感 | `wrangler queues list` 输出 | 用于 GraphQL 过滤 |
|
|
578
|
+
|
|
579
|
+
#### 19.2 使用流程(执行本文档任何命令前)
|
|
580
|
+
|
|
581
|
+
```bash
|
|
582
|
+
# 方法 1:临时导出(仅当前 shell 生效,关窗即失效,推荐)
|
|
583
|
+
export $(grep -v '^#' blocklets/core/.env.development.local | xargs -I{} echo {})
|
|
584
|
+
|
|
585
|
+
# 方法 2:按需只取这两个变量
|
|
586
|
+
export CLOUDFLARE_ACCOUNT_ID=$(grep '^CLOUDFLARE_ACCOUNT_ID=' blocklets/core/.env.development.local | cut -d= -f2)
|
|
587
|
+
export CLOUDFLARE_API_TOKEN=$(grep '^CLOUDFLARE_API_TOKEN=' blocklets/core/.env.development.local | cut -d= -f2)
|
|
588
|
+
|
|
589
|
+
# 验证(应输出 "✓ token loaded",不打印 token 值)
|
|
590
|
+
[ -n "$CLOUDFLARE_API_TOKEN" ] && echo "✓ token loaded"
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
#### 19.3 泄露应急处理
|
|
594
|
+
|
|
595
|
+
若 token 意外被提交到 git / 粘贴到 chat / 出现在日志:
|
|
596
|
+
|
|
597
|
+
1. 立即在 Cloudflare Dashboard → My Profile → API Tokens 中 **Revoke** 该 token。
|
|
598
|
+
2. 生成新 token,更新 `.env.development.local`。
|
|
599
|
+
3. 清洗 git 历史(`git filter-branch` 或 BFG);若已 push 到远端,通知 GitHub 做 secret scanning。
|
|
600
|
+
4. 在 `#security` 频道报备。
|
|
601
|
+
|
|
602
|
+
#### 19.4 文档审阅 checklist
|
|
603
|
+
|
|
604
|
+
合并/更新本文档时,reviewer 必须逐项核对:
|
|
605
|
+
|
|
606
|
+
- [ ] 无 token / secret / password 明文
|
|
607
|
+
- [ ] 无数据库连接串 / API key 明文
|
|
608
|
+
- [ ] 无 JWT / session token 明文
|
|
609
|
+
- [ ] 所有 `$NAME` 引用在 §14/§19.1 或等价位置有说明
|
|
610
|
+
- [ ] 示例命令默认从 env 读取变量,不硬编码值
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
### 20. 分析过程贡献记录
|
|
615
|
+
|
|
616
|
+
| 环节 | 结论贡献者 | 证据 |
|
|
617
|
+
|---|---|---|
|
|
618
|
+
| 代码走读锁定 `shouldRunInWindow` 5× 放大 | Claude(代码分析)| `shims/cron.ts:107-113` + 各 cron 表达式 |
|
|
619
|
+
| 质疑 "是 queue 告警吗?" 转方向用线上数据证伪/证真 | Pengfei | 告警截图(CF 邮件)+ 提供 env token |
|
|
620
|
+
| GraphQL 拉到 10 天日度 + 小时级分布 | Claude(API 调用)| CF GraphQL Analytics `queueMessageOperationsAdaptiveGroups` |
|
|
621
|
+
| 确认 ArcBlock 主账号而非个人账号 | Pengfei | 告警邮件指向 "(ArcBlock)" |
|
|
622
|
+
| 幂等安全性核对 | Claude(查 `handleRefund`、`handleSubscriptionJob`、`handleDepositVaultJob`)| `blocklets/core/api/src/queues/*.ts` 各 onJob 顶部 status 守卫 |
|
|
623
|
+
| 业务场景示例(订阅/退款/credit)| 共同拆解 | 代码 + env.ts cron 常量 |
|
|
624
|
+
| §17 可行性 / §18 承诺阈值 / §19 敏感信息约定 | Pengfei 要求 + Claude 起草 | 本次对话回顾 |
|
|
625
|
+
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
### 21. 变更记录
|
|
629
|
+
|
|
630
|
+
| 日期 | 内容 | 状态 |
|
|
631
|
+
|---|---|---|
|
|
632
|
+
| 2026-04-17 | 根因定位 + 方案初稿 + 线上数据采集 | ✅ 完成 |
|
|
633
|
+
| 2026-04-17 | 本文档产出 v1 | ✅ 完成 |
|
|
634
|
+
| 2026-04-17 | v2:补入 §17 详细可行性、§18 承诺阈值、§19 敏感信息约定;章节重编号 §20 §21 | ✅ 完成 |
|
|
635
|
+
| 2026-04-17 13:13 UTC | 改动 A 上线(通过 CF API PUT schedules,未重部署代码):cron `* * * * *` → `*/5 * * * *`。当前活跃 deployment_id=`88e327a2-e22f-4d77-bc7f-5bb9df638f96` / version_id=`2b0d89b9-e0cf-4a6b-81b1-236ec2315ab5`(Shijun 2026-04-15 部署的版本,未动)。repo 中 `wrangler.staging.json:64` 已同步改为 `*/5`(未 commit)。| ✅ 完成 |
|
|
636
|
+
| 2026-04-18 | Plan 2 阶段性数据(04-18 前 2h):04-18 00:00-02:00 UTC 队列 ops = 2,472,方向符合预期,授权开始 Plan 3+4 代码工作。| ✅ 阶段性判据通过 |
|
|
637
|
+
| 2026-04-18 | 改动 B(`shims/cron.ts:107-113` 窗口 5min→1min)+ TDD 单测(`cloudflare/tests/shims/cron.spec.ts`,30 测试全过)| ✅ 本地完成 |
|
|
638
|
+
| 2026-04-18 | 改动 C(`startRefundQueue` 迁移到 `crons/index.ts` 注册为 `refund.recovery` cron job,时间表 `0 */5 * * * *`;`worker.ts:1402-1403` 的硬编码调用已删除)| ✅ 本地完成 |
|
|
639
|
+
| 2026-04-18 | PR #1341 已提交(分支 `fix/cf-queues-cron-window-bug`,4 commits:改动 B / 改动 C / wrangler.staging 同步 / 本文档)| ✅ 已开 PR |
|
|
640
|
+
| 2026-04-19 | Plan 2 完整 24h 复核:04-18 全天 10,007 ops、04-19 全天 10,483 ops,**稳态降幅 ~23-27%**(远低于初期推算的 80%)。原因:ops 主要来自业务事件批量 push,非 cron 稳态空跑;Plan 1 只改了 cron 频率,未修代码级 5× 放大 bug,故降幅与 cron 次数降幅部分叠加但有限。| ✅ 数据校准 |
|
|
641
|
+
| 2026-04-19 | 新发现:Worker `exceededResources` 事件持续高发。04-17:1,022 次(35% 超限率);04-18:309 次;04-19:300 次 = 每小时 12 次 = 精确匹配 `*/5` cron 频率,意味着**每一次 scheduled handler 触发都 CPU/subrequest 超限被 CF 截断**。影响:`runAllScheduledJobs` 可能扫不完 30 个队列(靠后的队列永远扫不到)→ 业务侧延迟任务(refund 重试、subscription 续费重试等)存在被静默拖延的风险。这把 PR #1341 的定位从"预防性 cleanup"升级为"修复 scheduled handler 可靠性 + 降低 ops"。| ⚠️ 新发现 |
|
|
642
|
+
| 2026-04-20 | 月度累计复核:payment-kit-jobs 本月 101,306 ops + DLQ 35,合计约 101k / 1M 免费额度 = **10.1%**。按当前 ~10.5k/day 外推月底约 300k / 1M = 30%,**不会再触发 95% 告警**。告警风险短期解除,但代码级根因未修。| ✅ 阈值下行 |
|
|
643
|
+
| 2026-04-20 | PR #1341 review 反馈(P1-1,有效):shim 1min 窗口 × CF 触发 `*/5` 会让非 5 对齐的 cron(如 `paymentStatCronTime='0 1 0 * * *'`、`expiredSessionCleanupCronTime='0 1 * * * *'`)永不运行。修复:新增 commit 把 `wrangler.staging.json` 回到 `* * * * *`,使部署后 CF 触发和 shim 窗口同时恢复到设计态(1min × 1min);新增 4 条非 5 对齐 cron 的守卫测试。Plan 6(回 `*/1` cron)已折叠进 PR 本身。| ✅ 按 review 修复 |
|
|
644
|
+
| 2026-04-20 | PR #1341 review 反馈(P1-2,非本 PR 引入):`patch-user-card.ts` 的非存在导入路径来自独立 commit `e9764188`(2026-04-18 00:41 UTC),本地 `feat/cf-workers-migration` 已有但未 push 到 origin,故 PR diff 把它一并显示。处置:由该 commit 作者独立评估路径有效性,不在本 PR 范围。| ℹ️ 范围说明 |
|
|
645
|
+
| 2026-04-20 | PR #1341 第二轮 review(P1-2 retracted):reviewer 验证 `@arcblock/ux/lib/node_modules/@blocklet/js-sdk/dist/index.js` 在实际安装布局中确实存在(UserCard/utils.js 自己就这么导入),故撤回 build blocker 判定。| ✅ 核实 |
|
|
646
|
+
| 2026-04-20 | PR #1341 review P2-1 修复:`cronInstance.runAll()` 改为接受可选 `now` 参数;`worker.ts` scheduled handler 传入 `new Date(event.scheduledTime)`,避免 CF 晚投递导致跨分钟边界时匹配错位。新增 2 条集成测试覆盖该场景(测试总数 34 → 36 全过)。| ✅ 按 review 修复 |
|
|
647
|
+
| 2026-04-20 | PR #1341 review P2-2 修复:更新文档 §3(改动清单改为"历史/当前/最终"三态表)、§3.1(新增目标终态声明)、§4(指标对比加入 04-16 基线/应急态/预期三列)、§5(执行步骤按实际进度重写)。明确 `*/5` cron 只是 2026-04-17 当日的历史应急,PR 合入后不应再执行。| ✅ 按 review 修复 |
|
|
648
|
+
| (待定)| PR #1341 review(建议由 Shijun,他最熟 CF 部署)| ⏳ 等外部 |
|
|
649
|
+
| (待定)| Plan 5 staging 部署 + 冒烟测 + 2h/24h 数据验证(含 exceededResources 降幅核查)| ⏳ 等 PR 合入 |
|
|
650
|
+
| (待定)| Plan 6 cron 回到 `*/1` 恢复精度 | ⏳ 等 Plan 5 稳定 24h |
|
|
651
|
+
| (待定)| Plan 7 日度 ops 监控接入 | ⏳ 随时可做 |
|
|
652
|
+
| (待定)| Plan 8 `runAllScheduledJobs` D1 查询合并(可能从暂缓提前为必做,取决于 Plan 12 验证结果)| ⏳ 条件触发 |
|
|
653
|
+
| (待定)| Plan 9 wrangler 四份配置统一 | ⏳ 等 Plan 6 |
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
### 17 / 18 / 19 的读者快速索引
|
|
658
|
+
|
|
659
|
+
- 要**决定做不做某一项改动**? → §17.1 / §17.2
|
|
660
|
+
- 要**给业务方/上级汇报这次修复"承诺不会出错什么"**? → §18.1 硬约束
|
|
661
|
+
- 要**向测试或代码 reviewer 说明"合什么样的 PR 才安全"**? → §17.4 禁止事项 + §18 全部
|
|
662
|
+
- 要**在本地跑查询命令但不知道变量怎么来**? → §19.2 使用流程
|
|
663
|
+
- 要**确认本文档是否有泄密风险**? → §19.4 审阅 checklist
|