Bot プログラミングスキル 環境構築・インフラ 開発ログ

🛠️開発記録#258(2025/7/2)Mac mini (M4 Pro) が突然カクつく!――fseventsd メモリリークと Docker Desktop 最適化で復活させるまでの全手順

macOS 15.5 / M4 Pro 24 GB RAM 環境で「スリープ解除直後にマウスも反応しない」ほど重くなる――。
原因は fseventsd のメモリリーク+Docker Desktop の常駐メモリ肥大 でした。
本記事では、再現方法・原因切り分け・恒久対策スクリプト・Docker 設定最適化までをやり切ったログを共有します。


1. イントロ――再発した“カクつき”症状と今回のゴール

  • 現象
    • 起床 → スクリーンセーバ解除直後、数十秒~数分間はレインボーカーソル。
    • アクティビティモニタを見ると Swap 10 GB↑、CPU idle は 0〜10 %。
  • 目的
    1. 原因を特定し、再発防止(自動回復)を仕込む
    2. 開発+仮想通貨 bot 常時運用 を 24 GB RAM 内で安定させる

2. まずは現状把握:vm_stat / top / log show で取れた数字

# メモリと Swap のスナップショット
vm_stat; sysctl vm.swapusage

# メモリ消費上位プロセス確認
top -l 1 -o mem -stats pid,command,mem

# JetSam / GPU Process Kill などのシステムログ
log show --last 1h --style syslog \
  --predicate 'eventMessage CONTAINS "Jetsam" || eventMessage CONTAINS "GPU Process was killed"'
時点fseventsd RSSSwap 使用量VirtualizationService備考
発症直後8 GB10 GB / 11 GB4 GBBrave GPU Process Kill 多数
リセット後20 MB0.4 GB0 GBカクつき解消

3. 原因その①:fseventsd のメモリリーク を突き止める

3-1. 症状と再現条件

検証手順結果
bind-mount を大量に張ったコンテナを起動→Mac をスリープ復帰直後に fseventsd RSS が秒単位で増加
bind-mount なしで同一手順ほぼ増えない
外部モニタを外した状態変わらず再現

推測メカニズム

  • Docker Desktop の gRPC FUSE 無効 + bind-mount が大量にある
  • スリープ復帰 → 変更監視イベントが爆発 → fseventsd が処理し切れずリーク

3-2. 対策:watchdog スクリプト+cron で自動回収

/usr/local/sbin/fsevents_watchdog.sh

#!/bin/zsh
# fseventsd が 1 GB を超えたら強制終了し、launchd の自動再起動に任せる
THRESHOLD_KB=1048576

PID=$(pgrep fseventsd) || exit 0
RSS_KB=$(ps -o rss= -p "$PID" | tr -d ' ')
if [[ "$RSS_KB" -ge "$THRESHOLD_KB" ]]; then
  logger -t fsevents_watchdog "RSS ${RSS_KB}KB exceeds ${THRESHOLD_KB}KB → pkill"
  /usr/bin/pkill -9 fseventsd
fi
# 設置と権限付与
sudo install -m 755 fsevents_watchdog.sh /usr/local/sbin/

# root の cron に 15 分間隔で登録
sudo crontab -e
*/15 * * * * /usr/local/sbin/fsevents_watchdog.sh

ログ確認:

log show --last 15m --predicate 'eventMessage CONTAINS "fsevents_watchdog"'

4. 原因その②:Docker Desktop が食う常駐メモリ

4-1. Virtualization.framework を切る

BeforeAfter
com.apple.VirtualizationService 4 GB 常駐0 GB

Preferences ▸ General ▸ **Use virtualization framework**OFF にするだけで OK。
M4/M3 世代 Mac では Apple Hypervisor に戻す方が軽いケース多し。

4-2. 「場外 4 GB 枠」に絞る GUI 設定

メニュー理由
General ▸ Start Docker…OFFMac 起動直後は Docker が立ち上がらない
Resources ▸ CPUs 4 / Memory 4 GBbot 群+IDE で余裕/Swap が増えたら 6 GB へ
Resources ▸ Swap 1 GBbot 暴走時の逃げ道
Advanced ▸ gRPC FUSE ONbind-mount の FSEvents 激減
File Sharing必要最小限fseventsd の観測対象を絞る

4-3. Compose 側でコンテナ上限を設定

services:
  bot:
    image: mybot:latest
    mem_limit: 768m
    memswap_limit: 1g
  db:
    image: postgres:16
    mem_limit: 1.5g

5. 追加チューニング――Electron/Slack/Brave の負荷軽減

  1. Hardware-accelerated canvas を OFF
  2. Brave / Slack を Universal 版 → Apple Silicon 版へ統一
  3. Mission Control「モニタごとに個別スペース」設定の見直し(外部モニタ多用時のみ)

6. 試運転フェーズ:1 週間モニタリング 指標

項目目安値コマンド
Docker VM 使用メモリ≤ 80 %docker stats --no-stream
macOS Swap≤ 0.5 GBvm_stat; sysctl vm.swapusage
fseventsd RSS≤ 20 MBtop -pid $(pgrep fseventsd)
CPU 温度Idle 40–50 ℃sudo powermetrics --samplers smc -n 1

7. 結果と学び(観測中)

  • watchdog + cron 導入後、fseventsd が 100 MB を超えていない
  • Docker VM 4 GB でも bot 群・VS Code(Cursor)・Browser を同時稼働して Swap 0.3 GB 前後
  • 今後は GUI コンテナ(Grafana など)追加時のメモリ再見積もりが課題

8. まとめ

リーク元プロセスを特定して自動回収
ホストと VM のメモリ上限を揃える

たった 2 つの方針で「開発 × 常時運用」の両立が可能になりました。
同じ症状で悩む macOS + Docker Desktop ユーザの参考になれば幸いです!


付録:よくある Q&A

疑問回答
watchdog が動いているか確認したいlog show --last 1h --predicate 'eventMessage CONTAINS "fsevents_watchdog"'
cron が正しく動作しないroot の crontab かどうか / 改行コード / ファイルの改行終端を確認
Docker 起動時だけ VirtualizationService が現れる正常。常駐していなければ設定は反映済

これで明日からも快適に開発&bot運用ができる,,,はず!

-Bot, プログラミングスキル, 環境構築・インフラ, 開発ログ