如何限制内存 200 MB?

解读

在国内中小型私有化部署、边缘节点或 IoT 网关场景里,宿主机往往只有 1~2 GB 物理内存,却需要同时跑 CouchDB、Nginx、业务进程。面试官问“如何压到 200 MB”,并不是让你背诵官方默认参数,而是考察三点:

  1. 是否清楚 BEAM 虚拟机 的内存模型(进程堆、二进制堆、ETS、NIF);
  2. 能否把 OS 级、couchdb.ini、VM.args、systemd 四层 限制一起串成闭环,而不是只改一处;
  3. 是否知道 200 MB 以下会触发哪些副作用(视图构建慢、复制积压、文件句柄暴涨),以及怎么兜底。
    答得太浅(只说一句“改 vm.args”)会被直接打断:“还有吗?”;答得太偏(直接 hack 源码)会被认为不接地气。要把“为什么、怎么做、怎么验、怎么兜底”四步讲全。

知识点

  1. Erlang VM 内存域
    • 进程堆(proc_stack + proc_heap)
    • 二进制引用堆(binary)
    • ETS 表(couch_file、replication 内部表)
    • NIF 驱动(couch_icu、snappy 压缩)
  2. CouchDB 三级配置源
    • etc/vm.args → 控制 BEAM;
    • etc/default.ini / etc/local.ini → 控制 couch 应用;
    • 环境变量 COUCHDB_* → 启动时覆盖。
  3. Linux cgroups v1/v2
    • memory.limit_in_bytes / memory.max
    • OOM killer 打分逻辑(oom_score_adj)。
  4. systemd 资源管控
    • MemoryMax、MemorySwapMax、TasksMax。
  5. CouchDB 内部缓存旋钮
    • couchdb.buffer_size、file_compression、util_driver_buf、max_dbs_open。
  6. 监控命令
    • erlang:memory().、etop、observer、/proc/<beam.smp>/smaps_rollup。

答案

分四步落地,每一步都给面试官可量化的数字,方便他追问“你怎么证明生效”。

  1. OS 级硬封顶(防呆)
    在 /etc/systemd/system/couchdb.service.d/memory.conf 写入:

    [Service]
    MemoryMax=200M
    MemorySwapMax=0
    TasksMax=512
    

    执行 systemctl daemon-reload && systemctl restart couchdb
    验证:systemctl status couchdb 看到 “Memory: 198.4M” 接近上限即成功;同时 /sys/fs/cgroup/.../memory.max 应为 209715200。

  2. BEAM 虚拟机级软限制(精准)
    编辑 /opt/couchdb/etc/vm.args:

    +P 50000              %% 最多 5 万进程,省内存
    +Q 20000              %% 最多 2 万端口
    +MBas ageffcbf        %% 二进制分配器按 age 回收
    +MBlmbcs 512          %% 二进制堆最大 512 KB
    +MMmcs 30             %% 主分配器 30 MB
    +Musac false          %% 关闭 ultrasafe,省 5-8 MB
    

    重启后,在 curl http://127.0.0.1:5986/_system 返回的 memory.total 稳定在 180 MB 以下

  3. CouchDB 应用级缓存裁剪
    在 local.ini 追加:

    [couchdb]
    buffer_size = 1024              %% 1 MB 文件读写缓冲
    max_dbs_open = 50               %% 同时打开 50 库
    file_compression = snappy       %% 比 deflate 省 30% 磁盘,解压内存更小
    [query_server_config]
    reduce_limit = 100              %% 降低视图 reduce 内存爆炸风险
    

    重启后,通过 etop -sort memory 观察 couch_file 进程堆从 8 MB 降到 <1 MB

  4. 运行时监控与兜底
    写一行 crontab 每 5 分钟执行:

    erl -noshell -eval 'io:format("~p~n",[erlang:memory(total)]),halt().' | awk '{if($1>190000000) system("systemctl reload couchdb")}'
    

    当内存触碰 190 MB 主动 reload,触发 GC 防止 OOM。

    至此,200 MB 硬顶 + 180 MB 软限 + 自动兜底 闭环完成,面试官若问“视图太慢怎么办”,可答“把设计文档拆成 50 MB 以下库 + 预构建视图 + 错峰复制”,体现你对业务代价有预判。

拓展思考

  1. 再压 50 MB 是否可行?
    +P 降到 10000、关闭 couch_index_servercommit_count 实时刷盘、用 erlang:system_flag(backtrace_depth,5) 减少异常栈,可把峰值压到 150 MB,但视图构建时间翻倍,需提前在测试环境跑 npm run test:performance 确认 SLA。

  2. 容器场景差异
    国内很多省公司用旧版 Docker 1.13,只认 --memory 200m --memory-swap 0,但 /sys/fs/cgroup/memory/memory.statcache 字段会算在 200 M 内,导致 BEAM 实际堆只有 120 M;解决方法是挂载 tmpfs=/var/lib/couchdb 并把 file_compression 关断,让磁盘落临时文件而非缓存。

  3. 升级 3.x 后的变化
    CouchDB 3.2 引入 smoosh 压缩线程池,默认 max_priority=5 会吃 20 MB+;压内存需加 smoosh.intrusive=false 并把 concurrency=1,否则 200 M 预算直接穿顶。

  4. 面试加分话术
    “200 M 不是目标,是预算;我会先拿 observer 跑一周业务峰值,画出 内存拓扑图,再决定砍哪一块。如果客户后续要加节点,我随时把 systemd 里 MemoryMax 改成 400 M,零代码改动滚动重启,保证平滑扩容。”——一句话体现容量管理思维,面试官通常到此就喊“过”。