项目/家庭服务
家庭服务 · 运行中

PhotoPrism 相册 + 资源限制

家庭照片管理,并针对低配 .164 做了严格的 CPU/内存/IO 限制,防止拖垮整机。

PhotoPrismDockercgroups照片

这是什么

PhotoPrism 是我家里的照片管理服务,跑在 .164(Celeron J1900 / 3.8G 内存 / 1.5G zram)上,负责照片索引、浏览和视频转码,通过 photo.xiejun-server.top 访问。相册本身没问题,问题在于它身边还挤着 nginx-proxy-manager(80/443,所有网站的入口)、AdGuard(全屋 DNS)、natfrp、memos、filebrowser、reader——一台小机器承担了太多职责。

为什么要做限制

10-16 和 10-17 两次故障指向同一个模式:跑东西的容器没有内存上限。10-17 那次,PhotoPrism 峰值 2.2GB(RSS 1.64G + 换出 1.74G),叠加每日 SQLite 备份和两个 libx264 转码,把机器推进了机械盘 swap 抖动——load 峰值 79.15、iowait 84%–90%、73 个进程阻塞在不可中断 IO,最后 AdGuard 健康检查连续超时被重启,全屋 DNS 断了 2–3 分钟。体感是「服务器卡了、网站打不开」,实际是本机 IO 饱和。

现在怎么跑

我给 PhotoPrism 加了三层限制:容器硬限制(1200m 内存 / 1600m memswap / 1.5 核 / 512 pids),cgroup v2 精细限制(io.max 读 64MB/s、写 32MB/s,memory.high 1000M),以及一个 20 秒跑一次的看门狗——整机濒死时自动 docker stop photoprism,宁可相册暂时不可用,也不让反向代理和网站陪葬。顺手把换页从机械盘挪到 SSD,关键容器 CPU 权重提到 8 倍。当前状态:运行中,常驻内存约 171MB,PSI io full avg60 约 2.5%,限速实测写 24MB/s。

功能

  • 容器硬限制:1200m 内存、1600m memswap、1.5 核、512 pids,超限只杀自己
  • cgroup v2 精细限制:机械盘读写限 64/32MB/s、IOPS 80/60,memory.high 1000M 提前回收
  • 整机濒死自动保护:PSI io full avg60 > 50% 且负载 > 25 连续两次,自动停掉相册服务
  • 换页分级:zram(100) > SSD swapfile(50) > NAS 4G(10),不再打满机械盘
  • 关键容器 cpu_shares=2048、相册 128,CPU 争抢时关键服务约 8 倍优先
  • 转码降规格:FFMPEG_SIZE 1280、BITRATE 20Mbit/s,压低 CPU 与内存峰值

技术栈

PhotoPrismDocker Composecgroups v2systemd timerPSISQLitenginx-proxy-manager

使用 / 部署教程

1. 写容器硬限制

编辑 /opt/photoprism/compose.yaml(先 cp compose.yaml compose.yaml.bak 备份):

services:
  photoprism:
    mem_limit: 1200m
    memswap_limit: 1600m   # 最多 400M 换页
    cpus: 1.5
    cpu_shares: 128        # 关键容器设 2048
    pids_limit: 512
    environment:
      PHOTOPRISM_FFMPEG_SIZE: "1280"
      PHOTOPRISM_FFMPEG_BITRATE: "20"
    logging:
      driver: json-file
      options: { max-size: "10m", max-file: "3" }

docker compose up -d photoprism 重建,只重启它自己,反向代理和网站无感。

2. 用 cgroup v2 钉 IO 和内存软限

先确认块设备号:lsblk -o NAME,MAJ:MIN(资料里机械盘是 8:0)。写一个 photoprism-guard.sh,由 photoprism-guard.timer 每 20 秒重新施加一次(容器重建后也能自动补上):

echo "8:0 rbps=67108864 wbps=33554432 riops=80 wiops=60" > /sys/fs/cgroup/.../io.max
echo "default 20" > /sys/fs/cgroup/.../io.weight      # 默认 100
echo 1000M > /sys/fs/cgroup/.../memory.high          # 硬限 1200M 兜底

3. 加整机濒死看门狗

同一个 timer 里读 /proc/pressure/io 的 full avg60,大于 50% 且 1 分钟负载大于 25,连续两次(约 40 秒)就 docker stop photoprism。计数写 /run/photoprism-guard.bad,触发时间写 /run/photoprism-guard.stopped,日志用 journalctl -t photoprism-guard 看。恢复手动 docker start photoprism。

4. 让换页优先走 SSD

在 SSD 上建 4G swapfile,/etc/fstab 加一行(先备份 fstab),写成三级:zram(pri=100) > SSD(pri=50) > NAS 4G(pri=10)。systemctl daemon-reload 生效。

5. 提升关键容器优先级

docker update --cpu-shares 2048 npm adguardhome natfrp-service home memos reader filebrowser

规则存在容器配置里,重启后保持。

6. 验证

  • docker inspect photoprism 看 Memory / NanoCpus / CpuShares / PidsLimit
  • cat io.max、cat memory.high 确认 cgroup 生效
  • 容器内写 128MB 测速,应明显低于机械盘原始写速
  • 逐个 curl 各服务端口,确认全绿;再看 load、PSI、可用内存

7. 回滚

cp compose.yaml.bak-严格限制-20261017-2143 compose.yaml && docker compose up -d photoprism;看门狗用 systemctl disable --now photoprism-guard.timer(脚本保留但不生效)。

注意

  • 内存上限 1200m 意味着大相册批量索引或高码率转码时容器可能被 OOM 重启,属预期行为——保整机优先。
  • 看门狗触发后会真的把相册停掉,需要手动 docker start photoprism 恢复,建议配告警而不是靠体感发现。
  • 所有改动都留了 .bak 备份(compose.yaml、fstab、健康检查脚本),改之前先备份,出问题按回滚步骤还原。