mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-12 22:20:19 +08:00
fix: harden concurrency limits and high-RPM runtime paths
Bound request, stream, queue, and shutdown resource lifetimes. Reduce scheduler and Redis hot-path work and isolate database maintenance. Include regression coverage, load probes, and concurrency audit results.
This commit is contained in:
@@ -0,0 +1,526 @@
|
||||
# Aether 并发设计审查
|
||||
|
||||
- 日期:2026-09-09
|
||||
- 代码基线:`361952ada`
|
||||
- 背景:RPM 增加后服务出现卡死现象。
|
||||
- 范围:当前代码、默认配置、隔离本地复现。尚未取得故障实例、实际 RPM、线上配置或卡死时指标;以下是确认的代码问题及条件性风险,不代表已经确认本次生产故障根因。
|
||||
- 初次审查仅新增报告;后续本地修复状态见下文。未部署、未修改生产配置、未向线上发起压测。
|
||||
|
||||
## 第一轮修复状态
|
||||
|
||||
以下问题描述及行号对应审查基线 `361952ada`,不是修复后的代码位置。
|
||||
|
||||
- **P1-1 已修复:** 压缩上传按声明的压缩大小预留,未知长度上传从一个额度单元开始,随缓冲容量增长申请预算;解压计入同时存活的输入、中间输出和最终输出。扩容额度不足立即返回 503,避免多个请求各持部分额度互相等待;取消和失败释放额度。请求体完整读取默认超时改为 120 秒,显式配置 0 仍可关闭。该预算覆盖读取和解压的显式缓冲,不等于整个请求生命周期或进程 RSS 上限。
|
||||
- **P1-3 已修复:** SQL 改为 `FOR UPDATE OF user_plan_entitlements`,不同用户不再争抢共享套餐行,同一 entitlement 的扣费仍串行。套餐 overage 配置按当前语句快照读取,后续语句可读取已提交的配置变更。
|
||||
- **P1-4 已修复:** reqwest、h2c、wreq 和 tunnel 的流统一接入空闲读取期限;执行配置 `read_ms` 优先,否则使用 `AETHER_GATEWAY_UPSTREAM_STREAM_IDLE_TIMEOUT_MS`,默认 300 秒,显式 0 可关闭。首包期限独立保留,网关 keepalive 和空数据帧不重置空闲计时;已收到成功终止事件的请求不会因随后空闲被改记为失败。
|
||||
- **后续待处理:** P1-2 Redis 调度扫描、P1-5 响应捕获全局预算、P1-6 审计压缩及 P1-7 长期额度聚合和 SQL 期限,本轮未修改。
|
||||
|
||||
本地验证:
|
||||
|
||||
- `cargo test -p aether-gateway-frontdoor`:34 项通过。
|
||||
- 网关请求体、解压、超时、流结束及模块边界的针对性回归:73 项通过;另跑 Anthropic 原生流及直通兼容性回归:24 项通过。
|
||||
- `cargo test -p aether-data-postgres --lib settlement::tests`:6 项通过,1 项需要数据库的测试默认忽略;该测试另在隔离 PostgreSQL 14.17 中显式执行通过,覆盖不同用户并行、同用户阻塞、扣费余额及配置并发更新,临时实例已停止。
|
||||
- 修改文件格式检查和 `git diff --check` 通过。未执行全工作区测试或阶梯吞吐压测,尚不能据此给出修复后的 RPM 容量。
|
||||
|
||||
## 第二轮修复状态
|
||||
|
||||
- **P1-2 已处理调度中的管理扫描和无关指标读取:** 增加调度专用运行态入口,跳过全池 sticky 会话扫描、会话计数及 cooldown TTL;sticky 直达只查询当前绑定。成本窗口仅在成本限额、`cost_first` 或 `quota_balanced` 启用时读取,延迟窗口仅在 `latency_first` 启用时读取。默认 64 个候选且未启用这些策略时,消除原先 128 次历史窗口查询;这是代码路径比较,不是压测吞吐结论。管理查询仍保留原统计,调度成本检查不再套用管理显示的 key 数量截断。
|
||||
- **P1-5 已增加流式诊断捕获共享预算:** `AETHER_GATEWAY_STREAM_CAPTURE_MEMORY_BUDGET_BYTES` 默认 128 MiB,覆盖 provider/client 捕获的实际容量及扩容时的新旧分配,显式 0 关闭此类捕获。预算不足时保留连续前缀并标为截断,不阻塞客户端传输;取消和终态释放额度。计费观察独立消费完整数据,补充主解析器停用后的用量恢复,并保留同步 JSON 转流的终态摘要。完成语义恢复后及时释放预读副本。
|
||||
- **P1-6 已处理单条及 pending 批量写入的审计压缩:** 准备工作移到事务前的 blocking 任务,进程内最多 4 个工作任务、32 个含等待的准入任务,等待工作槽最多 1 秒、等待执行结果最多 30 秒。超时和容量不足返回可重试 `TimedOut`;取消后的运行任务继续持有许可,且只准备数据,不会自行写数据库。序列化直接写入带 8 KiB 缓冲的 gzip,避免完整 JSON 中间副本及普通路径一次额外 body 克隆。事务内仍保留依赖旧记录的生命周期、清空、恢复和幂等判断。
|
||||
|
||||
本地验证:
|
||||
|
||||
- PostgreSQL usage 模块:124 项通过,13 项需要外部条件的测试默认忽略。
|
||||
- 隔离 PostgreSQL 14.17 的 5 项真实回归通过,覆盖单条/批量完整审计读写、重复事件计数、过期终态 no-op 和捕获清空;临时数据库已停止。日志:`/tmp/aether-usage-concurrency.68ZESf/test.log`。
|
||||
- 第一批网关回归 68 项通过,包含真实 Redis 命令计数、管理统计保留、策略读取矩阵、超过管理显示上限的成本检查及调度结果一致性。
|
||||
- 最终流式模块及相关超时回归 187 项全部通过,覆盖捕获预算耗尽、并发预算释放、用量回退、累计更新及显式归零、协议转换、同步 JSON 桥接和非对象字段兼容。日志:`/tmp/aether-concurrency-round2-stream-final.log`。
|
||||
- 修改过的 Rust 文件格式检查和 `git diff --check` 通过。
|
||||
|
||||
仍需后续处理的边界:
|
||||
|
||||
- 启用成本/延迟策略时仍读取原始窗口,尚未改成增量聚合或合并刷新。
|
||||
- 128 MiB 不包含独立协议/计费解析缓冲、终态 base64 编码或 usage 队列副本。计费用量回退保留的单条协议记录仍有 Basic 5 MiB / Full 64 MiB 上限,记录完成即释放;大量并发超长单记录仍可能放大内存。审计准备许可约束任务数,也不是任意大 usage 记录的字节预算。
|
||||
- P1-7 长期额度聚合和 SQL/锁等待期限,以及独立实例阶梯压测,本轮未处理。
|
||||
|
||||
## 第三轮修复状态
|
||||
|
||||
- **P1-7 普通 SQL 等待期限:** PostgreSQL 连接默认设置 `statement_timeout=30000ms`、`lock_timeout=3000ms`,由 `AETHER_GATEWAY_DATA_POSTGRES_STATEMENT_TIMEOUT_MS` 和 `AETHER_GATEWAY_DATA_POSTGRES_LOCK_TIMEOUT_MS` 覆盖,显式 0 关闭,非法值在创建连接池时拒绝。覆盖直接连接池查询和事务;这是单条语句期限,不是整笔事务总期限。超时 SQLSTATE 仍按原可重试错误处理。
|
||||
- **P1-7 多窗口精确查询:** 长期请求额度和成本额度将多个窗口的独立扫描合并为一条带 `FILTER` 的聚合查询,保留用户锁、时间边界、有效预留过滤、当前事件排除、拒绝顺序和幂等语义。未引入近似额度或缓存;仍需扫描最大覆盖范围内的历史记录。
|
||||
- **维护任务隔离:** schema migration 和历史 backfill 使用关闭普通期限的专用连接,所有退出路径均关闭连接,避免配置泄漏回请求池。日/小时统计、钱包每日聚合、用量统计重建与 VACUUM 使用 5 分钟语句期限和 30 秒锁等待期限。
|
||||
- **P1-2 有界 Redis 聚合:** 单个窗口最多 512 条记录时在 Redis 内精确聚合,只返回总量和正值样本数;每批最多 16 个独立脚本。更大的窗口或聚合失败回到原完整查询;没有缓存金额,也没有无界 Lua 聚合。超过 512 条的成本窗口仍有原查询开销,并增加一次计数探测。
|
||||
- **P1-5/P1-6 事件正文保留:** 已构造的 usage 事件在同步保存计费相关字段后、进入终态队列或等待正文策略前,按四份诊断 JSON 正文的堆内存估算申请共享预算。`AETHER_USAGE_EVENT_CAPTURE_MEMORY_BUDGET_BYTES` 默认 128 MiB,显式 0 关闭正文保留。预算申请不等待,额度不足舍弃正文并标记 `Truncated`,保留费用、用量、终态和引用;事件副本单独申请额度,重试随事件保留额度,取消及释放事件时归还。异步策略读取仍在原来的终态执行和准入位置,维持提交、排序与异常隔离语义。
|
||||
- **减少中间副本:** usage envelope 和死信队列序列化改为借用正文及字段,避免序列化前的完整深拷贝。新增 `usage_runtime_event_capture_memory_budget_bytes`、`usage_runtime_event_capture_memory_retained_bytes` 和 `usage_runtime_event_capture_memory_downgraded_total` 指标;降级次数也可能包含随后按 Basic 策略清除的正文。
|
||||
|
||||
本地验证:
|
||||
|
||||
- Redis 运行态 3 项真实实例回归通过,覆盖 512/513 条边界、多批 Key、`u64` 精度和饱和、脚本重载、窗口变化及已完成的并发写入。日志:`/tmp/aether-round3-redis-tests.log`。
|
||||
- PostgreSQL 全量普通测试 229 项通过、24 项默认忽略;跨模块 `cargo check -p aether-data` 通过,维护 future 的 `Send` 回归通过。隔离 PostgreSQL 14.17 显式验证空库迁移和 usage 读写、超时回滚与期限隔离、精确多窗口准入和幂等、共享套餐锁,共 4 项通过。临时库已停止;日志:`/tmp/aether-postgres-deadlines.7sZ8w1/`。
|
||||
- 用量模块完整回归 264 项全部通过,含 13 项正文预算测试及终态队列容量、排序、异常隔离回归。日志:`/tmp/aether-round3-usage-agent-tests.log`。
|
||||
- 最终网关调度、真实 Redis 窗口回退及指标回归 61 项全部通过;最终网关、mock upstream 和 seed 工具构建通过。日志:`/tmp/aether-round3-gateway-tests.log`、`/tmp/aether-round3-verified-build.log`、`/tmp/aether-round3-seed-final-build.log`。
|
||||
- 修复压力 seed 工具按网关密钥加密 provider/client 凭据,并用已有凭据比较更新接口处理随机密文的重复初始化;隔离空库连续初始化两次已通过。mock chat 流按 `stream_options.include_usage=true` 输出终态用量,保留故障截断和未请求用量时的原行为,9 项测试全部通过。日志:`/tmp/aether-round3-mock-tests.log`;最终 mock 构建日志:`/tmp/aether-round3-mock-final-build.log`。
|
||||
|
||||
### 隔离端到端验收
|
||||
|
||||
使用本机 debug 构建、独立空 PostgreSQL 14.17 和 Redis、4 个 Tokio worker、入口并发上限 128、8 个客户端 API key。上游为单个零价 mock provider,每请求 20 个 256 字节内容块,首字节延迟 30 ms、块间隔 100 ms,完整流约 2 秒。请求启用流式 usage,客户端必须收到完整响应及 `[DONE]`。各档采样间隔 500 ms,停止流量后最多等待 45 秒排空;指标通过管理员会话采集,避免管理 Token 使用计数干扰排空判断。
|
||||
|
||||
| 并发 | 请求数 | 成功数 | 实测 RPS | 首字节 P95 (ms) | 总耗时 P95 (ms) | 网关峰值 RSS (MiB) | 排空 (s) |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 8 | 64 | 64 | 4.01 | 117 | 2069 | 107.0 | 8.7 |
|
||||
| 24 | 192 | 192 | 12.04 | 67 | 2015 | 122.3 | 9.9 |
|
||||
| 48 | 384 | 384 | 23.91 | 87 | 2036 | 143.3 | 9.8 |
|
||||
|
||||
- 三档共 640 个请求,全部 HTTP 200 且完整 SSE 结束,无请求错误。数据库恰有 640 条 `completed`,每条 input/output/total tokens 均为 `1/20/21`,合计 `640/12800/13440`,所有首字节时间有值。
|
||||
- 三档排空所需指标齐全,最终 usage 队列 lag、pending、计数 outbox、终态提交和有序生命周期待处理数均为 0;采样未观察到 PostgreSQL 锁等待,未出现 usage worker 处理失败。最终事件正文预算保留量为 0,未触发正文预算降级。
|
||||
- RSS 随并发档位上升,排空时未立即回落;不能据此证明长期无内存增长。最高档约 1435 RPM 是此短时、约 2 秒 mock 流的实测值,不代表生产容量,也没有修复前的同条件吞吐基线。真实长流、大请求、多候选账号池、长期额度和现金扣费负载未在本次阶梯压测覆盖;额度和结算语义由前述独立数据库回归验证。
|
||||
- 最终产物:`/tmp/aether-round3-pressure.T3sq7J/`,包含每档 JSON、原始日志、最终指标及 `usage-integrity.txt`;脚本:`/tmp/aether-concurrency-round3-pressure.sh`,总日志:`/tmp/aether-round3-pressure-final.log`。临时网关、mock、采样代理、Redis 和 PostgreSQL 均已停止。修改文件格式检查及 `git diff --check` 通过,未执行全工作区测试,未部署线上。
|
||||
|
||||
本轮仍未覆盖的内存与计算边界:
|
||||
|
||||
- 事件正文预算是 JSON 堆内存估算,不是进程 RSS 上限。仍不包含计费提取前的 seed、Redis 消费批次、数据库写入 DTO、压缩和序列化字符串,以及协议观察缓冲。
|
||||
- Redis 超大成本窗口仍走原始历史查询;长期 SQL 额度仍需扫描历史,尚未改为带迁移和对账的增量账本聚合。
|
||||
|
||||
## 第四轮修复状态
|
||||
|
||||
- **受限等待期间减少重复候选准备:** 普通指定模型和无指定模型能力查询保留 150 ms 等待窗口,分页候选路径保留首次受限后的 100 ms 窗口。首次完整准备确认仅被客户端 API Key 并发限制阻塞后,等待期间只读取并发判断所需的近期候选;恢复或到达期限后重新执行完整动态校验,不直接复用旧候选放行。持续阻塞且首次准备未耗尽期限时,完整准备收敛为首次和最终两次;若短暂恢复后重新受限,仍可在原期限内重新准备。等待窗口不重置,也不是包含数据库 I/O 的端到端硬超时。
|
||||
- **探测任务创建前合并:** 同一 AppState 的请求补探测按 provider 在 `spawn` 前合并;运行期间的重复触发只保留一次后续执行信号,完成和取消时清理本地条目。AppState clone 共享协调器,切换到不同 RuntimeState 时隔离。最多同时保留 1024 个活跃 provider,超过容量跳过此次 best-effort 补探测,不创建等待任务;周期基础探测继续运行,但不等价于请求触发的 Burst 补探测。
|
||||
- **跨实例退出交接:** 保留 Redis pending 和带 token 的锁协议,释放锁后复查新 pending,避免另一实例在退出窗口提交的补探测无人接手。读取 pending 失败时释放锁并停止本轮处理,避免残留 pending 导致重新争锁的忙循环。
|
||||
- **可观测性:** 增加 `pool_quota_probe_replenish_provider_capacity`、`pool_quota_probe_replenish_active_providers`、`pool_quota_probe_replenish_started_total`、`pool_quota_probe_replenish_coalesced_total` 和 `pool_quota_probe_replenish_capacity_rejected_total`。
|
||||
|
||||
本地验证:
|
||||
|
||||
- `cargo check --locked -p aether-gateway --lib` 通过。日志:`/tmp/aether-round4-check.log`。
|
||||
- 网关调度、分页候选、探测、模块边界、指标及真实 HTTP 并发等待回归共 376 项全部通过,包含本轮新增的 11 项调度等待和 8 项探测协调测试。日志:`/tmp/aether-round4-gateway-tests.log`。
|
||||
- 8 个同时受限的请求,通过真实候选筛选函数的依赖调用计数,确认完整候选读取共 16 次;恢复后重新加载被替换或移除的候选,轻量读取及完整重验的错误均正常传播。分页持续受限仅重启一次扫描,首次查询耗时计入原重试预算,取消后停止查询。
|
||||
- 单 provider 的探测运行中并发触发 64 次,仅创建一个任务并额外执行一次补探测。测试覆盖不同 provider 并行、100 次本地退出交接竞争、未首次执行即取消、运行中取消、panic、容量释放和 runtime 绑定隔离。两个本地协调器共享 Memory Runtime 模拟跨实例 pending/锁交接,精确覆盖旧任务最终检查后、解锁前的新触发;没有声称本轮运行了多进程 Redis 压测。
|
||||
- `cargo build --locked -p aether-gateway --bin aether-gateway` 通过,最终可执行文件已更新;日志:`/tmp/aether-round4-build.log`。修改文件格式和 `git diff --check` 通过,测试进程已退出。本轮未重新执行阶梯吞吐压测,第三轮的 640 请求结果仅代表其当时构建;未执行全工作区测试,未部署线上。
|
||||
|
||||
边界:
|
||||
|
||||
- API Key 并发检查仍使用全局最近 128 条候选中的同 Key 活跃候选行,依赖状态持久化及原 300 秒活跃窗口;未引入原子分布式许可,也未改变候选行计数为请求去重计数。
|
||||
- 探测协调为 best-effort;取消后的 Redis 锁仍按原 30 秒 TTL 释放,未增加续期或 exactly-once 保证。同步日志、超大历史窗口增量聚合、未覆盖的内存副本以及目标环境长期压测仍待处理。
|
||||
|
||||
## 第五轮修复状态
|
||||
|
||||
- **运行日志 I/O 脱离请求线程:** stdout 和滚动文件各使用独立专用写线程,保留 Pretty/JSON、Stdout/File/Both、动态日志过滤及原有文件权限检查。文件写入、flush 和轮转重开均在写线程执行;定期文件清理移到 blocking 任务,单次完成后才安排下一次清理。启动时仍同步校验日志目录和目标文件,配置错误正常拒绝启动。
|
||||
- **日志过载保护:** 每个目标最多排队 4096 条事件,复制后的日志正文最多保留 8 MiB,单条事件上限 256 KiB。字节预算包含生产者已预留的复制、排队和正在写入的正文;队列满、预算不足或单条过大时整条舍弃,不等待设备、不在请求线程同步回退 stderr。Both 两个目标独立接收和降级,可能保留不同的事件集合。
|
||||
- **退出排空:** 网关、隧道、两个运行示例及 13 个基准工具的最外层入口持有日志 guard,先结束 Tokio runtime 再关闭队列。升级/回滚的显式进程退出也调用关闭接口。关闭先停止全部目标接收,再在共用的 2 秒预算内等待已接收事件及最终 flush;阻塞中的系统 I/O 无法强制取消,超时后不无限 join 写线程。
|
||||
- **信号处理:** 网关 ready 后收到 SIGTERM/SIGINT 会结束运行函数并经过日志 guard;现有连接未被统一追踪,此路径仍按进程终止处理业务,不声称请求或用量队列优雅排空。启动过程中尚未进入信号等待时,以及 SIGKILL/abort,不保证排空。
|
||||
- **日志健康指标:** 网关与隧道指标端点增加 `logging_stdout_*` / `logging_file_*`,记录队列和字节上限、当前正文保留量、接收数、按原因区分的丢弃数、写入错误、线程 panic、关闭超时及线程状态。沿用服务指标命名空间前缀。关闭 API 返回成功仅说明线程处理完队列且最终 flush 成功,之前的写入错误仍需查看指标,不代表 fsync 持久化成功。
|
||||
|
||||
本地验证:
|
||||
|
||||
- 网关、隧道、loadtools 和 integration 的所有 binary/example 入口通过 `cargo check --locked`,未增加新的第三方依赖;integration 的依赖清单补充已有共享 runtime。日志:`/tmp/aether-round5-entrypoints-check.log`。
|
||||
- 共享 runtime 44 项单元测试及 2 项进程集成测试全部通过,包含 11 项新 writer 测试、2 项轮转回归、12 种初始化/输出格式/动态过滤场景,以及真实 stdout 堵塞测试。8 个生产者同时写入时,慢设备不阻塞生产者;4096 条和字节预算、整条拒绝、错误/panic 回收、退出竞争、stdout/file 双向隔离及阻塞 flush 均已覆盖。每种实际格式并发写入 512 条事件,记录完整且无重复。日志:`/tmp/aether-round5-runtime-final-tests.log`。
|
||||
- 真实 stdout 测试保持子进程管道完全不读,确认 stdout 队列触发丢弃后,文件仍收到完整 JSON 尾记录;guard 超时返回后,标准进程退出也成功完成。该测试耗时约 2.36 秒,包含日志的 2 秒退出等待;没有关闭管道来人为解除阻塞。
|
||||
- 网关入口 61 项、隧道 197 项回归全部通过,日志:`/tmp/aether-round5-service-tests.log`。以上合计 304 项测试通过,没有计入重复运行的首批测试。
|
||||
- Linux root/capabilities 专用日志 fixture 已适配异步排空,当前 macOS 环境未执行;Unix 文件权限、符号链接、多硬链接及安全轮转的普通测试已通过。
|
||||
- 网关和隧道最终二进制构建通过,日志:`/tmp/aether-round5-service-build.log`。隔离 PostgreSQL 和新网关实例的 Both/JSON 验收通过,stdout 和文件各保留 15 条完整日志,均包含唯一的 starting、ready 和 shutdown 事件;实际指标端点两路队列容量、接收及运行状态正常,丢弃、写入错误、panic 和关闭超时计数为 0。SIGTERM 后约 16 ms 正常退出,该耗时仅代表健康设备和无在途代理请求的此次烟测。产物:`/tmp/aether-logging-smoke-qvzN9U/result.json`,脚本:`/tmp/aether-nonblocking-logging-smoke.mjs`。
|
||||
- 临时网关和 PostgreSQL 已全部停止,测试与构建进程均已退出。修改文件格式检查及 `git diff --check` 通过;本轮未重跑业务阶梯吞吐压测,未执行全工作区测试,未部署线上。
|
||||
|
||||
本轮边界:
|
||||
|
||||
- 日志格式化、字段 Debug 展开和 JSON 序列化仍发生在调用线程,订阅器线程局部字符串也可能保留历史容量;8 MiB 预算只约束交给后台写入的正文,不包含这些临时对象、队列元数据或进程 RSS。
|
||||
- 运行日志在过载、I/O 错误或关闭超时下可能丢失,不能作为可靠计费账本;账务持久化流程不使用这条日志队列。2 秒仅限制日志关闭等待,不是整个服务退出期限,既有业务或 blocking 任务清理仍可能更久。
|
||||
- 超大历史窗口增量聚合、尚未覆盖的内存副本、完整请求优雅排空及目标环境长期压测仍需后续处理。本轮不调整线上配置,也不据此给出生产 RPM 容量。
|
||||
|
||||
## 第六轮修复状态
|
||||
|
||||
- **补齐诊断正文预算的所有权传递:** 将纯预算令牌放到 data contracts,运行时仍使用原环境变量、128 MiB 默认值和指标。同步及流式终态 seed 在等待提交前申请预算;Redis 解码后的事件重新纳入本进程预算;事件生成的数据库写入 DTO 继续持有对应额度。事件和受管理 DTO 的正文副本分别申请额度,释放正文后才归还;序列化跳过令牌,不改变队列协议或数据库字段。
|
||||
- **保留完整计费与终态语义:** 正常终态构建仍在 blocking 任务执行;预算不足时同步执行既有纯构建逻辑,先解析 token、显式 cache=0、图像估算、错误及终态,再舍弃诊断正文,随后进入原有有序提交和准入路径。保留原始终态观测时间,构建异常仍按原终态失败路径隔离。已有 `None`、`Disabled`、`Unavailable` 状态不被预算降级改写为 `Truncated`,避免破坏清空指令。
|
||||
- **旧队列消息兼容:** 解码前的原始字段继续保留到记录或死信处理完成,重试不提前 ACK,DLQ 保存原始字段。旧消息缺正文且缺 typed state 时保留元数据内已有缓存 TTL、tier 和请求事实,显式 `None` 仍清空,避免预算接线改变后续计费输入。
|
||||
- **数据库准备减少正文副本:** 单条与 pending 批量准备先移走四份正文和 headers,再复制两个存储投影需要的少量元数据,避免原先为清洗而深拷贝整份 DTO。预算随输入进入 blocking 压缩闭包,调用方取消不会提前释放仍存活的正文额度;压缩结束后原始 JSON 释放,压缩结果继续走原事务、审计和正文存储流程。
|
||||
|
||||
本地验证:
|
||||
|
||||
- data contracts 226 项、PostgreSQL 231 项、data runtime 356 项、usage runtime 282 项普通测试通过,合计 1095 项;其中本轮新增 27 项,覆盖队列积压、并发 seed、预算拒绝与复制、token/图像计费事实、typed clear、legacy metadata、重试/DLQ,以及取消和 panic 后额度释放。另有 25 项需要外部条件的测试默认忽略。日志:`/tmp/aether-round6-data-tests.log`、`/tmp/aether-round6-runtime-tests.log`。最终时间采样和反向 DTO 转事件的所有权修正后,usage runtime 282 项再次全部通过:`/tmp/aether-round6-usage-final-tests.log`,不重复计入总数。
|
||||
- 隔离 PostgreSQL 14.17 的 5 项真实回归通过。完整审计测试现覆盖受预算管理的单条、普通 pending 批量及同 request 重复批量写入,四份正文读取一致,准备结束后只保留调用方原对象的额度,最终释放为 0;同时验证过期终态 no-op、辅助计数幂等和 typed `None` 清空。日志:`/tmp/aether-round6-postgres.K8Aq5W/`;临时数据库已停止。
|
||||
- 网关、隧道、loadtools 和 integration 的 binary/example 入口通过 `cargo check --locked`,日志:`/tmp/aether-round6-entrypoints-check.log`;修改文件格式及 diff 检查通过。最终 `cargo build --locked -p aether-gateway --bin aether-gateway` 通过,网关可执行文件已更新,日志:`/tmp/aether-round6-gateway-build.log`。全部测试与构建进程已退出;尚未部署,未重跑业务阶梯吞吐压测,未执行全工作区测试。
|
||||
|
||||
本轮边界:
|
||||
|
||||
- 预算覆盖上述运行时链路持有的四份诊断 JSON 正文及副本,不是进程 RSS 上限。原始 Redis RESP、批次字段字符串及反序列化临时分配仍不受此额度约束;直接通过契约自行构造或反序列化的 DTO 默认不启用运行时预算。
|
||||
- seed 进入本链路前的 JSON/base64 解析、构建过程的临时正文复制、序列化/压缩结果和 SQL bind 缓冲未纳入。预算不足时的纯构建会使用调用线程 CPU;它保留计费兼容性,没有消除解析和估算开销。
|
||||
- 协议观察器及用量恢复缓冲、超大历史窗口增量聚合、完整请求优雅排空和目标环境长期压测仍待后续处理。第三轮吞吐数据不能作为本轮构建或生产容量结论。
|
||||
|
||||
## 第七轮修复状态
|
||||
|
||||
- **Redis 回复转移正文缓冲:** `XREADGROUP` 采用当前 redis crate 支持 owned conversion 的底层容器结构,避开 `StreamReadReply` 的借用转换;字段正文直接由 RESP `BulkString` 转成 `String`。`XAUTOCLAIM` 的消息正文、游标和删除 ID 同样使用所有权转移。保留 RESP2/RESP3、nil、重复字段覆盖、无效 UTF-8 和错误分类等既有解析语义,没有改变队列协议。
|
||||
- **减少队列处理副本:** Memory 队列读取直接遍历待交付条目,去掉全局队列锁内的整批临时正文克隆;队列、PEL 和调用方继续独立持有自己的数据。usage worker 在处理完一条消息后先释放原始字段,再等待 ACK/DELETE,避免已处理的大正文跨确认 I/O 继续存活。失败和死信路径仍保留所需原文到处理结束。
|
||||
- **流式用量恢复收敛保留字段:** Claude 的跨记录状态只累计 mapper 使用的 token 和缓存字段,以及非空用量出现标记,未知大字段不再随流长度累积,也不再每次复制完整累计对象。候选和 chunks 数组省略没有用量或 tier 信息的空元素,保留 Gemini 的首候选位置和倒序查找规则。完整且未超限的 SSE 记录直接借用当前输入切片,跨分片与超限记录继续沿用原 carry 和恢复规则。
|
||||
- **投影兼容修复:** 显式 `usage: null` / `usageMetadata: null` 保留字段存在性,避免错误回退到旧快照;独立 null 不新增清零信号,带其他非空用量的嵌套结构遵循原 mapper 的优先级。图片回复仅提取 `data/result` 数量以恢复 `request_count/image_count`,不复制图片正文。
|
||||
|
||||
本地验证:
|
||||
|
||||
- runtime state 76 项、usage runtime 282 项回归通过,合计 358 项。Redis parser 的 7 项新增测试用正文原指针和容量断言验证缓冲转移,另有 3 项 Memory 所有权和队列生命周期回归。日志:`/tmp/aether-round7-queue-tests.log`。
|
||||
- 新增真实 Redis 大消息回归,两种协议各 24 条消息、每条约 512 KiB 正文,3 个消费者同时分批读取,再以最多 5 条重领和 ACK/DELETE;48 条消息原文逐字节一致,无重复交付到不同读取结果,最终 pending、lag 和 stream length 都为 0。日志:`/tmp/aether-round7-large-redis-test.log`。测试自建 Redis 已关闭;此项已包含在前述 76 项中,不重复计数。
|
||||
- 网关流式、stream pump 和空闲读取期限 186 项回归全部通过,日志:`/tmp/aether-round7-stream-final-tests.log`。包含本轮新增的 9 项投影与借用测试,覆盖原始 Claude 累计对象对照、256 条带不同未知大字段的长流、10000 个空数组元素、正文复制计数、CR/LF/CRLF 分片及上限边界、显式 null 的实际 mapper 对照,以及仅有图片数量的回复。连同队列侧共 544 项通过,本轮新增 20 项;首批 parser、真实 Redis 和流式聚焦测试不重复计数。
|
||||
- 修改文件格式及 diff 检查通过,最终 `cargo build --locked -p aether-gateway --bin aether-gateway` 通过,网关可执行文件已更新,日志:`/tmp/aether-round7-gateway-build.log`。所有测试、构建进程和临时 Redis 均已退出;尚未部署,未执行新的业务吞吐压测或全工作区测试。
|
||||
|
||||
本轮边界:
|
||||
|
||||
- 原始 Redis 消费批次仍按配置的条数读取,不是字节预算。默认每批最多 128 条、最多 32 个 worker;本轮减少重复分配,没有限制任意大消息或整体批次的最大驻留字节,也没有通过丢弃账务消息缩小批次。当前同 worker 的 read/reclaim 已串行,已处理条目原本就逐条释放。
|
||||
- 流式恢复仍保留必要的跨分片单记录,Basic 5 MiB / Full 64 MiB 的原限制未改变;多行 `data:` 拼接、JSON 解码临时值、非空用量数组和主协议观察器不在全局正文预算内。不能因减少诊断副本而直接停用计费恢复。
|
||||
- 主协议解析器的累计文本、原始队列批次字节控制、超大窗口增量聚合、完整请求优雅排空及目标环境长期压测仍待后续处理。
|
||||
|
||||
## 第八轮修复状态
|
||||
|
||||
- **主协议用量观察器取消正文累计:** `StreamingStandardTerminalObserver` 为 OpenAI Chat、Responses(包括 compact)及 Gemini 选用现有 provider parser 的终态观察模式。协议转换仍使用完整模式;观察器沿用同一事件分类和用量解析,只保留摘要需要的状态。Claude 当前没有正文累计,独立图片观察器也继续使用原实现。
|
||||
- **长流正文和工具参数:** Responses 不再保存文本、双份推理文本、工具参数、工具结果和图片项正文;保留工具索引、名称及 namespace 校验,它们影响未知事件计数和工具调用结束原因。Chat 不再等待迟到的工具名称或 ID 而持续缓存参数。Gemini 不再保存累计文本、推理、签名、媒体、工具参数和结果,只记录是否见过工具调用以保留结束原因。
|
||||
- **opaque 项去重:** Responses 观察模式将未知扩展项的去重键改为固定 32 字节 SHA-256 摘要,按原始键的相同字节增量计算,避免加密内容或整个序列化项成为常驻键。默认转换和客户端 emitter 的原键及完整输出保持不变。
|
||||
|
||||
本地验证:
|
||||
|
||||
- `aether-ai-formats` 全部 922 项测试通过,包含本轮新增 16 项。逐个输入前缀及提前 EOF 对比完整解析器与观察模式的摘要,覆盖身份时点、用量、显式零、tier、错误、未知项去重、namespace、工具调用和 SSE / WebSocket 结构化入口。原有格式转换、会话历史、图片及同步转流回归均通过。日志:`/tmp/aether-round8-formats-final-tests.log`。
|
||||
- 长流回归在 2048 轮 1 KiB 文本、推理及工具参数输入后,直接断言 Responses 的正文缓冲容量仍为 0、Chat 工具缓冲为空;Gemini 在 32 轮多种 16 KiB 字段输入后所有正文状态 map 仍为空,并与完整模式核对终态帧。大 completed 项与 opaque 键的摘要字节、去重语义另有测试。此处验证内容保留行为,没有测量生产 RSS 或吞吐上限。
|
||||
- 网关流式、stream pump 和读取期限 186 项通过;Responses WebSocket 会话、上游和观察器 209 项通过。连同格式 crate 共 1317 项通过。日志:`/tmp/aether-round8-gateway-stream-tests.log`、`/tmp/aether-round8-gateway-websocket-final-tests.log`。WebSocket 回归直接运行同一份已编译测试程序;其间因现有 build script 监听 worktree 中不存在的 `.git/HEAD` 而触发的一次重复 Cargo 编译已主动停止,没有把中止当成测试通过。
|
||||
- 修改文件格式和 diff 检查通过,最终 `cargo build --locked -p aether-gateway --bin aether-gateway` 通过,日志:`/tmp/aether-round8-gateway-build.log`。本机内存压力较高,网关测试目标编译耗时 11 分 21 秒、最终构建 8 分 54 秒;这些是本地编译耗时,不是业务延迟。全部测试、构建进程已退出,未遗留本轮临时服务。尚未部署,未执行新的业务吞吐压测或全工作区测试。
|
||||
|
||||
本轮边界与后续:
|
||||
|
||||
- Responses 的工具身份和 opaque 摘要集合仍按不同逻辑项数增长;单条 JSON 解码、未知事件错误载荷及部分 Gemini 分类 helper 仍有临时分配。完整格式转换和会话历史依赖的正文仍保留,本轮不宣称整个解析器或进程具有硬性 RSS 上限。
|
||||
- Redis 原始批次仍没有硬性字节上限。现有 redis 连接的超时或 future 取消不会保证后台立即停止接收已发命令的回复,仅读取后套预算或减少 COUNT 不能解决任意大消息。建议下一步先为新生产的完整队列 envelope 实施精确序列化字节上限,超限诊断降级须保留计费事实并沿现有失败路径重试;旧消息和 PEL 仍需兼容排空。真正的接收预算还需要读取前预留及受控连接/解码器,不能直接依赖当前 usage body blob 表作为入队旁路,该表依赖已存在的 usage 父记录。
|
||||
- 超大窗口增量聚合、完整请求优雅排空及目标环境长期压测继续待处理。尚未部署。
|
||||
|
||||
## 第九轮修复状态
|
||||
|
||||
- **新增队列消息的完整字节上限:** `AETHER_GATEWAY_USAGE_QUEUE_PAYLOAD_MAX_BYTES` 默认 1 MiB,启用 usage runtime 时显式 `0` 非法。`UsageQueue::enqueue` 在发送 Redis 命令前,以有界 writer 编码完整 v1 JSON envelope,包含 UTF-8、转义、metadata、正文、headers 及其他字段;不会先生成无限制的完整 JSON 字符串再检查长度。普通消息保持原格式,原公开编码接口及历史消息解码继续兼容。
|
||||
- **诊断降级保留计费语义:** 完整消息超限后,先借用检查去掉四份正文和四份 headers 的核心字段大小,核心可容纳才克隆 metadata 并生成诊断投影;复用同一字节缓冲。保留 token、费用、显式零、错误存在性、身份、时间、终态、正文引用、预留 token 和计费维度。按完整 v1 消费者规则保留请求档位、推理参数、响应实际档位及缓存 TTL,并标记被移除的正文为 `Truncated`。显式 `None`、`Disabled`、`Unavailable` 不改为可回退的状态;JSON null 与非对象正文分别按旧解码和权威规则处理。
|
||||
- **无法安全编码时的失败路径:** 核心仍超限,或去掉正文无法保留原缓存 TTL 计费语义时,返回 `InvalidInput`。终态沿现有有并发限制的数据库路径使用原事件回退;数据库不可用、受压或写入失败时明确返回 `Failed`,保留 first-byte 状态,不误报已入队或已缓冲。这类输入错误不打开 Redis 熔断,也不会无限重试。重试接收前再次校验,覆盖主路径已熔断或入队槽耗尽的旁路;重试 worker 也会终止单条永久失败并继续处理后续条目。
|
||||
- **指标与临时分配:** 导出队列 payload 上限、诊断降级、编码拒绝及永久重试失败计数。payload 计数是进程级编码尝试,包含入队与重试预校验,不是唯一事件数。超长 tier/reasoning 字符串先检查已有的 64 字节限制,再执行大小写规范化,避免明知非法仍复制整个字符串。
|
||||
|
||||
本地验证:
|
||||
|
||||
- 用量模块全部 298 项通过,包含本轮新增的 9 项编码、6 项失败路径及 1 项配置回归。精确覆盖 UTF-8/转义字节边界、编码缓冲复用、字段透传、源事件不变、完整 v1 消费者对照,以及超限后的数据库回退、first-byte 保留、熔断/准入旁路拒绝和同重试分片继续排空。日志:`/tmp/aether-round9-usage-final-tests.log`。
|
||||
- 计费模块全部 87 项、数据契约全部 229 项通过。本轮新增 7 项真实队列读回计费对照与 3 项超长字段规范化回归;对照覆盖请求档位、缓存 TTL、显式零、未知价格、错误/取消、图片矩阵维度,以及 15 组非对象/null 正文组合。计费结果以原完整 v1 消息经旧解码路径后的行为为基线。连同用量模块共 614 项通过,不重复计入首批验证。日志:`/tmp/aether-round9-billing-contracts-final-tests.log`。
|
||||
- 网关指标、工作区模块边界、流式链路及 Responses WebSocket 共 438 项通过;网关入口配置全部 62 项通过,包括本轮新增的 payload 配置与指标回归。连同核心模块共 1114 项通过,本轮新增 28 项测试。直接复用本次构建生成的测试程序执行,日志:`/tmp/aether-round9-gateway-lib-tests.log`、`/tmp/aether-round9-gateway-main-tests.log`。
|
||||
- `cargo build --locked -j 1 -p aether-gateway --all-targets` 通过,最终网关可执行文件已更新;一次构建同时生成普通程序与测试目标,本地耗时 25 分 20 秒。日志:`/tmp/aether-round9-gateway-build.log`,产物清单:`/tmp/aether-round9-gateway-artifacts.jsonl`。修改文件格式及 diff 检查通过,所有本轮测试和构建进程均已退出;未创建临时服务。尚未部署,未重新执行业务吞吐压测或全工作区测试。
|
||||
|
||||
本轮边界:
|
||||
|
||||
- 本次限制针对新生产的单条 JSON payload,不包含 RESP 外壳、完整读取批次、历史消息、PEL、DLQ 或进程总 RSS。原始 JSON 树、headers、metadata 的存活内存及解析临时值不因此获得统一字节预算;默认 128 条批次、32 个 worker 仍可能同时接收很多消息。真正的接收预算仍需要读取前预留和受控连接/解码器。
|
||||
- 对过大的原 metadata 采用保守拒绝,避免为判断最终能否缩小而先深拷贝整个对象;即使后续规范化理论上可使其变小,也使用原事件回退。模型分类 helper 的超长输入临时分配仍需后续审查,本轮不宣称所有计费提取都有硬性内存上限。
|
||||
- 没有新增持久化超限旁路。若消息无法编码且受限数据库回退也失败,用量落库会失败,并通过日志和指标暴露;不能把有限本地缓冲称为可靠落盘。超大窗口增量聚合、完整请求优雅排空及目标环境长期压测仍待处理。尚未部署。
|
||||
|
||||
## 第十轮修复状态
|
||||
|
||||
- **阻塞读取改为独占连接:** 原 blocking stream lane 通过轮询复用 `ConnectionManager`,快 worker 再次读取时可能命中其他 worker 正在执行 `BLOCK` 的连接,造成队头阻塞;取消调用 future 后,后台 driver 仍可能继续执行旧命令。现改为有固定容量和信号量的独占池,先取得空闲槽位再发命令,等待者取消不会影响现有读取。连接和未 spawn 的 driver 由同一查询持有,取消、超时及解析失败一起丢弃,下一次使用该槽位时重建;完整解析成功才回池复用。保留原 lane 数量、认证、数据库、RESP 配置及超时/延迟统计。
|
||||
- **积压重领继续扫描游标:** 原运行时丢弃 `XAUTOCLAIM` 返回的下一扫描位置,worker 每次从 `0-0` 开始。当大量近期活动的待确认消息占据前段,Redis 单次扫描可能返回空页,后段过期消息长期得不到重领。新增兼容的分页接口,保留下一位置和已删除消息 ID;worker 在成功响应后推进游标,空页和删除页也推进,读取错误保持原位置,扫描结束再从头开始。写入失败的消息不提前确认,仍留在 PEL,回绕或 worker 重启后可再次重领。原返回消息列表的公开接口和旧队列实现继续兼容。
|
||||
- **内存队列并发入队顺序:** 将序号分配移入队列插入的同一个锁范围。此前等待插入的生产者可能先取得较小 ID,其他生产者先插入并被消费后,较小 ID 会被读取游标永久跳过;现在 ID 顺序与实际插入顺序一致。内存后端同时实现重领分页,按数值序号推进。
|
||||
|
||||
本地验证:
|
||||
|
||||
- `cargo check --locked -j 1 -p aether-runtime-state -p aether-usage-runtime` 通过。日志:`/tmp/aether-round10-core-check.log`。
|
||||
- 运行时状态全部 84 项、用量模块全部 300 项测试通过,共 384 项,包含本轮新增 10 项:连接池 2 项、内存队列 2 项、worker 游标 2 项、真实 Redis 4 项。覆盖连接容量、等待取消、多任务竞争、锁等待下的 ID 顺序、空页/删除页、读取失败重试、写入失败后回绕及原有记账流程。日志:`/tmp/aether-round10-queue-tests.log`。
|
||||
- 真实 Redis 测试另以 `--nocapture` 运行同一份测试程序,确认 6 次隔离实例就绪、没有跳过,4 项全部通过,不重复计入上述 384 项。使用本机 Redis、ACL 认证及数据库 7,覆盖 RESP2/RESP3、满池等待不发新命令、其他 lane 继续服务、快消费者复用空闲连接、取消/超时后服务端旧连接消失、重连保留认证/数据库,以及空扫描页后的 PEL 尾部和删除项恢复。日志:`/tmp/aether-round10-redis-receive-tests.log`。
|
||||
- 最终 `cargo build --locked -j 1 -p aether-gateway --bin aether-gateway` 在运行约 18 分 36 秒后因本机资源压力主动停止,退出码 143,不计为构建通过;停止前没有编译错误,日志:`/tmp/aether-round10-gateway-build.log`。期间清理了三份当前构建未使用的旧增量缓存,将可用磁盘从约 3 GiB 恢复至 7 GiB,但内存压力仍使编译持续缓慢。本轮没有修改网关入口、配置或指标,新增逻辑已在上述核心模块中验证;未重复编译网关测试目标,现有网关程序仍是上一轮产物。
|
||||
- 修改文件格式和 diff 检查通过。本轮测试、编译和临时 Redis 进程均已退出。尚未部署,未重新执行业务吞吐压测或全工作区测试;资源允许时仍需补跑最终网关构建。
|
||||
|
||||
本轮边界:
|
||||
|
||||
- 本轮修复连接占用和积压恢复,不是完整读取批次的字节预算。历史超大消息、RESP 解码、默认 128 条批次和多个 worker 的总驻留内存仍需后续控制;上一轮新消息 1 MiB 上限保持有效。
|
||||
- 连接生命周期的取消控制目前仅用于阻塞 `XREADGROUP`。非阻塞 stream 命令及 `XAUTOCLAIM` 仍沿用原共享连接;关闭连接不能撤销 Redis 已执行的读取,已进入 PEL 的消息仍依赖重领。内存后端分页仍扫描并排序符合条件的待确认条目,本轮未建立该扫描的硬性内存上限。
|
||||
- 超大窗口增量聚合、完整请求优雅排空及目标环境长期压测继续待处理。尚未部署,测试结果不能用来推断生产 RPM 上限。
|
||||
|
||||
## 第十一轮修复状态
|
||||
|
||||
- **读取批次的共享预留:** 新增进程级 usage worker payload 预留,默认总量 128 MiB、单批目标 8 MiB。根据当前 `queue_payload_max_bytes` 推导实际 `COUNT`,默认由 128 条降到最多 8 条;读取与重领、所有 worker 和自动扩容后的新 worker 共用同一份额度。许可在发命令前取得,并保留到原始字段处理、记账及 ACK 完成;取消、失败和空响应释放,预算不足时在原任务内等待,不新增缓存任务。收到消息后按字段值实际长度缩减多余预留。
|
||||
- **兼容历史消息与配置:** `AETHER_USAGE_QUEUE_READ_PAYLOAD_BUDGET_BYTES`、`AETHER_USAGE_QUEUE_READ_BATCH_PAYLOAD_BYTES` 分别控制总额与单批目标;0/非法值回退默认,极大值收敛到约 4 GiB 的有效额度,单批目标不超过总额。单条配置上限大于总额时明确返回配置错误,避免等待永远拿不到的许可。历史消息或其他生产者的大消息继续原记账流程,不在持有部分额度时等待追加,也不因超估算而删除或死信。
|
||||
- **重领连接与停止:** `XAUTOCLAIM` 改用上一轮的独占连接池,完整解析成功才回收连接。取消/超时会同时释放查询和 driver,避免归还读取预留后旧命令仍在后台接收。等待池容量也纳入原命令期限,指标归入实际使用的 `blocking_stream` lane;worker 取得重领响应前可被 shutdown 取消,取得响应后完成原处理与确认。读取与重领之间不嵌套持有池连接。
|
||||
- **扩缩容与观测:** worker 上报实际请求的 `COUNT`,防止读满 8 条却按 128 条误判为未满、压制扩容。新增 8 个 `usage_runtime_queue_read_*` 指标,涵盖总额、单批目标、当前预留、等待者、等待次数、累计字段字节及超估算条目/批次。累计字段字节包含字段名和值;预留及超估算判定使用值长度,合法 payload 恰好达到上限不会因字段名 `payload` 多出 7 字节而被误报。次数包含再次重领,不是唯一事件数。
|
||||
- **计费查询失败的恢复语义:** 原 worker 吞掉计费补全错误后继续写入/结算;暂时的数据库超时可能被后续“缺少实际费用”的永久错误覆盖,导致错误死信和 ACK。现在先传播原始错误,失败条目留在 PEL,后续重领再尝试;真正返回成功的无价格事件沿用原行为。三个直接落库入口也统一在补全失败时停止写入,不能误报已持久化或完成有序终态;已有受限数据库回退本来就正确停止,继续沿用。永久错误归档前先释放已解码事件,减少与原字段、死信编码的同时持有。
|
||||
|
||||
本地验证:
|
||||
|
||||
- 运行时状态全部 87 项测试通过,包含本轮新增 3 项真实 Redis 重领回归。日志:`/tmp/aether-round11-state-tests.log`。
|
||||
- 用量模块最终全部 321 项通过,包含本轮新增 9 项预留、8 项 worker、4 项直接落库回归;连同运行时状态共 408 项,本轮新增 24 项。覆盖跨 Queue/worker 共享额度、16 个并发任务竞争、极大配置不溢出或永久等待、读/重领实际 COUNT、慢写/ACK 持有、错误和停止释放、历史超估算消息继续处理,以及价格查询超时不提前写库或确认、后续成功尝试使用准确费用。日志:`/tmp/aether-round11-usage-final-tests.log`。直接落库回归中的恢复是后续显式调用,不是新增自动重试。
|
||||
- 直接运行本轮已编译的真实 Redis 接收测试程序,7 项全部通过,确认 9 次隔离实例就绪、没有跳过;不重复计入上述测试数。RESP2/RESP3、ACL、数据库 7 下验证读/重领取消和超时、服务端旧连接释放、PEL/游标/删除项恢复,以及共池时等待期限、等待取消、释放单个槽位后继续读取和重领。日志:`/tmp/aether-round11-redis-receive-tests.log`。
|
||||
- 修改文件格式与 diff 检查通过;网关 `cargo check --locked -j 1 -p aether-gateway --bin aether-gateway` 通过,耗时 3 分 14 秒,日志:`/tmp/aether-round11-gateway-check.log`。本轮测试、检查和临时 Redis 进程均已退出。本轮没有重复执行上一轮因本机资源压力中止的完整代码生成与链接,最终网关程序仍需在资源允许时构建;未部署,未执行业务 RPM 压测或全工作区测试。
|
||||
|
||||
本轮边界:
|
||||
|
||||
- 这是按当前生产配置估算的逻辑 payload 预留,不是网络接收字节或进程 RSS 的硬上限。滚动发布、其他实例使用更高上限、历史消息和直接写入额外字段都可超过估算。字段结构、字符串容量、Redis RESP 解码、连接缓冲高水位、解码 JSON 及死信序列化不由此获得硬上限;旧公开 Vec 读取接口保持兼容,不携带处理阶段许可。
|
||||
- 死信仍先追加再确认源消息,异常重试可能重复归档;历史大消息的死信 JSON 编码也没有独立硬字节预算。后续需要按源消息身份幂等的转移及受控超大消息恢复,不能以截掉原始账务字段或提前 ACK 代替。
|
||||
- 直接落库失败不会因此新增持久化重试渠道。超大窗口增量聚合、完整请求优雅排空及目标环境 RPM 压测仍待处理;尚未部署。
|
||||
|
||||
## 第十二轮修复状态
|
||||
|
||||
- **死信原子转移:** 内置 Redis 后端以一次 Lua 调用检查指定消费组的精确 PEL 身份,再追加完整死信、ACK 并删除源 ID;并发重领或提交成功但响应丢失后的重复调用不再次追加。Memory 后端在同一队列锁内完成转移,序号也在锁内分配。新增可选 trait 接口,默认返回 `None` 且无副作用;旧外部实现仍可使用原来的非原子追加后确认,内置转移报错不会降级为非原子写入。公开 `push_dead_letter` 保留原追加行为及 `{entry_id, fields, error}` JSON 格式。
|
||||
- **先检查,再写入:** 校验完整 canonical `u64-u64` ID、不同的源与目标键及非空字段;Redis 在首写前检查 PEL、目标类型和 XADD/XACK/XDEL 权限,避免可预见的脚本错误导致部分归档。Lua 不将 ID 转为浮点数,保留大整数精度。正文已被 trim 但 PEL 仍存在时,可使用调用者持有的完整字段归档。操作使用既有独占连接和 owned driver,等待池容量也计入超时,完整解析成功才回池;取消不能撤销已经完成的 Redis 命令,重试依靠 PEL 状态避免重复。
|
||||
- **独立的编码预留:** 新增 `AETHER_USAGE_DLQ_ENCODING_BUDGET_BYTES`,默认 64 MiB,及 `AETHER_USAGE_DLQ_ENCODING_MAX_JOBS`,默认 4。按原文字符串长度、JSON 最坏 6 倍转义及字段结构分隔符一次预留逻辑字节,使用 checked 运算和有界 writer;预留成功后才复制兼容接口输入或启动后台编码。worker 直接移动现有原始字段,永久记录错误时先释放解码事件。额度不足或单条过大立即失败,原消息保留在 PEL,不截断账务字段。后台任务取消等待后仍持有自己的许可,许可随编码结果保留到队列写入完成;与读取预留独立,避免持有部分读取额度再等待追加。
|
||||
- **worker 状态与指标:** 成功原子转移直接使用实际 ACK 数并报告一次死信,不再加入批次 ACK;返回 `NotPending` 不报告已归档,也不额外删除源消息。普通记录与旧后端追加仍批量确认,改为报告实际返回的 ACK 数。某条消息的存储转移失败时,只确认此前已成功处理的前缀,失败条目及尚未处理的后缀等待重领。编码预算拒绝及编码失败单独返回延期状态,保留坏消息并继续同批其他条目,只确认成功项,批次末尾仍报告失败;避免永远超过总预算的坏消息在每次重领时持续挡住正常账务。归档成功日志移到写入返回之后。新增 7 个 `usage_runtime_dlq_encoding_*` 指标:总额、最大/当前任务、预留字节、容量拒绝、超限拒绝及编码次数;次数包括重复尝试,编码成功不等于归档成功。
|
||||
|
||||
本地验证:
|
||||
|
||||
- `cargo test --locked -j 1 -p aether-runtime-state` 全部 102 项通过;本轮新增 15 项,包括 8 项 Memory 转移、6 项真实 Redis 和 1 项返回解析验证。测试编译耗时 13.62 秒,执行 2.45 秒;日志:`/tmp/aether-round12-state-tests.log`。
|
||||
- `cargo test --locked -j 1 -p aether-usage-runtime` 全部 341 项通过;本轮新增 20 项,包括 8 项编码预算、3 项队列接线、8 项 worker、1 项配置验证。测试编译耗时 30.79 秒,执行 1.16 秒;日志:`/tmp/aether-round12-usage-tests.log`。连同状态模块共 443 项通过,本轮新增 35 项,无编译警告。覆盖完整原文及所有转义、精确上界和溢出、并发饱和、未 poll/后台运行/存储等待取消、panic 释放、原子失败不回退、ACK 实际计数、成功前缀确认、超大坏消息延期后正常后缀继续、后续调高预算恢复,以及旧后端和公开追加兼容。
|
||||
- 直接运行已编译的 `redis_dead_letter_transfer_ --nocapture`,6 项全部通过,确认 RESP2/RESP3、ACL 认证及数据库 5 下共 8 次隔离实例就绪,没有跳过;不重复计入上述 443 项。覆盖 16 个并发调用只追加一份、丢弃成功结果后重试、分别拒绝 XADD/XACK/XDEL 时无部分写且修复后可恢复、错误目标类型/消费组/ID/同键/空字段、正文 trim 后仍保留 PEL、超过 Lua 整数精度的 ID。日志:`/tmp/aether-round12-redis-transfer-tests.log`。取消/超时连接释放同时由状态模块中的前两轮 owned-driver 回归覆盖。
|
||||
- `cargo check --locked -j 1 -p aether-gateway --bin aether-gateway` 通过,耗时 2 分 23 秒,无警告;日志:`/tmp/aether-round12-gateway-check.log`。修改文件格式及 diff 检查通过;测试、检查和本轮临时 Redis 进程均已退出,现有服务未变更。本轮未重复执行前轮因资源压力中止的完整代码生成和链接,网关可执行产物仍需后续完整构建;未部署,未进行目标环境业务 RPM 压测或全工作区测试。
|
||||
|
||||
本轮边界:
|
||||
|
||||
- 原子性和重复抑制按同一源 stream、消费组及源 ID 生效,不是永久去重索引。`NotPending` 只说明 PEL 不存在,外部 ACK、trim/delete、消费组销毁重建及其他消费者都可能改变这个状态,不能据此推断曾经归档。Redis XDEL 后其他消费组仍可能保留 PEL,Memory 沿用删除全部组 PEL 的既有语义;本轮不提供跨消费组的全局归档去重。
|
||||
- Redis 转移要求 7+ 的 `redis.acl_check_cmd` 和 `EVAL/TYPE/XPENDING/XADD/XACK/XDEL` 权限。Lua 错误本身没有回滚能力;脚本预检当前已知可失败的前置条件,不替代 Redis 持久化及故障恢复保证。Cluster 两键必须同 slot,当前默认 stream 键没有自动改名或迁移;缺能力、权限或跨 slot 失败会保留源消息,不能自动降级。
|
||||
- 编码预算覆盖任务所持原文长度与 JSON 上界,包含存储等待阶段,仍不是进程 RSS 或网络缓冲硬上限。字段容器、字符串多余容量、内存后端持久副本、Redis Cmd/packed-command/连接缓冲副本不计入;公开追加及旧后端可能沿用共享 driver。0/非法环境值回退默认,bytes 最大约 4 GiB,jobs 最大 128。保守 6 倍估算会拒绝实际编码较小的超大原文;这类存量需调高额度后重试或受控恢复,本轮未新增自动大消息通道。
|
||||
- 已提交但响应丢失时可以避免重复归档,当前进程的成功计数可能少计;普通 ACK 成功后 XDEL 失败也可能少计 ACK,指标不是持久化账本。死信总量及保留期限仍需运营管理,本轮没有静默修剪死信。完整请求优雅排空、超大窗口增量聚合与目标环境业务 RPM 压测仍待处理;尚未部署。
|
||||
|
||||
## 第十三轮修复状态
|
||||
|
||||
- **TCP 层先限量:** 原入口每次 accept 后都创建独立连接任务,HTTP 请求限流尚未生效的握手及空闲 keep-alive 不受请求 gate 保护。新增 frontdoor `HttpConnectionBudget`,二进制入口的全部监听分片和指标使用同一个 Arc;accept 后立即尝试取得许可,满额直接关闭刚接入的 socket 并让出执行,不创建 HTTP 任务或额度等待队列。先 accept 再准入,避免无流量的 reuseport 分片预占许可、饿住有流量的分片。
|
||||
- **许可跟随 socket:** 将许可放入底层 AsyncRead/AsyncWrite 包装,完整透传读、写、flush、shutdown 及 vectored write。socket 先释放、许可随后归还;HTTP/1 upgrade 会携带整个 IO 包装,所以连接 future 返回后 WebSocket 仍占一个许可,直到升级连接释放。HTTP/2 同一 TCP 内多路流共用一个许可,原 HTTP 请求和 WebSocket 会话 gate 保持独立;取消、解析失败及首个请求头超时也会释放底层 IO。
|
||||
- **accept 错误恢复:** 原 `listener.accept().await?` 会退出任意出错的监听任务,主入口随后停止其他监听分片。改用共同的 accept helper:ConnectionRefused/Aborted/Reset 重试,其他错误记录后退避一秒再试,资源耗尽不会触发无等待的重复 accept。兼容 `serve_tcp` 入口也使用相同预算和恢复逻辑。
|
||||
- **容量与观测:** 新增 `AETHER_GATEWAY_MAX_HTTP_CONNECTIONS` / `--max-http-connections`。未配置或 0 时按请求上限与 WebSocket 上限之和自动推导;自动和显式配置都限制在 1 至 65536,已知 FD soft limit 时再限制为 `max(1, (FD - 256) / 2)`。启动日志记录实际值,新增 `gateway_http_connections_limit/in_flight/high_watermark/rejected_total/accept_errors_total` 五项指标。
|
||||
|
||||
本地验证:
|
||||
|
||||
- `cargo test --locked -j 1 -p aether-gateway-frontdoor` 全部 43 项通过,无警告,包含本轮新增 9 项连接回归。覆盖默认/显式/0/FD 极值、不溢出的容量推导、IO 先于许可释放、拒绝立即消费 socket、读写及 vectored write/半关闭透传、两个真实 listener 共享 limit=1 后恢复、首次 poll 前取消、真实 HTTP/1 首头超时和解析失败、101 upgrade 后 HTTP future 已结束但升级 echo 仍持许可、真实 HTTP/2 两条并行请求共用一个连接许可,以及注入 accept 错误后继续服务、资源错误每次精确退避一秒。日志:`/tmp/aether-round13-frontdoor-tests.log`。暂停时钟的退避测试未真实耗尽进程 FD。
|
||||
- `cargo check --locked -j 1 -p aether-gateway --all-targets` 最终通过,耗时 3 分 42 秒,无警告,覆盖主程序、库及测试/示例等目标;日志:`/tmp/aether-round13-gateway-final-check.log`。本轮另新增 2 项 AppState 共享预算和指标接线测试,已随全部目标完成编译检查,但没有重新生成及执行大型网关测试程序,不计入上述 43 项执行通过数。初次检查发现新增测试误用了不存在的 `for_tests` 方法,已按现有测试模式修正为 `AppState::new()`;初次退出码 101,不计为通过。
|
||||
- 修改文件格式及 diff 检查通过。frontdoor 测试新增三个已锁定版本的开发依赖引用,Cargo.lock 仅增加对应依赖名,没有升级依赖版本。本轮测试、检查及临时 TCP 连接/任务均已退出,现有服务未变更;没有重复进行网关完整代码生成和链接,也没有部署或执行业务 RPM 压测。
|
||||
|
||||
本轮边界与后续发现:
|
||||
|
||||
- 这是已准入的入站 socket 数量上限,不是 HTTP/2 请求任务、全部 FD 或进程 RSS 上限。每个监听分片在 accept 与同步准入之间可短暂持有一个待判定 socket,kernel backlog、上游、Redis、数据库及其他入口不计入。容量规划仍需实测;健康检查使用同一入口,也可能在连接满额时被拒绝。
|
||||
- 满额发生在 HTTP 解析前,客户端看到连接关闭而不是 429/503;客户端和反向代理的重试应有退避。成功请求结束后 keep-alive 连接继续占额度,直到连接释放;本轮没有新增空闲连接回收、强制流期限或完整请求优雅排空。现有超时及流式传输语义保持原样。
|
||||
- 公共兼容 `serve_tcp` 每次调用使用独立预算,默认 4096,可用同名环境变量覆盖,最大 65536;它没有二进制入口的请求/WS 容量配置和 FD 探测,也未将预算绑定到其默认 AppState 的五项指标。二进制入口拥有 FD 约束、跨分片共享及指标接线。
|
||||
- 另确认 usage 窗口 Lua 每次准入全量清理过期成员,集中到期可能阻塞 Redis。不能直接换成分批删除加当前窗口 ZCOUNT:合法并发请求的时间参数可能乱序到达,后来的较早 cutoff 会重新计入此前逻辑上已过期但未物理删除的成员。保持精确额度需配套单调清理水位等状态设计;本轮没有修改这段逻辑。相同终态的事件级及 stored-record 级成本预留协调可能重复取得 PostgreSQL 用户行锁,后续可研究携带匹配身份的首次结果,不能无条件删除任一调用。
|
||||
- usage 独立 runtime 的本地重试渠道仍缺少完整停止与排空生命周期;当前数量有界,但进程退出前未入 Redis 的缓冲终态仍需统一处理。本轮没有修改停机流程、迁移架构或部署,业务 RPM 与长期排空压测仍待执行。
|
||||
|
||||
## 第十四轮修复状态
|
||||
|
||||
- **消除同次写入的重复成本协调:** worker 和直接落库原来先按事件协调成本预留,upsert 返回后又按存储记录协调一次,正常终态重复获取 PostgreSQL 用户行及预留行锁。现在首次调用返回的持久化结果同时匹配 request ID、用户、服务端预留 token、实际费用单位、终态和完成秒数时,携带一个仅限本次写入的内部结果;存储记录再次匹配全部字段才省去第二次协调。钱包结算、原用户级锁、费用检查及幂等性继续执行。
|
||||
- **保留冲突和重试语义:** 首次返回 `None`、返回其他终态或身份、upsert 后记录变化,都继续按存储记录重新协调。首次协调出错仍在 upsert 前停止;upsert 失败后的新尝试重新协调,不跨请求或重试缓存结果。公开协调与结算 API 签名不变;没有更改数据库表或放宽账务条件。
|
||||
- **修正验证工具:** PostgreSQL 结算基线补齐有余额的钱包及所属用户,并核对结算记录、快照、outbox、钱包消费和供应商累计值,检查失败返回非零退出码。此前无钱包的全量拒绝不能算吞吐成功。网关夹具补齐启动流程会创建的默认路由,通过 testkit 对象读取受保护的指标,缺失必要指标直接报错;容量报告保留 HTTP 状态和错误样本。探针新增排空基线与最终必要指标字段,判定阈值不变。网关阶梯夹具使用与主程序相同的 8 MiB Tokio worker 栈,避免 debug 路由调用链在默认小栈上溢出。
|
||||
- **隧道夹具按当前入口运行:** 仅在 `testkit` 功能下增加适配入口,将测试请求交给现有网关签名验证、正文完整性校验和临时 spool 流程,再进入内部 relay;没有开放跳过鉴权的 HTTP 路由。每条压力请求使用独立签名和 nonce。每个档位先检查合法签名成功并返回完整正文,无签名、篡改正文及重放均被拒绝;模拟节点通过并发计时等待响应,避免旧夹具在单个 WebSocket 读循环逐条 sleep 造成串行瓶颈。每个档位结束后显式取消并等待模拟节点任务。
|
||||
|
||||
本地验证:
|
||||
|
||||
- `cargo test --locked -j1 -p aether-usage-runtime`:349 项全部通过,本轮新增 8 项,包含多字段不匹配矩阵、失败/取消/零费用、失败重试、256 个同用户并发写入和 32 次重复投递。重复投递使用真实 Memory 结算仓库,余额只扣一次。日志:`/tmp/aether-round14-usage-tests.log`。`gateway_pressure_probe` 既有 21 项测试全部通过,日志:`/tmp/aether-round14-probe-tests.log`。
|
||||
- 本轮已成功完整构建并链接网关、容量基线、结算基线和压力探针,补上第十至十三轮只做编译检查而未更新可执行文件的验证。主要日志:`/tmp/aether-round14-full-build.log`、`/tmp/aether-round14-final-harness-build.log`;无编译警告。
|
||||
- 主网关真实 TCP 冒烟:2 个监听分片共享连接上限 4,保持 4 条空闲连接后,另外 32 条连接在共 7 ms 内被关闭;释放后健康检查恢复 200,认证指标确认 high-watermark=4、rejected=32。网关正常退出,日志:`/tmp/aether-round14-connection-smoke.log`。
|
||||
- 隔离 PostgreSQL 14 结算热点:同一用户、同一钱包,100 并发完成 2000 笔真实结算,0 失败;2000 条 usage、结算快照及已处理 outbox 完整,待处理为 0,钱包消费与供应商费用均为 2.00 USD。耗时 2332 ms,857 次/秒,P95=251 ms;采样锁等待最高 62 个、最长 549 ms,同一钱包仍需串行扣费。报告:`/tmp/aether-round14-postgres-settlement-final.json`。
|
||||
- 网关同步/流式、执行运行时同步/流式和隧道共 5 个阶梯场景,在 8/32/128/256 的 gate 上限各执行 8 倍请求,共 16928 条全部 HTTP 200,无拒绝或读取失败。前四场景各 3392 条,隧道共 3360 条,隧道并发为上限减 1,因为 WebSocket 会话占一个许可。最终前四场景在途归零,隧道采样时只剩该会话,high-watermark 均达到对应上限。每个档位的签名、正文和重放预检通过。报告:`/tmp/aether-round14-capacity-final.json`;最终构建日志:`/tmp/aether-round14-capacity-auth-final-build.log`。这些场景是 Memory 仓库/模拟节点功能和容量验证,与下面真实数据库业务压测分开解读。
|
||||
- 主网关 + 隔离 PostgreSQL/Redis + 本地模拟上游,8 个 API key、4 个网关 Tokio worker、2 个监听分片、HTTP 请求上限 256、TCP 上限 512、数据库池上限 24,执行以下约 2 秒的流式请求,要求完整读取且出现 SSE `[DONE]`:
|
||||
|
||||
| 并发 | 请求数 | 成功 / 失败 | 实测 RPS | 首字节 P95 | 完整响应 P95 | 排空判定耗时 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 16 | 128 | 128 / 0 | 8.01 | 123 ms | 2062 ms | 9.93 s |
|
||||
| 64 | 512 | 512 / 0 | 32.09 | 69 ms | 2046 ms | 9.86 s |
|
||||
| 128 | 1024 | 1024 / 0 | 57.34 | 106 ms | 2074 ms | 119.21 s |
|
||||
|
||||
业务压测报告目录:`/tmp/aether-round14-pressure.UYCeSq`。全部 1664 个请求为 HTTP 200;SQL 验证全部 completed,input/output/total tokens 分别为 1664/33280/34944,没有缺失首字节时间或单条 token 不匹配。所有档位必要排空指标齐全并通过连续安静期检查,最终用量队列、PEL、DLQ、outbox、请求在途和数据库锁等待均为 0。128 并发档网关 RSS 峰值约 190 MiB、末值约 147 MiB,FD 从峰值 338 降至 62,Tokio 活跃任务降至 85;没有要求 RSS 或常驻后台任务归零。
|
||||
|
||||
本轮边界:
|
||||
|
||||
- 本地 debug 构建、模拟上游和短时压测不能推导生产 RPM 上限,也没有同环境修复前后的性能对照。模拟上游费用为 0,非零钱包金额另由上述 PostgreSQL 热点及 Memory 幂等测试验证。临时 PostgreSQL 夹具关闭 fsync、同步提交及 full-page writes,热点吞吐数不代表开启生产持久化后的性能。
|
||||
- 阶梯夹具使用 75 ms 模拟同步/隧道响应或 3 次 25 ms 流式间隔。256 并发时网关同步/流式 P95 分别为 770/808 ms,超过该夹具的 300 ms 延迟预算,吞吐从 128 并发的 596/754 RPS 降至 537/524 RPS;全部请求成功不等于此档位容量合格。执行运行时 P95 为 104/96 ms,隧道为 115 ms。需在目标环境定位网关层 CPU、调度及后台写入开销后决定并发上限,不应直接按本地峰值放大配置。
|
||||
- 128 并发后的完整排空约需两分钟;此前 45 秒检查及一次 120 秒检查未通过,不能作为快速回收的证据。当前复测确认最终回收,但仍需目标环境下长期压力、连接空闲回收和停机排空验证,不能归因为已确认的泄漏或宣称完全消除积压风险。
|
||||
- 第十三轮发现的 Redis 过期窗口集中清理和 usage 本地缓冲优雅停止仍待后续处理。尚未部署、未调整生产配置,未执行全工作区测试。
|
||||
|
||||
本轮构建、测试、压测及临时服务均已退出,原有开发服务未改动;格式及 diff 检查通过。验证过程中出现的缺失默认路由、旧指标入口、未签名 relay、夹具小栈溢出和流 ID 类型编译错误均已修正;此前失败或全量拒绝的报告不计入上述通过结果。
|
||||
|
||||
## 第十五轮修复状态
|
||||
|
||||
- **整窗过期时异步释放:** Redis 用量准入脚本原来逐条同步删除整个过期 sorted set,集中到期的大集合会长时间占用命令线程。现在对超过 256 条的集合检查首尾时间:首条未过期时跳过清理,末条也已过期时在同一次 Lua 调用内 `UNLINK` 整个键,让 Redis 在后台释放旧对象,随后仍按原规则准入并写入新事件。小集合及混合新旧记录的窗口继续精确删除,空集合直接使用计数 0。
|
||||
- **保留时间和事务语义:** 所有规则先清理,再检查,最后全部消费;任一规则拒绝时仍不写入新事件。后续规则的过期清理不会被前面规则的拒绝跳过。没有新增清理水位、辅助键或改变事件时间戳;乱序时间、重放、释放补偿、`Retry-After` 和键 TTL 沿用旧语义,无需迁移 Redis 数据。`UNLINK` 不可用或被 ACL 拒绝时退回原同步删除,不把未清理的旧记录当作已清理。
|
||||
- **减去请求内重复工作:** 清理后计数只在当前 Lua 调用内复用,不再重新 `ZCARD`;Rust 端用 `OnceLock` 复用不可变脚本对象,避免每个请求复制脚本文本和计算 SHA1。每次调用仍单独构造键和参数;Redis 脚本缓存丢失时仍由现有客户端处理 `NOSCRIPT` 并重新加载。
|
||||
|
||||
本地验证:
|
||||
|
||||
- `cargo test --locked -j1 -p aether-runtime-state -p aether-usage-runtime`:状态模块 110 项、用量模块 349 项全部通过,共 459 项,0 失败;1 项较大数据计时基线默认忽略,已另行显式执行通过。日志:`/tmp/aether-round15-regression-tests.log`。
|
||||
- 新增 8 项真实 Redis 回归及 1 项计时基线。单独执行新增回归时确认 11 次隔离实例就绪,无 fixture 跳过,覆盖 Redis 8.0.3、RESP2/RESP3、ACL 认证及数据库 6。验证 10 万条记录一次 detach 后键复用、截止边界及 Lua 精确整数上界、混合窗口保留有效项、首规则拒绝时后续窗口仍清理、拒绝 UNLINK 后兼容、64 个并发争抢只放行 8 个、`SCRIPT FLUSH` 后幂等重载,以及 2 个协议各 512 步新旧脚本差分对照。对照逐步核对判定及完整剩余记录,包含时间回退、重复事件、额度变化和释放补偿。日志:`/tmp/aether-round15-redis-verified.log`。
|
||||
- 隔离 Redis 的 3 次对照各装入 30 万条同时过期记录。旧准入调用耗时 51.968 / 49.395 / 51.431 ms,新路径为 0.576 / 0.222 / 0.320 ms;另外装入 4096 条有效记录,各执行 1000 次持续准入,旧路径 P50/P95=86/123 us,新路径为 74/111 us,最终记录一致。日志:`/tmp/aether-round15-cleanup-timing-final.log`。测试验证逻辑和数据一致性,不以容易受本机负载影响的耗时阈值作为断言。
|
||||
- `cargo build --locked -j1 -p aether-gateway --bin aether-gateway` 完整构建与链接通过,耗时 4 分 15 秒,无警告;日志:`/tmp/aether-round15-gateway-build.log`。全工作区格式检查、新增 include 测试文件的独立格式检查及 diff 检查通过;本轮构建、测试和临时 Redis 实例均已退出,原有开发服务未改动。
|
||||
|
||||
本轮边界:
|
||||
|
||||
- 这是整窗过期的优化,不是所有清理操作的硬耗时上限。混合窗口仍会同步删除其全部过期项;若大量旧记录与有效项同时存在,阻塞风险仍在。分批逻辑清理需要额外状态与完整的乱序、重试和滚动升级设计,不能直接以当前 cutoff 计数代替物理删除。
|
||||
- Redis 自动 TTL 过期的释放由服务端过期策略决定,可能先于本脚本发生,不由此改为异步。UNLINK 加速也依赖服务端支持及 ACL 许可;兼容回退仍有原同步释放成本。异步释放期间旧对象仍可能占内存,本轮没有提供 Redis 内存或 RSS 硬上限。
|
||||
- 数字来自本机 debug 构建、隔离 Redis 和单窗口局部对照,不代表网关端到端吞吐或生产 RPM 容量;未修改生产 Redis 配置、未部署,未重复上一轮业务压测。usage 停机排空及前轮观察到的高并发延迟仍待处理。
|
||||
|
||||
## 第十六轮修复状态
|
||||
|
||||
- **混合窗口的稀疏存活项重建:** 窗口超过 1024 条、有效项为 1–256 条且过期项超过有效项四倍时,先复制有效成员及原始 score 到临时键,再在同一次 Lua 内 `UNLINK` 旧集合并替换。先验证权限并完整构建临时集合,保留原 TTL;临时键已存在、Redis 缺少 ACL 检查接口或可选命令受限时继续精确同步清理。无清理水位或持久辅助数据,乱序、重放、多规则拒绝和释放语义不变。有效项超过 256 条的混合窗口仍走同步路径,不能据此宣称所有窗口清理已有硬耗时上限。
|
||||
- **请求与用量统一停机:** 收到退出信号后停止所有监听分片接收请求,HTTP/1 和 HTTP/2 等待已接收响应;默认 30 秒到期后取消剩余连接任务,并使升级后的连接读写返回关闭错误。对可能脱离 HTTP 生命周期的流式终态、取消回调及 WebSocket 审计,在创建后台任务前登记 producer。另将整条代理请求及响应体纳入登记,覆盖响应头前断开、内联响应体及断开后的后台消费,避免先关闭用量入口再提交终态的竞态。路由的 `cancel_on_client_disconnect=false` 策略保持继续完成上游请求,后台处理受后续用量排空期限约束;配置为 true 时按原规则取消并持久化取消终态。
|
||||
- **排空本地用量缓冲:** 等待 producer 结束后关闭生命周期入口,立即冲刷延迟事件并唤醒重试退避,等待各阶段与候选记录写入完成。已确认写入 Redis 的事件可留待下次消费;Memory 队列必须实际消费,存在未恢复的本地 DLQ 不判定为成功。worker 完成当前写入及 ACK 后停止,随后结束空闲分发任务和专用 Tokio runtime。超时明确报错;运行库层面保留未完成工作供再次调用停机,主进程不会把超时打印成排空成功。
|
||||
- **缩短上游空闲连接滞留:** reqwest 和浏览器传输的每客户端、每 origin 空闲连接默认上限由 1024 降至 32,默认空闲期限为 15 秒;H2C 池默认上限由 512 降至 32,并补上主动清理所需 timer。活动请求及响应流不受空闲期限限制。配置项为 `AETHER_GATEWAY_UPSTREAM_POOL_MAX_IDLE_PER_HOST` 和 `AETHER_GATEWAY_UPSTREAM_POOL_IDLE_TIMEOUT_MS`;HTTP、用量停止期限分别用 `AETHER_GATEWAY_HTTP_SHUTDOWN_TIMEOUT_MS`、`AETHER_GATEWAY_USAGE_SHUTDOWN_TIMEOUT_MS` 配置,均默认 30000 ms。
|
||||
- **保留恢复过程证据:** 压测探针记录有界的逐次排空观测,包含用量 producer、延迟事件、业务队列、Tokio 任务、连接和 FD。必要指标、任务基线容差以及连续 7 秒安静期保持原判定,新增本地待处理指标有值时也必须归零。
|
||||
|
||||
已完成的本地验证:
|
||||
|
||||
- frontdoor 45 项、用量运行库 360 项、负载工具库 17 项、压力探针 21 项、状态模块 113 项、网关主程序 66 项、请求生命周期 8 项、传输模块 115 项及流式断开策略 1 项,共 746 项通过。状态模块另有 1 项默认忽略的计时基线,已显式执行通过。新增停机验证覆盖 producer 竞态、并发终态、重试失败后再次停止、延迟事件、多个 worker、写入途中停止、Memory 队列、响应头前断开、后台响应体、HTTP/1 和 HTTP/2 挂起处理器的实际释放,以及升级连接。日志:`/tmp/aether-round16-final-regressions.log`、`/tmp/aether-round16-state-regression.log`、`/tmp/aether-round16-main-final-tests.log`、`/tmp/aether-round16-request-lifecycle-tests.log`、`/tmp/aether-round16-transport-tests.log`。流式策略测试首次使用默认小栈时发生栈溢出;按网关入口相同的 8 MiB 栈设置 `RUST_MIN_STACK=8388608` 后通过,日志:`/tmp/aether-round16-disconnect-policy-test.log`。
|
||||
- Redis 新增混合重建测试覆盖 RESP2/RESP3、1/16/256/257 个有效项、TTL、非过期键、重复提交、已有临时键,以及 ZCOUNT/PTTL/EXISTS/UNLINK/RENAME/PEXPIRE 权限受限后的精确回退。定向 11 项测试确认 15 次隔离 Redis 实例就绪,无 fixture 跳过;原有 1024 步新旧脚本差分测试继续通过。日志:`/tmp/aether-round16-redis-tests.log`。
|
||||
- 同机 Redis 8.0.3 对照,30 万条过期记录分别混合 1/64/256 个有效项,旧脚本为 48.009/54.660/52.924 ms,新脚本为 0.320/0.377/0.496 ms,最终成员完全一致。整窗过期 3 次为旧脚本 56.802/51.804/50.886 ms、新脚本 0.744/0.258/0.318 ms。4096 条有效记录的持续调用 P50/P95 为旧脚本 87/131 us、新脚本 76/123 us;计时不作为断言阈值。日志:`/tmp/aether-round16-redis-timing.log`。
|
||||
- 网关主程序和压力探针完整构建、链接通过,无警告,日志:`/tmp/aether-round16-final-build.log`。补齐请求生命周期登记后再次完整构建主程序通过,日志:`/tmp/aether-round16-final-gateway-build.log`。
|
||||
|
||||
真实入口压力和停机验证:
|
||||
|
||||
- 使用隔离 PostgreSQL 14、Redis 8.0.3 和约 2 秒的流式模拟上游,8 个 API key、4 个网关 worker、2 个监听分片、请求上限 384、TCP 上限 768、数据库池上限 24。要求完整读取且出现 SSE `[DONE]`。本轮 PostgreSQL 未关闭持久化,启动对照实查 `fsync`、`synchronous_commit`、`full_page_writes` 均为 on;Redis 夹具关闭 AOF/RDB,因此只验证保留 Redis 进程时的网关重启恢复。
|
||||
- 连接池单变量对照使用同一构建,仅设置旧空闲上限/期限 1024/90000 ms 或新默认 32/15000 ms。旧配置的 128、256 并发均全部 HTTP 200,业务待处理指标分别约 2.8/5.1 秒归零,但两档都未通过 150 秒恢复检查,最后 Tokio 活跃任务为 229/350、FD 为 206/327;脚本返回失败,不记为恢复通过。新配置分别在 22.217/26.542 秒通过原有恢复判定,FD 降至 65/63。这确认本地长时间恢复的主要滞留来自空闲连接及相关任务,而不是业务队列持续积压。对照目录分别为 `/tmp/aether-round16-pressure.3xsT7P`、`/tmp/aether-round16-pressure.iysPcj`;不把一次对照的吞吐或首字节波动视为可靠容量提升。
|
||||
- 最后补齐整个代理请求的 producer 登记后,使用最终构建再次执行新配置,结果如下。必要排空指标齐全,最终队列 lag、PEL、DLQ、outbox、本地用量待处理及数据库锁等待均归零,无 worker 处理失败;恢复判定包含连续 7 秒安静期。
|
||||
|
||||
| 并发 | 请求数 | 成功 / 失败 | RPS | 首字节 P95 | 完整响应 P95 | 恢复判定耗时 | 最终 FD / Tokio 任务 |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 128 | 1024 | 1024 / 0 | 56 | 150 ms | 2141 ms | 22.325 s | 65 / 87 |
|
||||
| 256 | 2048 | 2048 / 0 | 115 | 155 ms | 2192 ms | 26.243 s | 63 / 85 |
|
||||
|
||||
最终报告目录:`/tmp/aether-round16-pressure.JI8vPF`,执行日志:`/tmp/aether-round16-pressure-final-new.log`,验证脚本:`/tmp/aether-round16-pressure.sh`。该轮 256 并发 FD 峰值 595、最终 63;RSS 峰值约 283 MiB,检查结束仍约 282 MiB,没有把短时间 RSS 不下降判为泄漏或声称内存已回到启动值。
|
||||
|
||||
- 正常 SIGTERM:32 条已开始输出的流全部完整结束,网关成功退出;重启同一数据库与 Redis 后,核对 3104 条 completed,逐条 input/output/total tokens 为 1/20/21,首字节时间无缺失。
|
||||
- 将 HTTP 停止期限缩短为 50 ms:默认继续完成策略下,32 个客户端都观察到流中断,网关继续完成其后台请求后成功退出;恢复后 3136 条全部 completed,token 合计 3136/62720/65856,逐条仍为 1/20/21。随后仅在测试数据库启用断开取消策略,另外 32 条流中断后全部落为 cancelled、HTTP 499、billing_status=void、token 为 0,首字节时间保留。最终数据库为 3136 条 completed 加 32 条 cancelled,未把默认继续完成误判为取消。
|
||||
- 最终脚本对 SQL 完整性失败显式退出。早期验证脚本误将默认断开策略预期为取消,并遇到旧 Bash 的条件失败未中止问题;已修正断言和预期,上述停机终态与 token 结论只采用最终目录的复测结果。
|
||||
|
||||
全工作区格式检查、新增 include 测试文件的独立格式检查及 diff 检查通过。本轮构建、测试、压测、临时 PostgreSQL/Redis 和模拟服务均已退出,原有开发服务未改动。
|
||||
|
||||
边界:
|
||||
|
||||
- 新连接池设置只限制空闲缓存,不能推导整个进程的 FD 或 RSS 上限,也不能替代活动请求并发预算。减少空闲连接会增加突发流量之间重新建立连接的次数;需要在目标环境观察握手成本及连接复用。
|
||||
- 有界停机无法保证 SIGKILL、依赖持续故障或超过停止期限时的本地未持久数据不丢失;本轮未引入磁盘日志或更换消息架构。进程管理器的强杀期限应覆盖两阶段停止和额外收尾时间。
|
||||
- 第十四轮短响应夹具在 256 并发的延迟超预算尚不能判定已解决;本地 debug、模拟上游和短时压力结果不代表生产容量。未部署或修改生产配置,未执行全工作区测试。
|
||||
|
||||
## 第十七轮修复状态
|
||||
|
||||
本轮继续处理混合过期 Redis 大窗口和短响应网关的历史记录读取开销,未部署生产。
|
||||
|
||||
- **Redis 混合大窗口:** 当过期项超过 4096、有效项超过 256 时,先执行只读规划,随后在独占连接上 WATCH 全部规则,每条命令最多复制 512 个有效成员;通过 MULTI/EXEC 校验期间没有源数据变化,最后在一个 Lua 调用中替换窗口并完成原有多规则检查与消费。旧进程的 ZADD/ZREM、TTL 变化也会使 WATCH 失效。复制期间不提前清理其他规则,避免乱序请求、重复事件和跨规则拒绝改变原有额度语义。
|
||||
- **维护资源限制:** 每个 Redis runtime router 最多两条按需维护连接,最多尝试八次,包含连接池等待的总耗时受原有命令超时约束;未配置超时时使用 30 秒上限。取消或失败会丢弃仍有 WATCH/MULTI 状态的独占连接,未提交副本有 60 秒 TTL。成功提交后没有额外的可失败网络收尾操作。缺少所需 ACL 能力或 Redis 不支持 ACL 预检时保留原有精确清理路径。
|
||||
- **候选内存仓库索引:** 请求更新不再全表查找逻辑主键,按 request_id 查候选;最近记录查询改用创建时间索引,在复制之前取 limit。三个索引在同一写锁中维护,覆盖 ID 替换、相同时间排序和删除空索引。所有写入入口保留清洗,读取不再重复重建已经清洗的 JSON。
|
||||
- **公共候选诊断清洗:** 提取借用 JSON 对象的内部函数,移除持久化清洗前的整份输入克隆、清洗结果克隆及诊断对象的再次克隆;字段白名单、诊断大小限制、管理员与公开投影语义不变。PostgreSQL 与 Memory 都调用这一公共函数。
|
||||
- **调度读取精简投影:** 新增 `list_recent_runtime` 读取身份、状态、计数及时间,Memory 从时间索引直接构建不带诊断的记录,PostgreSQL 查询不读取诊断和能力 JSON。调度与自适应 RPM 观察使用此路径,管理员和请求可观测性继续读取完整记录。精简前后并发计数、RPM 和失败冷却判断保持一致。
|
||||
|
||||
已完成 241 项检查:状态模块 119 项、候选仓库与数据契约 28 项、调度核心 92 项、隔离 PostgreSQL 14 精简投影检查 1 项,以及单独执行的 Redis 大窗口计时检查 1 项。PostgreSQL 检查使用独立临时实例和连接级临时表,包含 32 KiB 诊断字段,验证精简投影与完整记录的运行时字段一致,且完整读取仍保留诊断。最终网关压测程序编译通过;临时数据库均已停止。日志:`/tmp/aether-round17-state-complete-tests.log`、`/tmp/aether-round17-projection-tests.log`、`/tmp/aether-round17-scheduler-tests.log`、`/tmp/aether-round17-postgres-live.log`。
|
||||
|
||||
首次短响应采样确认调度等待 `list_recent` 的全局读锁,读路径原本复制全部历史记录再逐条重做 JSON 清洗;仅修索引后最近 128 条完整 JSON 的复制与清洗仍是热点,因此继续将调度读取改为精简投影。最终采样仍有候选仓库读写锁等待,但没有原先全量历史复制的放大行为,其他开销分布在请求处理、凭据解密和 JSON 构建。本机其他进程负载较高,不能直接与第十四轮数字比较。
|
||||
|
||||
短响应对照使用本轮修改前保留的二进制与最终二进制,同为本机 debug 构建、Memory 网关夹具、模拟上游约 75 ms、每点请求量为并发数的八倍。最终版本及随后复跑的对照版各执行 15344 个请求,全部成功,无失败或拒绝。网关 P95 单位 ms:
|
||||
|
||||
| 场景 | 并发 | 修改前复测 P95 | 修改后 P95 | 修改后 RPS |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 同步 | 128 | 1025 | 222 | 857 |
|
||||
| 同步 | 256 | 2049 | 408 | 800 |
|
||||
| 流式 | 128 | 569 | 263 | 678 |
|
||||
| 流式 | 256 | 1564 | 451 | 794 |
|
||||
|
||||
128 并发达到该夹具的 300 ms 预算;256 并发两种网关路径仍超过预算。最终版本中独立执行器和隧道曲线的 P95 均低于预算。结果位于 `/tmp/aether-round17-capacity-final.json`、`/tmp/aether-round17-capacity-control-recheck.json`。本机对照时系统 CPU 约 92%–99%,同一进程包含压测客户端、网关与模拟上游,不能将这里的 RPS 视为生产容量。用于 CPU 采样的额外长测有采样器干扰,未用于上述性能对照。
|
||||
|
||||
Redis 8.0.3 同机前后对照,均含 300000 条过期记录,单位 ms:
|
||||
|
||||
| 有效项 | 旧整次调用 | 新整次调用 | 旧最长命令 | 新最长命令 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 257 | 64.750 | 2.199 | 64.351 | 0.218 |
|
||||
| 4096 | 77.099 | 7.331 | 76.301 | 0.681 |
|
||||
| 32768 | 62.999 | 45.464 | 62.583 | 0.914 |
|
||||
| 150000 | 77.200 | 242.083 | 76.840 | 1.585 |
|
||||
|
||||
最终成员与旧脚本一致;最长命令来自隔离 Redis 的 SLOWLOG,包含 EVAL/EXEC。有效项多时整次清理更慢,但复制批次之间允许其他命令执行,降低对同一 Redis 其他请求的连续阻塞。原有整窗过期和不超过 256 个有效项的快速路径继续通过。4096 条有效项持续调用 1000 次,旧 P50/P95 为 155/286 us,新为 152/310 us;未将计时作为测试断言。记录:`/tmp/aether-round17-redis-timing.log`。
|
||||
|
||||
边界:复制需要临时保存有效成员,额外内存与有效项数量相关;频繁并发修改可能触发重试或达到命令期限,未提交的复制不会替换原始窗口。提交成功但回复丢失仍具有现有 Redis 命令的结果不确定性,应使用同一事件 ID 重试。ACL 不足或不支持预检的 Redis 仍走同步回退,不应把本优化描述为所有配置下的 Redis 总阻塞硬上限。代码层面的已复现放大路径已修复,但不能据此宣称所有容量问题均已解决;256 并发的最终验收仍需在代表性环境运行 release 构建并结合持续压测确认。未部署生产。
|
||||
|
||||
## 主要判断
|
||||
|
||||
系统已经有入口并发门、请求体内存预算、认证缓存合并、前后台数据库池隔离及多种有界队列。问题主要在于部分请求路径仍执行全量统计、锁范围过大,以及资源预算和超时没有覆盖完整生命周期。
|
||||
|
||||
这些问题会相互放大:每请求成本增加,使请求停留更久;在途请求增加后继续竞争 Redis、数据库和内存,客户端重试又增加负载。只提高入口并发数或数据库连接数不能消除这些瓶颈。
|
||||
|
||||
平均在途请求约为 `RPM / 60 × 平均请求持续秒数`。例如 600 RPM、平均持续 60 秒,约有 600 个在途请求;该例是容量计算,不是当前实例测量值。
|
||||
|
||||
## P1-1:压缩或未知长度请求一次占满全局请求体预算
|
||||
|
||||
**代码:**
|
||||
|
||||
- `crates/aether-gateway/frontdoor/src/body.rs:392`:带非 identity Content-Encoding,或缺少 Content-Length 时,预留 `min(单请求上限, 全局预算)`。
|
||||
- `apps/aether-gateway/src/state/app.rs:40`:默认全局预算 256 MiB;`apps/aether-gateway/src/headers.rs:20` 的单请求默认上限同为 256 MiB。
|
||||
- `apps/aether-gateway/src/state/app.rs:195`、`crates/aether-gateway/frontdoor/src/body.rs:201`:默认没有请求体完整读取超时。
|
||||
- `crates/aether-gateway/frontdoor/src/body.rs:154`:等不到预算则返回过载;默认等待预算 250 ms。
|
||||
|
||||
**结果:** 即使压缩后的请求只有 1 KiB,也会预留全部 256 MiB。压缩上传或未知长度上传的读取阶段因此被串行化;一条一直未传完的请求可以长期占住预算,其他需要读取请求体的请求陆续收到 503。这里的 256 MiB 是预留额度,并非立即分配的实际内存。
|
||||
|
||||
**验证:** 使用实际 `BodyBufferPolicy` 的独立程序复现:1 KiB gzip 请求预留 268435456 字节,剩余许可为 0;第二个普通 1 KiB 请求等待约 252 ms 后被拒绝,释放第一个预留后立即恢复。程序显式采用默认参数,验证的是预算准入,不是完整 HTTP 慢上传压测。源码位于 `/tmp/aether-frontdoor-budget-repro.rs`,可执行程序位于 `/tmp/aether-frontdoor-budget-repro`。`cargo test -p aether-gateway-frontdoor body::tests -- --nocapture` 的 13 项既有请求体测试通过。
|
||||
|
||||
**建议:** 设置适合实际上传大小和速率的读取期限;根据业务明确单请求大小上限,使一个普通上传不能耗尽全局预算。进一步改为分段、有界读取及解压预算,或为大型上传分配独立额度。增量预留必须避免多个请求各持部分预算、同时等待扩容导致死锁,不能简单取消当前保护。
|
||||
|
||||
## P1-2:调度请求执行 Redis 全库扫描和历史样本聚合
|
||||
|
||||
**代码:**
|
||||
|
||||
- `apps/aether-gateway/src/dispatch/pool_scheduler.rs:142`、`:985`:候选分页和 sticky 路径调用管理侧 runtime 统计函数。
|
||||
- `apps/aether-gateway/src/handlers/admin/provider/pool/runtime/reads.rs:126`:cache affinity 且 sticky TTL 非零时,扫描所有匹配会话,再 MGET 全部结果。
|
||||
- `crates/aether-runtime/state/src/redis/runtime.rs:289`:SCAN 循环直到游标归零,COUNT 200 是每轮提示,不是总量上限。
|
||||
- `apps/aether-gateway/src/dispatch/pool_scheduler.rs:1984`:扫描生成的会话总数和按 key 统计并不进入实际调度状态。
|
||||
- `apps/aether-gateway/src/handlers/admin/provider/pool/runtime/reads.rs:208`、`:225`:无条件对每个候选 key 拉取成本和延迟窗口的原始成员,即使相应排序或限额未启用。
|
||||
|
||||
**结果:** 一页 64 个不同候选 key 就产生 128 次窗口查询,另加会话扫描等操作。成本窗口默认 5 小时,历史样本按时间修剪;随着 RPM 增加,每个请求需要读取和聚合的历史数据也增加。`join_all` 并发等待不会消除 Redis 执行量及返回数据量。SCAN 和窗口读取都使用 Admin lane,管理请求也可能受到影响。
|
||||
|
||||
**建议:** 为调度建立独立读取接口,只读取当前 sticky 绑定及调度需要的状态;会话总数留给管理统计。按启用策略读取成本或延迟,改用增量计数、时间桶或后台维护的短时快照;保留严格额度检查的原子性。对相同池的刷新合并,避免每个请求重复拉取历史。
|
||||
|
||||
## P1-3:结算错误地锁住公共套餐行,跨用户串行
|
||||
|
||||
**代码:** `crates/aether-data/adapters/postgres/src/settlement.rs:482` 的 `user_plan_entitlements JOIN billing_plans ... FOR UPDATE` 没有限定锁的表。
|
||||
|
||||
**触发:** 普通用户的非零费用结算,且用户有有效套餐。即使套餐没有 daily_quota,查询也先锁行,之后才判断 `grants.is_empty`。
|
||||
|
||||
**结果:** 不同用户只要使用同一个套餐,就会争抢同一条 `billing_plans` 行。锁持续到事务结束,其间还可能汇总每日账本、写额度流水、更新钱包和结算快照。首先影响后台结算和队列消化速度,不能据此断言前台数据库池必然同时耗尽。
|
||||
|
||||
**验证:** 在隔离 PostgreSQL 14.17 中,两用户拥有不同 entitlement、共享一个 plan。事务 A 持原 SQL 锁时,事务 B 因 500 ms lock_timeout 失败,错误明确指向 `relation "billing_plans"`;对照使用 `FOR UPDATE OF user_plan_entitlements` 后事务 B 成功。临时 PostgreSQL 已停止。该验证证明锁冲突,不是完整结算吞吐压测;部署 Compose 使用 PostgreSQL 15。
|
||||
|
||||
复现脚本:`/tmp/aether-plan-lock-repro-20260909.sh`;本次日志:`/tmp/aether-plan-lock-repro.XoYxfO/`。
|
||||
|
||||
**建议:** 将锁范围限定为需要更新的用户 entitlement;明确套餐配置并发变更的一致性规则。添加真实双连接回归:同用户额度不能重复扣,不同用户同套餐不能互相阻塞。
|
||||
|
||||
## P1-4:首包之后的流缺少空闲期限
|
||||
|
||||
**代码:**
|
||||
|
||||
- `apps/aether-gateway/src/execution_runtime/stream/execution.rs:2699`:默认 inline 直通路径仅在首包前使用 timeout,首包后直接 `upstream.next().await`。
|
||||
- `apps/aether-gateway/src/execution_runtime/transport.rs:3375`:stream 不使用总请求 timeout。
|
||||
- `apps/aether-gateway/src/execution_runtime/transport.rs:4212`:该 reqwest 客户端配置连接超时,没有设置读取超时。
|
||||
|
||||
**结果:** 上游已经发送首包、随后不再发送数据也不关闭连接时,只要下游继续保持连接,该流就可能长期占据请求名额、provider 并发守卫及缓冲。坏流逐渐积累会压缩可用容量。target permit 在首次向客户端 yield 时释放,不能把它算作整条流一直占用的资源。
|
||||
|
||||
**建议:** 增加可按 provider 配置的上游空闲期限;计时以真实上游活动为依据,网关自己的 keepalive 不应重置它。超时后关闭上游并走一次终态结算,确保断开、取消和超时都释放请求及 provider 守卫。对合法长思考模型采用匹配其行为的阈值。
|
||||
|
||||
## P1-5:并发上限与实际常驻内存不匹配
|
||||
|
||||
**代码:**
|
||||
|
||||
- `apps/aether-gateway/src/main.rs:433`、`:467`:自动入口上限为每 CPU 1024,结合 FD 下调,但没有内存预算。
|
||||
- `apps/aether-gateway/src/execution_runtime/stream/execution.rs:167`、`:400`:Basic 模式每份流分析缓冲上限 5 MiB;Full 为 64 MiB。
|
||||
- 同文件 `:1993`、`:2031`:分别累积 provider 和 client 两份 body,包括直通路径。
|
||||
|
||||
**结果:** 两份捕获缓冲达到上限时,Basic 单流约 10 MiB,Full 约 128 MiB,尚未计入请求 JSON、转换状态、队列及其他内存。1000 条都达到 Basic 捕获上限的流,仅这两份内容就约 9.77 GiB。这是达到上限时的预算估算,不是普通小回复的固定内存或已测 RSS。
|
||||
|
||||
**建议:** 用实测每请求内存和 cgroup/物理内存确定入口容量;为响应捕获建立全局字节预算和截断策略。Basic 优先使用增量 usage/error 解析,直通时避免重复捕获同一内容。请求体读取预算不能充当整个流生命周期的内存保护。
|
||||
|
||||
## P1-6:审计压缩在 Tokio 线程和数据库事务内同步执行
|
||||
|
||||
**代码:** `crates/aether-data/adapters/postgres/src/usage/mod.rs:8457` 开始事务并锁 request;`:8522` 同步准备审计内容;`:12362` 序列化 JSON,`:12376` 执行 gzip level 6。`:12478` 附近最多处理四份请求/响应 body;inline 阈值为 0。
|
||||
|
||||
**结果:** 有 body 捕获,尤其大上下文、高并发时,CPU 压缩同时占据 Tokio 工作线程、数据库连接和事务锁。前后台连接池隔离不能隔离同一进程内的 CPU 和内存争用。
|
||||
|
||||
**建议:** 在开启事务前完成可独立准备的序列化和压缩,放到有并发和字节预算的 blocking worker;事务内只保留必须原子执行的读写。检查相同 body 的去重,避免单纯增加 worker 数量导致 CPU 和内存进一步饱和。
|
||||
|
||||
## P1-7:长期额度准入按用户串行扫描,且默认无锁等待期限
|
||||
|
||||
**代码:** `crates/aether-data/adapters/postgres/src/settlement.rs:276` 锁 `users` 行;`:611`、`:850` 在准入中取得该锁后,分别逐窗口 COUNT 请求预留或 SUM 成本预留。释放及对账还会争同一用户锁。`crates/aether-data/adapters/postgres/src/tx.rs:15`、`:116` 的默认读写事务不设置 lock_timeout 或 statement_timeout。
|
||||
|
||||
**触发:** 配置长期请求额度或成本上限;不能把普通短期 RPM 规则一概算入这条路径。
|
||||
|
||||
**结果:** 同一用户多个 key 的准入串行;窗口内历史越多,锁内工作越多。等待锁的事务还占用连接。池 acquire_timeout 只管取得连接之前的等待;Compose 的 idle_in_transaction_session_timeout 也不能终止正在执行的锁等待 SQL。
|
||||
|
||||
**建议:** 按用户和额度窗口维护原子聚合及预留,避免准入反复扫描明细。为前台和后台事务分别设定锁等待和 SQL 期限,失败时回滚并限制重试;精确额度和幂等结算约束必须保留。
|
||||
|
||||
## 次要放大器
|
||||
|
||||
- **P2,过载后重复计算:** `apps/aether-gateway/src/ai_serving/planner/state/scheduler.rs:100` 在 API key 并发受限时短间隔重做候选读取和排序。建议在昂贵规划前做准入,使用有界等待或通知。
|
||||
- **P2,探测前置任务未合并:** `apps/aether-gateway/src/maintenance/runtime/pool_quota_probe.rs:1675` 先 spawn,再去 Redis 去重及争锁。池恶化时仍随请求量创建任务。建议在 spawn 前按 provider 合并触发信号。
|
||||
- **P2,同步日志输出:** `crates/aether-runtime/base/src/tracing.rs:403` 直接写 stdout,`:695` 的文件写持全局 Mutex。磁盘或日志收集变慢时可能阻塞 Tokio worker。改为有界日志队列,并明确队列满时策略;当前没有证据证明这是本次故障主因。
|
||||
|
||||
## 修改和验收顺序
|
||||
|
||||
1. 先修公共套餐锁范围、读取期限及压缩上传预算问题;这些都有明确且局部的触发条件。
|
||||
2. 从请求调度移走管理扫描,按需读取运行态,避免每请求重算历史统计。
|
||||
3. 修流空闲期限,建立响应捕获全局预算,将压缩移出事务及异步工作线程。
|
||||
4. 优化长期额度聚合,并为数据库锁等待、SQL 执行和过载重试设置预算。
|
||||
5. 用独立测试实例及可控制延迟的 mock upstream 做阶梯压测;每档记录吞吐、P95/P99 首包及总耗时、RSS、CPU、队列积压,停止流量后检查资源能否回落。
|
||||
|
||||
生产定位至少需要:故障实例和版本、CPU/内存/FD 配额、实际 RPM 和平均流时长、是否使用压缩上传/账号池/套餐/完整 body 捕获。采集 `pool_runtime_state` 阶段延迟、Redis Admin lane 延迟、数据库 checked-out/lock waiting、usage 队列 lag 和 Tokio 任务数。CPU 低且锁等待高、Redis 延迟高、RSS 持续涨、请求体大量 503 分别对应不同路径,不能仅凭“卡死”选择一个原因。
|
||||
|
||||
现有 `crates/aether-testing/loadtools/src/bin/gateway_pressure_probe.rs` 可用于受控环境的压力和排空观测;不要用健康检查接口的吞吐代替真实 AI 调度及结算链路的容量。
|
||||
Reference in New Issue
Block a user