macOSでバックグラウンド常駐推論を安定運用する小ワザ集
Linux サーバの感覚で macOS 上に推論サービスを常駐させようとすると、細かいところで足を掬われます。「Linux では動いたワンライナーが Mac では動かない」「ログを grep したら文字化けエラーで落ちる」——地味だけど時間を溶かすやつです。
この記事は、Seed-VC ベースの音声変換サービス(FastAPI + uvicorn、ローカルの 127.0.0.1:8770)を macOS 上でバックグラウンド運用するなかで溜まった、短い TIPS 集です。Linux 前提の記事には載っていない Mac 特有のハマりどころを中心にまとめます。
TIP 1: setsid / timeout は macOS に無い
まず前提として、macOS(BSD 系)には GNU coreutils の setsid や timeout が標準では入っていません。Linux で常駐化やタイムアウト付き実行に使うこれらを、そのまま Mac のスクリプトに書くと command not found で転びます。
対処は2択です。
- Homebrew で
coreutilsを入れてgsetsid/gtimeoutを使う - 標準ツールだけで代替する(次の TIP)
外部依存を増やしたくない常駐スクリプトでは、標準ツールでの代替が無難でした。
TIP 2: 常駐は nohup + disown で
setsid が無い環境で「シェルを閉じてもプロセスを生かす」には、nohup と disown の組み合わせが確実です。
# 推論サービスをバックグラウンド常駐させる
nohup bash scripts/start-backend.sh > backend.log 2>&1 &
disown
nohup… ハングアップシグナル(SIGHUP)を無視させ、端末が切れても死なないようにする> backend.log 2>&1… 標準出力と標準エラーをログファイルへ&… バックグラウンド実行disown… このジョブをシェルのジョブテーブルから外す。これでシェルを閉じても道連れにならない
nohup だけでもだいたい生き残りますが、disown まで打っておくと「ターミナルを閉じたら止まった」という事故を確実に防げます。
TIP 3: バイナリ混じりのログで tr / grep が死ぬ → LC_ALL=C
これが Mac でいちばんハマりました。推論のログには、進捗バーの制御文字や、まれにマルチバイトの化けたバイト列が混じることがあります。それを macOS の tr や grep に食わせると、
tr: Illegal byte sequence
で処理ごと落ちます。ロケールが UTF-8 前提だと、不正なバイト列を「不正な文字」と見なして例外にするためです。
対処は、そのコマンドだけロケールを C(バイト列として素通し) に落とすこと。
# ログから不要な制御文字を除去する(Illegal byte sequence 回避)
LC_ALL=C tr -d '\r' < backend.log > backend.clean.log
LC_ALL=C grep "ERROR" backend.log
LC_ALL=C にすると、テキストを「文字」ではなく「バイト」として扱うので、不正シーケンスで死ななくなります。ログを機械的に加工・検索するパイプラインでは、これを付けておくのが安全です。
TIP 4: 起動完了は health エンドポイントで待つ
モデルの読み込みには時間がかかるので、nohup で起動した直後にリクエストを投げると「まだ準備中」で失敗します。「起動したはず」を sleep 10 で決め打ちするのは応急処置で、遅いマシンでは早すぎ、速いマシンでは待ちすぎます。
正攻法は、サービスの health エンドポイントを叩いて 200 が返るまで待つことです。この構成では起動時に http://127.0.0.1:8770/health を案内しているので、これをポーリングします。
# health が通るまで待ってから次に進む
until curl -sf http://127.0.0.1:8770/health >/dev/null; do
sleep 1
done
echo "backend ready"
curl -sf は失敗時に非ゼロ終了するので、until と組み合わせれば「準備できるまで待つ」がそのまま書けます。固定待機ではなく状態で待つ——地味ですが、起動スクリプトの信頼性が段違いになります。
TIP 5: 停止は pkill でパターン指定
PID をファイルに残していない常駐プロセスは、pkill でコマンドラインのパターンから止めるのが手軽です。
# uvicorn で立てた推論サービスを止める
pkill -f "uvicorn server:app"
-f はコマンドライン全体にマッチするので、ポートやアプリ名まで含めた具体的なパターンにすると、無関係なプロセスの巻き添えを避けられます。より丁寧にやるなら、起動時に PID を控えておいてそれを狙い撃つ運用にします。
まとめ
- macOS には
setsid/timeoutが無い。Homebrew のcoreutils(gsetsid/gtimeout)を入れるか、標準ツールで代替する - 常駐は
nohup ... & disown。SIGHUP を無視させ、ジョブテーブルから外して端末切断の道連れを防ぐ - ログにバイナリが混じると
tr/grepがIllegal byte sequenceで死ぬ → その場だけLC_ALL=Cでバイト列として素通しする - 起動完了は
sleepの決め打ちでなく health エンドポイントをuntil curl -sfでポーリングして状態で待つ - 停止は
pkill -f "具体的なパターン"。巻き添えを避けるため、マッチ文字列は十分に限定する