"Too many open files" の対処 - ファイルディスクリプタ枯渇

"Too many open files" の対処 - ファイルディスクリプタ枯渇

この記事で解決できること

  • Too many open files エラーの原因(プロセス上限 vs システム上限)が分かる
  • ulimitlsof現在の FD 使用状況 をすぐ確認できる
  • limits.conf / sysctl.conf / systemd オーバーライドで 恒久的に上限を上げる 方法が分かる

結論(最短 3 ステップ)

  1. ulimit -n または /proc/<pid>/limitsどの上限に当たっているか を確認
  2. lsof -p <pid> | wc -lプロセスが実際に開いている FD 数 を確認
  3. 原因に応じて /etc/security/limits.conf(PAM 経由プロセス)または LimitNOFILE=(systemd サービス)で恒久設定

前提(対象環境)

  • OS:Ubuntu / Debian / RHEL 系
  • 権限:sudo が使える前提
  • systemd ベースのサービス管理前提

「Too many open files」とはどういうエラーか

ファイルディスクリプタ(FD)は、Linux がファイル・ソケット・パイプなどを管理するための 整数の識別子。プロセスはファイルを開くたびに FD を 1 つ消費する。

Linux には 2 種類の FD 上限がある。

  • プロセス上限(EMFILE): 1 プロセスが同時に開ける FD の数。ulimit -n で確認できる
  • システム上限(ENFILE): OS 全体で開ける FD の総数。/proc/sys/fs/file-max で確認できる

多くの場合、エラーは プロセス上限への到達(デフォルト 1024)が原因。高トラフィックな Web サーバ・データベース・Node.js アプリは 1024 では不足することが多い。

典型症状

  • Nginx / Apache / MySQL / Node.js が負荷時に突然エラーを吐く
  • ログに Too many open files (EMFILE)accept4: Too many open files が出る
  • ulimit -n の値(デフォルト 1024)付近で接続数が詰まる

1. FD 使用状況を確認する

1-1. 現在のシェル・プロセスの上限を確認

$ ulimit -n       # ソフト上限(実効値)
$ ulimit -Hn      # ハード上限(プロセスが自分で引き上げられる上限)
1024
1048576

ソフト上限(1024)が実効値。プロセス自身がハード上限まで引き上げることができる。

1-2. 特定プロセスの上限と使用数を確認

$ cat /proc/<pid>/limits | grep 'open files'
Limit                     Soft Limit           Hard Limit           Units
Max open files            65536                65536                files

実際に開いている FD 数を確認する:

$ ls /proc/<pid>/fd | wc -l
# または
$ sudo lsof -p <pid> | wc -l

1-3. システム全体の FD 使用状況

$ cat /proc/sys/fs/file-nr
13472	0	524288

3 列の意味:現在の使用数 / 解放予約済(常に 0) / システム上限

システム上限値の確認:

$ sysctl fs.file-max
fs.file-max = 524288

プロセス上限(ulimit -n)がシステム上限(fs.file-max)より先に詰まるのが通常。システム上限に達することは稀。

2. 原因プロセスを特定する

2-1. FD 使用数の多いプロセスをランキング

$ sudo lsof 2>/dev/null | awk '{print $1, $2}' | sort | uniq -c | sort -rn | head -20
   4821 nginx   1234
   3102 mysqld  5678
   1204 node    9012

2-2. 特定プロセスが何を開いているか確認

$ sudo lsof -p <pid>
COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   1234  www    0r   REG   8,1   1234      /var/log/nginx/access.log
nginx   1234  www    3u  IPv4  56789       0t0  TCP *:80 (LISTEN)

ソケットが大量にある場合、コネクションリーク(close し忘れ)が原因である可能性が高い。

2-3. ソケット状態の確認

$ ss -s
Total: 12034
TCP:   11820 (estab 8000, closed 200, orphaned 20, timewait 200)

estab(確立済み)が異常に多い場合、アプリ側の接続管理を見直す。

3. 一時的な上限引き上げ(応急処置)

現在のシェルセッションのみに有効な一時設定。ログアウト後は元に戻る。

$ ulimit -n 65536

既に起動しているプロセスの上限を変えたい場合は prlimit を使う:

$ sudo prlimit --pid <pid> --nofile=65536:65536

ulimit の変更はそのシェルセッションのみ有効。恒久設定は次セクションを参照。

4. 恒久的な設定変更

4-1. PAM 経由プロセス(ログインセッション・一般デーモン)

/etc/security/limits.conf または /etc/security/limits.d/*.conf に追記する。

$ sudo vi /etc/security/limits.d/99-fd-limits.conf
# * は全ユーザー対象。特定ユーザーの場合はユーザー名を指定
*    soft    nofile    65536
*    hard    nofile    65536

# 特定ユーザーのみ変える場合
www-data soft nofile 65536
www-data hard nofile 65536

設定を反映するには 再ログイン(またはサービス再起動)が必要。確認:

$ su - www-data -s /bin/bash -c 'ulimit -n'
65536

Ubuntu 18.04 以降は /etc/systemd/system.confDefaultLimitNOFILE= でシステムワイドなデフォルトも変更できる。ただし全サービスに影響するため、サービス単位の設定(4-3)を優先する。

4-2. システム全体の上限(fs.file-max)を上げる

通常はプロセス上限の対処で十分。システム上限に達した場合のみ変更する。

$ sudo sysctl -w fs.file-max=1048576    # 即時反映(再起動で消える)

永続化するには /etc/sysctl.d/99-fd.conf を作成する:

$ sudo vi /etc/sysctl.d/99-fd.conf
fs.file-max = 1048576
$ sudo sysctl --system    # 全 sysctl.d/*.conf を再読み込み

4-3. systemd 管理サービスの場合

systemd は PAM を経由しないため limits.conf は無効。サービス単位でオーバーライドを作る。

$ sudo systemctl edit nginx    # エディタが開く

以下を追記して保存する:

[Service]
LimitNOFILE=65536

または手動でファイルを作成する:

$ sudo mkdir -p /etc/systemd/system/nginx.service.d/
$ sudo vi /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65536
$ sudo systemctl daemon-reload
$ sudo systemctl restart nginx

設定が反映されているか確認する:

$ cat /proc/$(pgrep -o nginx)/limits | grep 'open files'
Max open files            65536                65536                files

LimitNOFILE=infinity はカーネル 5.15 以降で使用可能。それより古い環境では数値で指定する(65536 または 1048576)。

5. やってはいけないこと

次に読む