Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ ENABLE_API_KEY_REDIS_CACHE="true" # 是否启用 API Key Redis 缓存(
# 降低该值会按签发时间收紧已签发 ADMIN_TOKEN 签名 cookie 的剩余寿命,且不会延长其原始 exp。
AUTH_SESSION_TTL_SECONDS=604800 # Web UI 登录态过期时间(秒,默认 604800 = 7 天,范围 60-31536000)
SESSION_TTL=300 # 代理请求上下文缓存时间(秒,默认 300 = 5 分钟;不控制 Web UI 登录态)
SESSION_SNAPSHOT_ROOT=./data/session-snapshots # filesystem Session 快照目录
STORE_SESSION_MESSAGES=false # 会话消息存储模式(默认:false)
# - false:存储请求/响应体但对 message 内容脱敏 [REDACTED]
# - true:原样存储 message 内容(注意隐私和存储空间影响)
Expand Down Expand Up @@ -209,6 +210,8 @@ ENDPOINT_PROBE_SCHEDULER_ENABLED=true
# ENDPOINT_PROBE_INTERVAL_MS controls the base interval. Single-vendor interval is fixed.
ENDPOINT_PROBE_INTERVAL_MS=60000
ENDPOINT_PROBE_TIMEOUT_RETRY_INTERVAL_MS=10000
# Consecutive failures back off exponentially from the timeout retry interval, capped here.
ENDPOINT_PROBE_FAILURE_BACKOFF_MAX_MS=600000
# When no endpoints are due, scheduler will still poll DB periodically to pick up config changes.
# Default: min(ENDPOINT_PROBE_INTERVAL_MS, 30000)
ENDPOINT_PROBE_IDLE_DB_POLL_INTERVAL_MS=30000
Expand Down
9 changes: 7 additions & 2 deletions deploy/k8s/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
deploy/k8s/
├── namespace.yaml # 命名空间
├── app/ # 应用层
│ ├── deployment.yaml # Deployment (2 副本基线;迁移由 PG advisory lock 串行化)
│ ├── deployment.yaml # Deployment (2 副本基线;Session 快照使用节点 hostPath)
│ ├── service.yaml # Service (可渲染为 ClusterIP/NodePort)
│ ├── hpa.yaml # HPA (CPU 70% / 内存 80%)
│ ├── hpa.yaml # HPA (CPU 70%)
│ ├── pdb.yaml # PodDisruptionBudget (maxUnavailable=1)
│ └── networkpolicy.yaml # NetworkPolicy (仅在 Ingress 模式应用)
├── postgres/ # PostgreSQL StatefulSet
Expand Down Expand Up @@ -54,6 +54,11 @@ deploy/k8s/
| `{{INGRESS_CLASS}}` | Ingress className | 自动探测 |
| `{{TIMEZONE}}` | 容器时区 | `Asia/Shanghai` |

> App 的 filesystem Session 快照不使用 PVC,而是挂载节点本地
> `/var/lib/claude-code-hub/session-snapshots` hostPath。这个目录只在同一节点上的 Pod 间共享;
> 默认配置适用于单节点 k3s,或明确保证所有 App Pod 位于同一节点的部署。多节点集群应在系统设置中
> 切换为 Redis,或自行提供真正的共享文件系统。PostgreSQL/Redis 仍使用 StorageClass/PVC。

Comment on lines +57 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

引用块间的空行触发 markdownlint MD028

第 57-60 行与 62-63 行是两个 > 引用块,中间用一个普通空行分隔,markdownlint-cli2 报告 MD028 (no-blanks-blockquote):不同渲染器对此处理不一致,有些会把它们合并为同一个引用块并保留内部空行,有些则视为两个独立块。

📝 建议修复
 > 默认配置适用于单节点 k3s,或明确保证所有 App Pod 位于同一节点的部署。多节点集群应在系统设置中
 > 切换为 Redis,或自行提供真正的共享文件系统。PostgreSQL/Redis 仍使用 StorageClass/PVC。
