Skip to content
Merged
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 @@ -209,8 +209,11 @@ DETACHED_STREAM_METERING_RESERVE_BYTES=16777216
# 内置多进程间分摊)。
# 预算不足时请求等待现有前缀被消费,不会关闭门禁或误触发供应商切换。
# 未设置时使用统一自动内存预算;显式设置仍作为门控子项上限。
# 该上限仅在「系统设置 > 内存准入」开启时生效。
# STREAM_GATE_GLOBAL_PREBUFFER_BYTE_CAP=268435456

# 以下内存准入相关配置仅在「系统设置 > 内存准入」开启时生效(默认关闭)。
# 关闭时请求正文与流式门控前缀全部留在内存,不排队、不落盘、不返回本地 429。
# 受管业务内存总预算:默认根据可用 RAM + 0.5 * 可用 swap 自动计算。
# 同时遵守 cgroup v1/v2 可见祖先配额、memory.high,以及热点的物理余量。
# 该预算是应用软上限,不等于容器 RSS 硬限制。
Expand Down
8 changes: 6 additions & 2 deletions docs/research/ttft-memory-aware-admission.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

关联 #1473,部分涉及 #1466。v0.9.5 的门控按每请求 40 MiB 预留,默认四个 worker 各分到 64 MiB,因此每个 worker 同时只有一个请求能等待首内容。慢上游占住这个位置,其他请求即使上游很快也会被本地队列阻塞。

## 开关

内存准入由系统设置 `enableMemoryAdmission` 控制,默认关闭。关闭时分配器只记账不设限:租约与增长总是成功,不向 primary 申请授权;请求正文与门控前缀全部留在内存,不落盘;门控子限额 `STREAM_GATE_GLOBAL_PREBUFFER_BYTE_CAP` 与物化前的 V8 堆余量检查不生效;不会产生 `local_capacity_exceeded`。开启后下文全部机制生效。每个 worker 在读取入站正文前按系统设置缓存同步开关,保存设置后随缓存失效广播在各进程生效。

## 请求与门控

- 门控从 128 KiB 工作集开始,随自有字节块和解析堆栈实际增长。扩容不持有部分正文排队,不能立即取得容量就把前缀逐块暂存到磁盘。首内容之后按原顺序回放。
- 增量 JSON/SSE 分类只保存协议路径事实和有界错误预览,不为大工具参数、图片或回显帧创建完整对象。重复 JSON 键、错误优先级、compaction、终止帧和合法深层 JSON 保持既有语义。
- 入站正文只有一个流消费者;压缩输入和解压输出逐块检查大小、必要时暂存。JSON 物化前按字节和对象结构估算容量,密集小对象的预算高于同样长度的长文本。
- 入站正文只有一个流消费者;压缩输入和解压输出逐块检查大小、必要时暂存。JSON 物化前按字节和对象结构估算容量,密集小对象的预算高于同样长度的长文本。物化与解析期间按峰值 `8192 + 8 × 字节 + 结构` 预留;解析完成后收缩到请求持续持有的 `8192 + 4 × 字节 + 结构`(原始字节、UTF-16 字符串、对象结构与一份出站副本),流式响应与后台消费者期间只占用该持有量。按峰值持有到请求结束时,长时间流式请求积压会让账面占用达到实际常驻内存的 3 到 4 倍,额度耗尽后新请求全部在正文准入阶段返回 429。
- 过滤、重试和 multipart 仍保留完整语义。日志视图改为按需生成;未启用调试正文或 Langfuse 时不长期保存出站序列化副本。
- 首次向上游发请求前取得门控基础容量。本地准入最多等待 20 秒,容量不足返回本地 `429`、`local_capacity_exceeded`、`Retry-After: 1`;不触发供应商 failover、健康扣分或熔断。

Expand All @@ -30,7 +34,7 @@ hotBudget = min(autoBudget, headroom)

hotBudget 只在启动(多进程为全部 worker 就绪)时计算一次,作为固定上限。运行期每秒的收紧条件是 `limit = min(启动上限, 在用量 + headroom)`:0.6 折扣已体现在启动上限里,不再对剩余内存重复折扣。否则进程自身常驻内存(Next.js 基础堆等非受管分配)增长会在零租约时持续压低正文额度;只有真实物理余量低于启动上限或出现内存压力时才收紧。

Linux 使用 MemAvailable、SwapFree,同时检查可见 cgroup v2/v1 的成员和祖先限制,遵守 memory.high、swap 禁用及 v1 memory+swap 联合限额。无法确认容器 swap 额度时不增加 swap 容量;非 Linux 以可用物理内存保守估计。
Linux 使用 MemAvailable、SwapFree,同时检查可见 cgroup v2/v1 的成员和祖先限制,遵守 memory.high、swap 禁用及 v1 memory+swap 联合限额。cgroup 已用量按 working set 计算:v2 为 `memory.current - inactive_file`,v1 为 `memory.usage_in_bytes - total_inactive_file`(memsw 联合用量同样扣除),与 kubelet/cAdvisor 一致;溢写文件、读取过的代码与日志产生的可回收 page cache 不计入占用,否则设置了内存上限的容器运行一段时间后额度会被 cache 压到 0。无法确认容器 swap 额度时不增加 swap 容量;非 Linux 以可用物理内存保守估计。

`CCH_MEMORY_BUDGET_BYTES` 可明确指定受管分配预算,替代自动比例;显式模式保留 10% 当前 RAM 余量并遵守热点物理容量。该值约束正文物化与门控工作集,并非进程 RSS 硬上限;需要硬上限时应使用容器/cgroup 内存限制。DB、Replay、detached drain 和异步写队列继续使用原有独立限额,尚未全部迁入同一个分配器。

Expand Down
1 change: 1 addition & 0 deletions drizzle/0124_system_settings_memory_admission.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "system_settings" ADD COLUMN "enable_memory_admission" boolean DEFAULT false NOT NULL;
Loading
Loading