PythonでOSError: [Errno 24] Too many open filesを修正する方法

intermediate🐍 Python2026-05-14| Python 2.7+ / Python 3.x(Linux、macOS、Unix — OSレベルのファイルディスクリプタ制限があるサーバーまたはワークステーション)

Error Message

OSError: [Errno 24] Too many open files
#python#oserror#file-descriptor#resource-limit#ulimit

TL;DR — クイックフィックス

プロセスがOSの許可数を超えるファイル(またはソケット)を開いたまま閉じていません。ファイルディスクリプタの上限に達しています。

**本番環境での最速修正:**実行中のユーザーの制限を引き上げる:

ulimit -n 65536

これで一時的に余裕ができます。ただし、次のデプロイ前に根本原因を修正してください — ulimitだけでは永遠に乗り切れません。

このエラーの見た目

Traceback (most recent call last):
  File "worker.py", line 47, in process
    f = open(path, 'r')
OSError: [Errno 24] Too many open files

ソケットでも同様のエラーが発生します:

OSError: [Errno 24] Too many open files
  File "server.py", line 112, in accept
    conn, addr = self.sock.accept()

どちらも根本原因は同じです。ファイルディスクリプタ(FD)はファイル、ソケット、パイプ、一部のIPCメカニズムで共有されています。単純に枯渇してしまったのです。

根本原因

開いているファイルやソケットはそれぞれ1つのファイルディスクリプタを消費します。LinuxとmacOSはプロセスごとにソフトリミットを設けており、Linuxでは通常 1024 です。現在の値を確認するには:

ulimit -n
# または
cat /proc/sys/fs/file-max  # システム全体のハードキャップ

ほぼすべてのケースで2つの原因が考えられます:

  • FDリーク:コードがファイルやソケットを開いたまま閉じていない。上限に達するまで静かに積み重なっていく。
  • 正当な枯渇:クローラー、ログプロセッサー、コネクションプールがデフォルトの許容量を超えるFDを本当に必要としている。

現在プロセスが開いているFDの数を確認するには:

# PIDをプロセスIDに置き換える
ls /proc/PID/fd | wc -l

# またはlsofを使う
lsof -p PID | wc -l

上限に近づくばかりで下がらない?それはリークです。

修正1 — リークを止める(最も一般的な修正)

with文を使いましょう。Pythonのコンテキストマネージャーは、ブロックの途中で例外が発生してもclose()を呼び出します — 手動クリーンアップは不要です。

問題のあるパターン:

f = open('data.csv', 'r')
content = f.read()
# f.close()を忘れている — 上で例外が発生するとFDがリークする

修正後:

with open('data.csv', 'r') as f:
    content = f.read()
# ここでFDが解放される(何があっても)

ソケットも同様です:

import socket

with socket.create_connection(('example.com', 80)) as sock:
    sock.sendall(b'GET / HTTP/1.0\r\n\r\n')
    response = sock.recv(4096)
# ソケットは自動的に閉じられる

修正2 — 長時間実行コードでは明示的に閉じる

withが使えない場合もあります — オブジェクト属性やクラスレベルのハンドルがその例です。代わりにtry/finallyでラップしましょう:

f = open('output.log', 'a')
try:
    f.write(line)
finally:
    f.close()

HTTPセッションも同じ落とし穴があります。コンテキストマネージャーとして使いましょう:

import requests

with requests.Session() as session:
    resp = session.get('https://api.example.com/data')
    print(resp.json())

修正3 — OSの制限を引き上げる

高並列サーバーや大量のファイル処理では、数千のFDが本当に必要になることがあります。デフォルトの1024では低すぎます。

一時的(現在のシェルセッションのみ):

ulimit -n 65536

ユーザーへの永続的な設定/etc/security/limits.confを編集:

# /etc/security/limits.conf
www-data soft nofile 65536
www-data hard nofile 65536

変更を有効にするにはログアウトして再ログインしてください。ulimit -nで確認できます。

systemdサービスの場合[Service]セクションに追加:

[Service]
LimitNOFILE=65536

適用するには:sudo systemctl daemon-reload && sudo systemctl restart yourservice

修正4 — PythonのresourceモジュールをJapanese

スクリプト内から制限を引き上げたい場合 — systemdもシェルアクセスもない?resourceモジュールを起動時に使えます:

import resource

soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
print(f'Current limits: soft={soft}, hard={hard}')

# ソフトリミットをハードリミットまで引き上げる
resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard))

注意点:rootなしでは、ソフトリミットをハードリミットまでしか引き上げられません。それ以上はulimitまたはlimits.confが必要です。

修正5 — コネクションプールサイズを制限する(requests、aiohttp、データベース)

高負荷時、HTTPクライアントやDBドライバーはプールへの返却より速く接続を開くことがあります。プールサイズを明示的に固定しましょう:

import requests
from requests.adapters import HTTPAdapter

session = requests.Session()
adapter = HTTPAdapter(pool_connections=10, pool_maxsize=20)
session.mount('https://', adapter)
session.mount('http://', adapter)

asyncioでは、セマフォで同時オープン数を制御できます:

import asyncio

async def process_files(paths):
    sem = asyncio.Semaphore(100)  # 同時FDを100に制限

    async def open_with_limit(path):
        async with sem:
            # ここでファイルまたはネットワーク操作を行う
            pass

    await asyncio.gather(*[open_with_limit(p) for p in paths])

この100はulimitに基づいて調整してください。安全な目安として、ソフトリミットの50%未満に抑えましょう。

修正の確認

ワークロード実行中にFD数をリアルタイムで監視する:

# PIDのFDカウントをライブ表示
watch -n1 'ls /proc/PID/fd | wc -l'

# プロセスが実際に認識している制限を確認
cat /proc/PID/limits | grep 'open files'

カウントが横ばいになればリークは解消されています。まだ増え続けている場合はlsofで原因を特定しましょう:

lsof -p PID | sort -k9 | head -40

同じファイルパスのエントリが繰り返し出てくる箇所がリークです — そこを重点的に調査してください。

クイック診断チェックリスト

  • ulimit -nを実行 — まだデフォルトの1024ですか?引き上げましょう。
  • ls /proc/PID/fd | wc -lを確認 — 上限にどれだけ近いですか?
  • コード内でwithブロック外にあるopen(呼び出しを検索する。
  • 閉じていないsocket、裸のrequests.get()呼び出し、DBカーソルを探す。
  • スレッドや非同期を使っている?並列オープン数が制限されているか確認 — 無制限のgather()やワーカー数無制限のThreadPoolExecutorは使わない。

Related Error Notes