-
+>
 > NodePort 回落模式下,`scripts/deploy-k8s.sh` 会自动跳过 `app/networkpolicy.yaml`,

As per static analysis hints: [warning] 61-61: Blank line inside blockquote (MD028, no-blanks-blockquote)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
> App 的 filesystem Session 快照不使用 PVC,而是挂载节点本地
> `/var/lib/claude-code-hub/session-snapshots` hostPath。这个目录只在同一节点上的 Pod 间共享;
> 默认配置适用于单节点 k3s,或明确保证所有 App Pod 位于同一节点的部署。多节点集群应在系统设置中
> 切换为 Redis,或自行提供真正的共享文件系统。PostgreSQL/Redis 仍使用 StorageClass/PVC。
> App 的 filesystem Session 快照不使用 PVC,而是挂载节点本地
> `/var/lib/claude-code-hub/session-snapshots` hostPath。这个目录只在同一节点上的 Pod 间共享;
> 默认配置适用于单节点 k3s,或明确保证所有 App Pod 位于同一节点的部署。多节点集群应在系统设置中
> 切换为 Redis,或自行提供真正的共享文件系统。PostgreSQL/Redis 仍使用 StorageClass/PVC。
>
> NodePort 回落模式下,`scripts/deploy-k8s.sh` 会自动跳过 `app/networkpolicy.yaml`,
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 61-61: Blank line inside blockquote

(MD028, no-blanks-blockquote)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/k8s/README.md` around lines 57 - 61, Remove the blank line separating
the adjacent blockquote lines in the README so the entire note remains one
continuous blockquote and satisfies markdownlint MD028.

Source: Linters/SAST tools

> NodePort 回落模式下,`scripts/deploy-k8s.sh` 会自动跳过 `app/networkpolicy.yaml`,
> 避免默认的 Ingress 命名空间白名单阻断外部访问。

Expand Down
30 changes: 21 additions & 9 deletions deploy/k8s/app/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ spec:
- name: AUTO_MIGRATE
value: "true"
- name: DB_POOL_MAX
value: "24"
value: "8"
- name: DB_POOL_IDLE_TIMEOUT
value: "20"
- name: DB_POOL_CONNECT_TIMEOUT
Expand All @@ -78,6 +78,10 @@ spec:
value: "604800"
- name: SESSION_TTL
value: "300"
- name: SESSION_SNAPSHOT_ROOT
value: /var/lib/claude-code-hub/session-snapshots
- name: DASHBOARD_LOGS_POLL_INTERVAL_MS
value: "10000"
- name: MESSAGE_REQUEST_WRITE_MODE
value: "async"
- name: MESSAGE_REQUEST_ASYNC_FLUSH_INTERVAL_MS
Expand All @@ -90,30 +94,30 @@ spec:
value: {{TIMEZONE}}
resources:
requests:
cpu: 500m
memory: 512Mi
cpu: 250m
memory: 2Gi
limits:
cpu: "4"
memory: 4Gi
cpu: "2"
memory: 5Gi
livenessProbe:
httpGet:
path: /api/actions/health
path: /api/health/live
port: 3000
initialDelaySeconds: 15
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /api/actions/health
path: /api/health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
timeoutSeconds: 5
failureThreshold: 2
startupProbe:
httpGet:
path: /api/actions/health
path: /api/health/live
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
Expand All @@ -131,3 +135,11 @@ spec:
capabilities:
drop:
- ALL
volumeMounts:
- name: session-snapshots
mountPath: /var/lib/claude-code-hub/session-snapshots
volumes:
- name: session-snapshots
hostPath:
path: /var/lib/claude-code-hub/session-snapshots
type: DirectoryOrCreate
Comment on lines +138 to +145

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

不要用 hostPath 作为多副本的共享快照存储。

hostPath 仅在单个节点本地持久化;副本被调度或重调度到不同节点后,Session 快照会丢失或无法读取,与 SESSION_SNAPSHOT_ROOT 的共享持久化契约不符。请改用共享 PVC(RWX),或在多节点部署时选择 Redis 快照后端。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/k8s/app/deployment.yaml` around lines 138 - 145, Replace the
session-snapshots hostPath volume in the deployment’s volumeMounts/volumes
configuration with a shared RWX PVC, and configure the PVC-backed storage for
SESSION_SNAPSHOT_ROOT so replicas can access the same snapshots across nodes.
Alternatively, when multi-node deployment uses the Redis snapshot backend,
remove this local hostPath dependency and apply the corresponding Redis
configuration.

