2026:在 launchd 管理的租用 Mac mini 上,为 OpenClaw 设置 ulimit、内存与 CPU 护栏
位于香港、日本、韩国、新加坡或美国的 ProxyMac Mac mini M4 上,OpenClaw 常在常驻内存(RSS)越过 launchd 隐式边界时“悄悄死掉”:进程状态仍显示运行,但频道适配器不再接活,因为内核驱逐页面的速度快于你的探测周期。运维往往把锅甩给“模型不行”,真正原因却可能是无人值守作业里默认 ulimit -n 只有 256,或是 MCP 工具服务 描述的那类子进程风暴。本篇 2026 指南梳理静默 OOM 的指纹,对照交互式 shell 与 LaunchAgent 的两套 ulimit 世界,给出三条可量化的遥测阈值(70% 内存余量、1 万级打开文件、每小时 2GB 级别的换入强度作参照),提供可复制的 SoftResourceLimits plist 片段,并把上限与 并行代理并发、健康探测 串起来,让你在用户察觉之前就收到告警。
建议与 部署排障 搭配阅读以完成日志分流;需要按运维手册执行步骤时走 帮助中心。若多次触顶说明是架构瓶颈而非笔误,再用 定价 升级内存档位。
把资源问题讲清楚,本质是帮团队建立共同语言:到底是 FD、RSS、CPU 还是磁盘 I/O 成为第一限制因子。下面各节按“信号—根因—改法”顺序展开,避免你在凌晨三点同时调大三类参数却不知道为什么生效。
在指责模型之前,先记录这些静默 OOM 与限制信号
macOS 统一日志里,jetsam 相关事件常被信息级噪声淹没。若观察到工具延迟 p95 突然放大到 4 倍以上,而 CPU 仍低于 40%,这更像是内存压力而非 LLM 变慢。另一个线索是 MCP stdio 桥在并发尖峰时出现 ENOTFILE 或 Too many open files。
- 压缩器换入:在 16GB mini 上若持续高于 500 MB/小时,说明舒适余量已经不足。
- LaunchAgent 的 throttleReason 若出现
resource-limits字样,请把对应 plist 版本纳入 git。 - 本地网关偶发 502:worker 子进程无法 fork 时常见,多与
RLIMIT_NPROC相关。
把这些信号写成可查询的标签(机器 region、git commit、模型名、并发度)后,回归分析会快很多;否则你只能凭印象说“昨晚好像卡过”。
launchd 与交互式 shell:两套 ulimit 世界
SSH 会话可能在 ~/.zprofile 里执行 ulimit -n 65535;LaunchAgent 不会自动继承。务必在 plist 实际调用的同一包装脚本里打印限制,而不是在你笔记本的 shell 里打印。
| 上下文 | 典型 maxfiles | 谁设置 | 风险 |
|---|---|---|---|
| SSH 登录 | 10240+ | shell rc | 容易在排查 launchd 时产生误导 |
| LaunchAgent 默认 | 256–1024 | 系统默认 | MCP 突发很快打满 |
| 写入 plist SoftResourceLimits 后 | 8192–65535 | 你的运维团队 | 需与代码侧 FD 卫生策略一致 |
若你在文档里写“我们已把 ulimit 调到 65535”,请明确指的是哪条执行路径:仅 SSH 生效的说明对无人值守作业没有证据效力。
另一个常见坑是登录项与 LaunchAgent 混用:前者可能继承图形会话的更高配额,后者却在后台以更小默认值启动。把它们画在同一张架构图里,标出“谁拉起 openclaw、父进程是谁、环境变量从哪来”,能避免排障时三个人给出三套互相矛盾的 ulimit -a 截图。
值得把人从床上叫起来的遥测阈值
在现有 健康探测 脚本中,每分钟追加三类数字到 syslog:空闲页、文件描述符计数、压缩器页。当任意连续两个样本同时越过以下边界,就应该升级告警:
阈值不是魔法数字,而是把“还能扛”与“已经在悬崖边”分开的刻度。第一次触发建议先自动采集 10 分钟的 sample 或 footprint 快照,再决定是降并发还是升配。把采样窗口写进值班手册,能避免“只看一眼活动监视器就结案”的草率结论。
Plist 片段:SoftResourceLimits,且别把 launchd fork 炸掉
键必须写在代理字典之下,而不是全局域。保持 hard ≤ soft × 1.25,让错误配置快速失败而不是把系统卡住。
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key><integer>8192</integer>
<key>NumberOfProcesses</key><integer>512</integer>
<key>Stack</key><integer>8388608</integer>
</dict>
仅在保留第二条 SSH 会话的前提下使用 launchctl kickstart -k gui/$(id -u)/com.example.openclaw 重载;纪律可参考 网关重启与恢复。
变更 plist 时,建议同时更新变更记录:谁批准、预期 RSS 上限、回滚命令。这样下一次“临时加 limit”不会变成永久技术债。
并发:在盲目提高限制前先乘算 RSS
并发指南 里的每个并行代理都会复制模型句柄与 MCP 套接字。若实验里把并发翻倍带来 3.2 GB RSS 增量,就不要顺手把 NumberOfFiles 也翻倍“以防万一”——那是在掩盖泄漏。更稳妥的是先把并发压在物理内存的 70%(扣除 macOS 基线服务后)以内。
RLIMIT_MEMLOCK 很少能拯救 OpenClaw;请按 密钥加固 使用钥匙串托管秘密,而不是 mmap 巨型缓存。
当你确实需要更高并发时,优先评估模型分片、批大小 与 工具调用去抖;把 plist 当作最后手段,而不是第一旋钮。
若你把 RSS 与 FD 曲线对齐观察,往往会看到阶段性相关:FD 尖峰稍后出现,意味着先是连接泄漏,随后才触发内存里的缓冲堆积。此时应先修代码路径,而不是把 NumberOfFiles 一次性拉到极限值,否则下一次事故会在更大的规模上爆炸。
常见问题
若需 8G/16G 档位内存预算、活动监视器与端侧大模型调度,请参阅Mac mini 内存压缩与 Swap 优化指南。
是否应以 root 跑 OpenClaw 以获得更高限制? 不建议——应修正 plist;root 只会扩大爆炸半径。
活动监视器够吗? 现场排查可以,但夜间回归要靠自动化的 memory_pressure 日志。
Docker 边车怎么办? 若同机混跑容器,请先把它们的 RSS 从 70% 预算里扣掉。
为何 Apple Silicon Mac mini 仍适合资源密集的 OpenClaw
M4 的统一内存让模型权重与系统服务共享带宽,避免廉价 x86 虚拟机上常见的 PCIe/NUMA 惊喜。按区域租用时,通过 定价 对齐财务预期;当你需要 24 GB SKU 才能撑住更高并发时,升级理由也更好写。原生 launchd 集成优于“Linux 容器假装友好”的那套 systemd 体验。请把 plist 版本与 Git 提交并列记录(参见 GitOps 配置版本化),把本文放进新人入职清单,并在工作负载结束后回收 mini——当金属可预期时,限额也会更好调。
最后一句话留给值日生:任何“提高 limit 就能解决”的结论,都应该附带前后 RSS 曲线截图,否则下周你还会再提一次同样的 MR。