テクノロジー · 実用ガイド

Linuxでファイル監視上限のENOSPC?sysctlを上げる前にinotify消費台帳を作る

同じENOSPCでも、監視マーク、inotifyインスタンス、キュー、同一ユーザーの別プロセスが原因になり得ます。まず読み取り専用で棚卸しします。

複数プロセス、読み取り専用台帳、共有inotifyプールを示すオリジナル概念図。
Watch at Easeオリジナル概念図:インスタンス、マーク、可視性を記録してから次へ進みます。 Watch at Ease

Linuxの開発ツールが `ENOSPC: System limit for number of file watchers reached` と表示する場合、多くはディスク満杯ではなくinotify資源の枯渇です。エラーのパスは次に登録できなかった場所であり、最大消費プロセスを示すとは限りません。

制限は実ユーザーID単位なので、エディター、言語サーバー、開発サーバー、テスト、同期ツール、コンテナが同じ枠を共有します。`max_user_watches`が高くても`max_user_instances`は枯渇し、キューあふれは別の故障です。

sysctl変更、再起動、一括終了の前に、`/proc`から読み取り専用の消費台帳を作ります。上限、インスタンス、マーク、プロセス、権限欠落、名前空間を比較し、一つの可逆な変更だけを再測定します。

inotify系のENOSPCかを確認する

正確なエラー、コマンド、OS、パスを保存します。`fs.watch`、Chokidar、Watchpack、エディターなどLinuxの監視登録で失敗したならinotify枯渇が有力です。書き込み失敗なら`df -h`と`df -i`も確認します。

`id -ru`で実ユーザーIDを記録し、`max_user_watches`、`max_user_instances`、`max_queued_events`を読みます。前二つは登録失敗、キューあふれは`IN_Q_OVERFLOW`とイベント欠落を起こします。

インスタンスと監視マークを別々に数える

inotifyインスタンスは`anon_inode:inotify`記述子ですが、一つに多くのマークが入ります。記述子数はインスタンス数を示しても、`max_user_watches`への距離は示しません。

読み取れる各inotify `fdinfo`で`inotify`から始まる行を数え、PIDと実UIDごとに合計します。記述子数は独立した`instances`列に残します。

権限欠落を表せる消費台帳を作る

各PIDについて実UID、プロセス名、インスタンス数、マーク数を記録します。`/proc/<pid>/cmdline`の文字列を実行してはいけません。走査中に終了したプロセスは競合として扱い、ゼロにしません。

拒否または読めない`fdinfo`も数えます。自分のプロセスだけでもユーザー単位の診断はできますが、全システムに他の消費者がいない証明にはなりません。hidepid、コンテナ、名前空間境界では対象プロセス側の名前空間で再実行します。

テスト済みの読み取り専用台帳を実行する

次のスクリプトを`inotify-ledger.sh`として保存し、内容を確認してから`bash inotify-ledger.sh`を実行します。procfsとsysctlファイルを読むだけで、sysctl書き込み、プロセス終了、サービス再起動、削除、私的パス解決は行いません。

```bash #!/usr/bin/env bash set -u printf 'setting\tvalue\n' for name in max_user_watches max_user_instances max_queued_events; do path="/proc/sys/fs/inotify/${name}" if [[ -r "$path" ]]; then IFS= read -r value < "$path" printf '%s\t%s\n' "$name" "$value" else printf '%s\tUNREADABLE\n' "$name" fi done printf '\npid\truid\tcomm\tinstances\twatches\tunreadable_fdinfo\n' for proc in /proc/[0-9]*; do pid=${proc##*/} [[ -r "$proc/status" ]] || continue ruid=$(awk '/^Uid:/{print $2; exit}' "$proc/status" 2>/dev/null || true) [[ -n "$ruid" ]] || continue comm=$(tr '\t\n' ' ' < "$proc/comm" 2>/dev/null || true) instances=0 watches=0 unreadable=0 shopt -s nullglob for fdinfo in "$proc"/fdinfo/*; do fd=${fdinfo##*/} target=$(readlink "$proc/fd/$fd" 2>/dev/null || true) [[ "$target" == "anon_inode:inotify" ]] || continue ((instances+=1)) if [[ -r "$fdinfo" ]]; then count=$(awk '/^inotify /{n++} END{print n+0}' "$fdinfo" 2>/dev/null || printf '0') ((watches+=count)) else ((unreadable+=1)) fi done if ((instances > 0 || watches > 0 || unreadable > 0)); then printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$pid" "$ruid" "$comm" "$instances" "$watches" "$unreadable" fi done | sort -t $'\t' -k5,5nr -k4,4nr printf '\nnamespace\ttarget\n' for ns in pid user mnt; do printf 'self_%s\t%s\n' "$ns" "$(readlink "/proc/self/ns/$ns" 2>/dev/null || printf 'UNREADABLE')" printf 'init_%s\t%s\n' "$ns" "$(readlink "/proc/1/ns/$ns" 2>/dev/null || printf 'UNREADABLE')" done ```

隔離テストでは一つのNode.jsプロセスが三つの一時ディレクトリを監視し、台帳はインスタンス1、マーク3を正しく報告しました。PID 1の名前空間が読めない環境では`UNREADABLE`を残し、見えない利用をゼロにしません。

失敗パスと最大消費者を混同しない

例外のパスは上限に達した登録先です。最大消費者は別ワークスペース、拡張ホスト、言語サーバー、同期ツール、長寿命プロセスかもしれません。公開issueでは拡張が数万マークを使い、別のserveが先に失敗しました。

台帳をマーク数で並べ、確認済みで不要な消費者を一つだけ通常終了して再実行します。大きな低下が因果の証拠です。再起動は証拠を消し、同じ広い監視が戻るかを示しません。

上限変更前に監視範囲を監査する

最大消費者の監視ルートと除外設定を確認します。`.venv`、生成物、キャッシュ、依存ディレクトリは監視価値が低いことがあります。VS Codeは`files.watcherExclude`で大きな不要フォルダーを先に除外するよう案内しています。

一般的な`node_modules`除外が全監視器に効くとは限りません。エディター、拡張、言語サーバー、テスト、フレームワークは別々に購読できます。マーク所有コンポーネントを変更し、台帳の減少と必要イベントの維持を確認します。

台帳が必要性を示した上限だけ上げる

正当な範囲が`max_user_watches`を超えるなら、メモリ余裕を考えた値をディストリビューションのsysctl方法で適用します。インスタンス枯渇ならマーク上限だけ変えても無効です。旧値、設定ファイル、アプリ版を保存します。

万能の数字を全ホストに貼らないでください。VS Codeは524,288を一例として最悪時のメモリ費用も示しますが、コンテナ、小VM、複数ユーザーでは予算が違います。適用後の値、台帳、メモリを再確認します。

ランダムな修正ではなく分岐表を使う

一PIDが支配的ならその消費者の範囲や版を見直します。多くの妥当な消費者が合計で上限を超えるなら`max_user_watches`を慎重に上げます。インスタンスが上限に近ければオブジェクト過多や漏れを調べ、不完全台帳なら可視性を先に解決します。

両方が十分低いなら最小ワークスペースで再現し、アプリ自身の監視ログを確認します。報告には前後の台帳、正確な上限、カーネルとアプリの版、最小再現を付け、秘密や私的パス一覧は付けません。

一大消費者、複数消費者、可視性欠落へ分岐するオリジナル概念図。
Watch at Easeオリジナル概念図:証拠の分岐は所有者と境界を示し、自動変更を許可しません。 Watch at Ease