6 changes: 0 additions & 6 deletions deploy/k8s/app/hpa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ spec:
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
Expand Down
24 changes: 18 additions & 6 deletions docs/k8s-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
└────────┬─────────┘
┌────────▼─────────┐
│ Deployment │ HPA 2~6 副本,CPU 70% / Memory 80%
│ Deployment │ HPA 2~6 副本,CPU 70%
│ claude-code-hub │ PDB maxUnavailable=1
└────┬─────────┬────┘
│ │
Expand All @@ -31,11 +31,11 @@
| 维度 | Docker Compose | Kubernetes |
|------|----------------|-----------|
| 高可用 | 单容器 | HPA + PDB,滚动更新不中断 |
| 存储 | 本地卷 | PVC (由集群 StorageClass 管理) |
| 存储 | 本地卷 | PostgreSQL/Redis 使用 PVC;Session 快照默认使用节点 hostPath |
| 域名 | Caddy (可选) | Ingress / Traefik IngressRoute / NodePort |
| 密钥 | `.env` 文件 | Kubernetes Secret |
| 升级 | `docker compose pull` | `cch update` (带迁移 + 回滚) |
| 适用 | 个人/小团队 | 生产 / 多节点 / 企业 |
| 适用 | 个人/小团队 | 单节点 k3s;多节点需为 Session 快照改用 Redis 或共享存储 |

---

Expand All @@ -59,6 +59,11 @@
- **macOS** 只用于管理端 (kubectl),集群侧建议 Linux
- 磁盘 ≥ 80GB (PostgreSQL 50GB + Redis 10GB + 系统)

> 默认 `sessionSnapshotStore=filesystem`,App Pod 挂载节点本地
> `/var/lib/claude-code-hub/session-snapshots`。普通 `hostPath` 不跨节点共享,因此默认模板要求单节点,
> 或由运维明确保证所有 App Pod 调度到同一节点。多节点集群必须把 Session 快照切换为 Redis,
> 或自行替换为共享文件系统;数据库 PVC 不受这一限制。

### 集群选型

**选项 A — 单机 k3s (推荐新手 / 家用 / 自建)**
Expand Down Expand Up @@ -213,6 +218,9 @@ bash scripts/deploy-k8s.sh --replicas 3 --hpa-min 3 --hpa-max 10 -y
默认模板保留 `replicas=2`,但 `AUTO_MIGRATE` 入口 `src/instrumentation.ts` 会先获取 PostgreSQL advisory lock,
因此首次多副本启动时迁移会串行执行。如果你更关心首启速度,也可以先用 `--replicas 1` 部署,确认健康后再扩容。

filesystem Session 快照的 `hostPath` 只能在同一节点上的副本间共享。不要在普通多节点调度下依赖它
读取其他节点写入的快照;多节点部署请在 Dashboard 系统设置中切换到 Redis,或提供共享文件系统。

### Codex `/v1/responses` WebSocket 反代

`/v1/responses` 端点对 Codex 客户端会走 **WebSocket 升级**(其余路径仍是 HTTP)。
Expand Down Expand Up @@ -307,7 +315,9 @@ WS,务必把 `/v1/responses` 排除在缓存规则之外。

### 修改应用环境变量

`deploy/k8s/app/deployment.yaml` 里枚举了常用环境变量(连接池、超时、限流开关、MESSAGE_REQUEST 批量参数等)。建议流程:
`deploy/k8s/app/deployment.yaml` 里枚举了常用环境变量(连接池、超时、限流开关、MESSAGE_REQUEST 批量参数等)。
当前 App 默认 `DB_POOL_MAX=8`、`DASHBOARD_LOGS_POLL_INTERVAL_MS=10000`,Session 快照根目录为
`/var/lib/claude-code-hub/session-snapshots`。建议流程:

1. 修改 `deploy/k8s/app/deployment.yaml` 模板
2. 运行 `bash scripts/deploy-k8s.sh -y` 走升级分支(自动保留 Secret)
Expand Down Expand Up @@ -435,7 +445,7 @@ cch backup

```bash
# 1. 停 app (避免写入):先删掉 HPA,再直接 kubectl 缩到 0 (cch scale 要求 >=1)
# CPU/内存 HPA 不支持直接把 minReplicas 改成 0
# HPA 不支持直接把 minReplicas 改成 0
kubectl -n claude-code-hub delete hpa claude-code-hub 2>/dev/null || true
kubectl -n claude-code-hub scale deployment/claude-code-hub --replicas=0

Expand Down Expand Up @@ -540,7 +550,7 @@ deploy/k8s/
├── app/
│ ├── deployment.yaml # replicas, env, resources, 3 个 probe, preStop sleep
│ ├── service.yaml # ClusterIP / NodePort
│ ├── hpa.yaml # CPU 70% / 内存 80%, scaleUp/Down 策略
│ ├── hpa.yaml # CPU 70%, scaleUp/Down 策略
│ ├── pdb.yaml # maxUnavailable=1
│ └── networkpolicy.yaml # 仅在 Ingress 模式应用
├── postgres/
Expand Down Expand Up @@ -586,6 +596,8 @@ deploy/k8s/
| 端口 | `23000:3000` | Ingress / NodePort |
| 持久化 | `./data/postgres`、`./data/redis` | PVC (StorageClass) |

App 的 Session 快照是例外:默认使用节点本地 hostPath,不新增 PVC。它只保证同节点共享,不提供跨节点一致性。

### 进一步阅读

- 主 README: [README.md](../README.md)
Expand Down
139 changes: 139 additions & 0 deletions docs/performance-optimization-roadmap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# CCH 性能优化落地方案

## 1. 结论

本轮性能问题的主因不是 CPU 或宿主机算力不足,而是上游低质量请求被重试、hedge、长超时和大上下文放大后,进一步推高 Node 常驻内存、Redis 大对象、连接池和多副本后台任务数量。

本轮优先落地不新增 Pod、sidecar、PVC、Redis、PgBouncer、CronJob 或其他外部组件的优化。实施原则是先消除确定性的资源放大器,再为重试总 deadline 和 AvailableModels 上游缓存建立独立契约,避免在一个 PR 中同时改变多套转发语义。

## 2. 本轮已落地

### 2.1 TTFT 与 TTFB 分离

- `TTFB` 定义为网关收到请求到最终 winner 的响应头到达。
- `TTFT` 定义为网关收到请求到首个协议有效内容到达,不把 SSE comment、metadata、usage-only、空 delta 或 terminal frame 当作首 token。
- 新口径写入 `timingSemanticsVersion = 2`。历史混合口径不回填,不参与 TTFB、TTFT 和 TPS 聚合。
- 输出速率使用 `outputTokens / (durationMs - ttftMs)`。TTFT 不可用时输出速率也不可用。
- 请求详情、普通日志表、虚拟日志表、排行榜、Public Status 和 Langfuse 均使用统一口径。

收益:避免把旧 TTFT 数值错误展示为 TTFB,也避免用错误的首字时间计算输出速率和排行榜指标。

### 2.2 排行榜成功率和缓存系数

- redirect 后的供应商和模型继续计算成功率,并标记统计口径为 original 或 redirected。
- 只有没有可计数 outcome 时才显示“不适用”。
- 流式、非流式和 Gemini terminal 路径均写入 cache effectiveness 所需字段。
- 聚合任务使用持久 cursor、幂等窗口 upsert 和数据库 advisory lock,避免多实例重复聚合及重复窗口。

收益:修复排行榜成功率全部“不适用”和缓存系数缺失,同时把缓存效果聚合从副本级重复工作收敛为单次有效工作。

### 2.3 Session 快照默认落盘

- 系统设置支持 `filesystem`、`redis` 和 `disabled`,默认 `filesystem`。
- filesystem store 使用 gzip level 1、单异步 worker、atomic rename、文件锁和跨 Pod 清理锁。
- TTL 继承 `SESSION_TTL`,过期文件自动删除。
- 默认单逻辑快照上限 8 MiB、单 Pod pending 上限 64 MiB、目录上限 10 GiB。
- 写入在请求热路径上只做有界入队;超大快照、队列预算不足、损坏文件或磁盘读取失败均 fail-open,不阻塞网关转发。
- backend 切换会原子更新 settings cache 并重配置 store,避免保存设置后短暂回退到 filesystem。

收益:把完整大请求快照从 Redis 内存和 AOF 中移出,同时以明确的大小、队列和目录预算限制本地磁盘风险。

限制:Kubernetes 默认使用节点本地 `hostPath`,只保证同一节点上的副本可共享。多节点部署应切换为 Redis 或自行提供共享文件系统。本轮不新增共享存储组件。

### 2.4 多副本后台任务治理

- cache effectiveness 使用 PostgreSQL transaction advisory lock。
- cloud price sync、replay cleanup 和 probe-log cleanup 使用 PostgreSQL advisory lock,并在锁被占用时直接跳过。
- Redis leader lock 在 production 中 fail-closed;Redis 未 ready 或获取异常时不回退为每 Pod 的 memory lock。
- replay 和 cache effectiveness scheduler 增加防重入、current promise、stop flag 和有界 shutdown quiescence。
- shutdown 在关闭 PostgreSQL 和 Redis 前先停止 scheduler 并等待在途任务退出。

收益:副本数量增加时,聚合、清理和同步任务不再按副本线性放大,也避免资源关闭期间后台任务继续访问已关闭连接。

### 2.5 Endpoint probe 指数退避

- endpoint 保存连续探测失败次数。
- 成功后归零;失败时由 SQL 原子加一。
- 调度间隔按 `10s -> 20s -> 40s -> 80s -> ...` 退避,默认上限 10 分钟。
- `ENDPOINT_PROBE_IDLE_DB_POLL_INTERVAL_MS` 已接入实际调度器。

收益:持续超时或不可达的 endpoint 不再以固定高频率被所有调度周期重复探测,减少无效连接、日志和定时任务压力;恢复成功后自动回到正常频率。

### 2.6 Kubernetes 资源与连接池

- `DB_POOL_MAX` 从 18/24 级别收敛到 8,降低副本扩张时的 PostgreSQL 理论连接上限。
- App request 调整为 `250m CPU / 2Gi memory`,limit 调整为 `2 CPU / 5Gi memory`。
- HPA 删除 memory metric,仅保留 CPU 70%,避免 Node 常驻内存直接触发无收益扩容。
- liveness/startup 使用 `/api/health/live`,readiness 使用 `/api/health/ready`,readiness timeout 为 5 秒。
- dashboard 日志轮询间隔调整为 10 秒。
- Session snapshot 目录挂载到节点本地 hostPath。

收益:避免低 CPU、高常驻内存场景持续扩容,降低连接池和后台任务随副本数放大的风险,同时保留针对进程存活与依赖就绪的独立健康检查。

## 3. 当前建议直接执行的运维动作

这些动作不需要代码或新增组件,收益高且风险低:

1. 禁用欠费、quota 用尽、分组停用、持续 5xx 或持续超时的 provider/endpoint。
2. 对错误率接近 100% 或长期达到 probe 退避上限的 endpoint 建立人工复核清单,不依赖大量 fallback 掩盖故障。
3. 为超大上下文设置更低并发或独立 provider group,优先控制同时驻留的大请求数量。
4. 观察 Redis `used_memory`、AOF rewrite、session key 大小和 eviction 是否在切换 filesystem 后持续下降。
5. 观察 PostgreSQL CCH 连接数是否随 `DB_POOL_MAX=8` 稳定在安全区间。

## 4. 建议拆分为后续独立 PR

### 4.1 Request-local first-content deadline

这是下一项最高优先级优化,但不应在本轮同时实现。推荐契约:

```text
一个 request-local absolute first-content deadline
所有 provider retry、hedge、rectifier 和 transport fallback 共享剩余时间
H2 -> H1、proxy -> direct、WS -> HTTP 不计为 provider attempt
首个协议有效内容到达后解除 first-content deadline
客户端 abort 继续保持 499,provider timeout 和 deadline timeout 使用稳定的独立分类
```

收益:从根源限制请求长时间驻留,避免失败 provider 通过多层 transport fallback 和 provider fallback 把总耗时扩大到 60-100 秒以上。

拆分原因:legacy serial、legacy hedge、Discovery、H2/H1、proxy/direct、Responses WS/HTTP 和 rectifier 都要共享同一 absolute deadline;粗暴加入最大 attempt 数会把 transport fallback 错计为 provider retry,存在明显兼容风险。

### 4.2 AvailableModels per-provider cache、singleflight 和共享限并发

推荐契约:

```text
只缓存每个 provider 的成功模型结果,不缓存用户或 group 聚合结果
同一 provider/config fingerprint 的并发请求 singleflight
使用进程级共享并发上限,结果按原 provider 顺序回填
失败不覆盖成功 cache,瞬时失败不做长 negative cache
provider 配置变更时通过现有 invalidation 广播清理本地缓存
group、活动时间窗和用户可见性仍在每次请求实时过滤
```

收益:减少上游 `/models` 重复请求和无界 fan-out,降低多个客户端同时刷新模型列表时的 stampede。

拆分原因:必须同时保证 group 隔离、活动时间窗、allowlist、配置失效、失败缓存和结果顺序;只加一个 TTL Map 容易返回已禁用 provider 或跨 group 的旧模型。

## 5. 明确不做

- 不新增 Pod、sidecar、CronJob、PgBouncer、Redis 实例、metrics adapter 或共享存储组件。
- 不用 SQLite 保存 Session 快照。当前数据模型是按 sessionId 读取和合并的压缩 JSON blob,filesystem atomic file 的写放大和清理路径更短,也避免 SQLite WAL、vacuum 和跨进程锁竞争。
- 不继续使用 memory HPA 作为主要扩容信号。后续如需改为活跃流、并发或 RPS,应先复用现有可观测数据或平台能力,不能为本优化额外引入组件。
- 不在本轮加入统一 outbound attempt counter。
- 不回填历史 TTFT/TTFB 混合数据,以免产生看似精确但语义错误的指标。

## 6. 验收与观察指标

上线后建议按 1 小时、24 小时和 7 天三个窗口对比:

- Redis session/snapshot key 总字节、最大 key、eviction、AOF 大小和 rewrite 时长。
- 单 Pod 与总 Node 内存、HPA 副本数、CPU utilization、readiness failure。
- PostgreSQL CCH 连接数和连接等待。
- endpoint probe 次数、持续失败 endpoint 的实际探测间隔。
- cache effectiveness scheduler 每窗口实际执行次数和重复窗口数。
- TTFB P50/P95/P99、TTFT P50/P95/P99、TTFB 与 TTFT 的差值。
- 请求 attempt/hedge 分布、499、`STREAM_RESPONSE_TIMEOUT` 和“所有供应商暂时不可用”数量。
- 大上下文请求的并发数、驻留时长和单请求 snapshot 大小。

如果 Redis 内存和后台重复任务明显下降,但请求 P95/P99 仍主要由多轮 fallback 决定,应优先实施 request-local first-content deadline,而不是继续增加副本或连接池。
Loading
Loading