Published on

리눅스 Too many open files 즉시 진단·해결

Authors

서버에서 갑자기 Too many open files가 터지면 증상은 다양한 형태로 나타납니다. 웹 서버는 새 연결을 못 받고, DB 커넥션이 맺어지지 않거나, 로그 파일이 열리지 않고, 심하면 애플리케이션이 연쇄적으로 실패합니다. 하지만 본질은 단순합니다. 프로세스가 열 수 있는 파일 디스크립터(File Descriptor, FD) 한도에 도달했거나(프로세스 한도), 시스템 전체 FD 풀이 고갈된 것입니다.

이 글은 “지금 장애가 났다”는 상황을 가정하고, 즉시 확인할 명령어부터 원인 분류(누수 vs 트래픽 폭증 vs 설정 미스), 그리고 영구 해결까지 한 번에 이어지는 런북 형태로 정리합니다.

1) 에러 메시지의 의미: 파일이 아니라 FD가 부족하다

리눅스에서 소켓도 파일처럼 취급됩니다. 즉 다음이 모두 FD를 소비합니다.

  • TCP 연결 소켓
  • 파일 핸들(로그, 설정, 캐시 파일)
  • 파이프, 이벤트 디스크립터(eventfd), inotify 워치
  • 라이브러리/런타임이 내부적으로 여는 FD

따라서 “파일을 많이 여는 프로그램”만 문제가 되는 게 아니라, 커넥션이 많은 서버도 동일하게 터집니다.

2) 5분 내 응급 진단 체크리스트

아래 순서대로 보면 대부분의 케이스는 빠르게 좁혀집니다.

2.1 현재 셸(혹은 프로세스)의 FD 한도 확인

현재 셸 기준으로는 다음이 가장 빠릅니다.

ulimit -n

실제 서비스 프로세스 기준으로는 /proc을 봐야 합니다.

pid=1234
cat /proc/$pid/limits | grep -i "open files"

여기서 Max open files가 낮으면, 애플리케이션이 FD를 더 열 수 없어 즉시 실패합니다.

2.2 시스템 전체 FD 사용량 확인

프로세스 한도가 충분해도, 커널 레벨에서 시스템 전체 FD 풀이 꽉 차면 동일한 에러가 납니다.

cat /proc/sys/fs/file-nr

출력은 보통 allocated unused max 형태입니다(커널 버전에 따라 의미가 약간 다를 수 있음). 관찰 포인트는 현재 할당량이 최대치에 근접했는지입니다.

최대치 확인:

cat /proc/sys/fs/file-max

2.3 어떤 프로세스가 FD를 많이 쓰는지 TOP 찾기

가장 실전적이고 빠른 방법은 /proc을 훑는 것입니다.

for p in /proc/[0-9]*; do
  pid=${p#/proc/}
  comm=$(cat /proc/$pid/comm 2>/dev/null)
  fdcount=$(ls -1 /proc/$pid/fd 2>/dev/null | wc -l)
  echo "$fdcount $pid $comm"
done | sort -nr | head -20

여기서 상위에 뜨는 PID가 “범인 후보”입니다.

2.4 범인 프로세스의 FD 종류 파악

FD가 무엇으로 채워졌는지 보면 원인이 거의 드러납니다.

pid=1234
ls -l /proc/$pid/fd | head
  • socket:[...]가 대부분이면 커넥션 폭증, 커넥션 누수, keep-alive 설정 문제 가능성
  • 특정 로그 파일이 수천 개면 로테이션/재오픈 문제 가능성
  • anon_inodeeventpoll이 많으면 런타임/라이브러리 레벨 리소스 누수 가능성

lsof가 있다면 더 편합니다.

lsof -p 1234 | head
lsof -p 1234 | awk '{print $5}' | sort | uniq -c | sort -nr | head

2.5 네트워크 소켓 폭증인지 확인

웹/게이트웨이/프록시 계층이면 소켓이 압도적으로 많습니다.

ss -s
ss -tan state established | wc -l
ss -tan state time-wait | wc -l
  • ESTAB가 폭증이면 실제 동시 연결이 많거나 연결이 닫히지 않는 상황
  • TIME-WAIT가 폭증이면 짧은 연결이 과도하거나(클라이언트/서버), 커넥션 재사용이 안 되는 상황

3) 원인별로 빠르게 분류하는 방법

3.1 “한도가 너무 낮다” 케이스

증상:

  • 특정 프로세스 Max open files가 1024, 4096 같은 낮은 값
  • 트래픽 증가 없이도 특정 시점부터 바로 실패

해결 방향:

  • 프로세스 한도 상향(일시/영구)
  • systemd 단위 파일에서 제한 해제

3.2 “FD 누수” 케이스

증상:

  • 프로세스 FD 수가 시간에 따라 단조 증가
  • 트래픽이 줄어도 FD가 내려오지 않음

확인:

pid=1234
watch -n 2 "ls /proc/$pid/fd | wc -l"

누수는 애플리케이션 버그일 수 있고, 라이브러리나 커넥션 풀 설정 문제일 수도 있습니다.

3.3 “트래픽/커넥션 폭증” 케이스

증상:

  • 특정 시간대에 FD가 급격히 튐
  • socket 비중이 압도적
  • ss -s에서 established 혹은 time-wait 폭증

해결 방향:

  • 커넥션 풀/keep-alive/백프레셔/레이트 리밋
  • 업스트림 장애로 재시도 폭증(폭풍 재시도)

재시도 폭증은 FD뿐 아니라 CPU, 메모리, 네트워크를 동시에 망가뜨리는 전형적인 장애 패턴입니다. 백오프 설계 관점은 OpenAI 429·insufficient_quota 재시도와 백오프 설계도 함께 참고하면 좋습니다.

4) 즉시 복구(임시 조치) 방법

임시 조치는 “서비스를 살리는 것”이 목적이고, 근본 원인 해결은 다음 섹션에서 합니다.

4.1 프로세스 한도 즉시 상향(현재 셸에서 실행한 프로세스에만 적용)

운영 중인 프로세스의 한도를 바꾸는 것은 제한적입니다. 신규로 띄우는 프로세스에 적용하려면:

ulimit -n 65535
# 그 다음 서비스를 재기동하거나 새로 실행

이미 떠 있는 프로세스를 즉시 살려야 한다면 대개 재시작이 필요합니다(프로세스 생성 시 한도가 정해지는 경우가 많음).

4.2 시스템 전체 file-max 상향(즉시)

시스템 전체 FD 풀이 부족한 경우:

sysctl -w fs.file-max=2097152

영구 반영은 아래에서 다룹니다.

4.3 정말 급하면: 문제 프로세스 재시작

FD 누수든 폭증이든, 당장 에러를 멈추려면 재시작이 가장 빠른 경우가 많습니다. 다만 재시작 루프에 빠지면 장애가 더 커집니다. 재시작이 반복될 때는 systemd 서비스가 반복 재시작될 때 원인 추적법을 함께 보면서 재시작 정책과 로그를 먼저 정리하세요.

5) 영구 해결: 한도 설정을 올바르게 고정하기

5.1 PAM limits로 사용자별 nofile 설정(로그인 세션 기반)

/etc/security/limits.conf 또는 /etc/security/limits.d/*.conf에 설정합니다.

예시:

# /etc/security/limits.d/99-nofile.conf
* soft nofile 65535
* hard nofile 65535

주의:

  • systemd로 띄운 서비스는 이 설정이 그대로 적용되지 않을 수 있습니다.
  • 컨테이너 환경에서는 호스트/런타임 제한도 함께 봐야 합니다.

5.2 systemd 서비스 단위에서 LimitNOFILE 설정(가장 흔한 정답)

서비스 유닛에 다음을 추가합니다.

# /etc/systemd/system/myapp.service.d/override.conf
[Service]
LimitNOFILE=65535

적용:

systemctl daemon-reload
systemctl restart myapp
systemctl show myapp -p LimitNOFILE

5.3 커널 파라미터 영구 반영

/etc/sysctl.conf 또는 /etc/sysctl.d/*.conf:

# /etc/sysctl.d/99-fd.conf
fs.file-max = 2097152

적용:

sysctl --system

6) 근본 원인 해결: FD를 “덜 쓰게” 만드는 실전 포인트

한도만 올리면 당장은 버티지만, 누수나 폭증은 언젠가 더 큰 사고로 돌아옵니다.

6.1 커넥션 풀과 keep-alive 점검

  • DB/Redis/HTTP 클라이언트의 풀 사이즈가 트래픽 대비 과도하지 않은지
  • keep-alive가 꺼져 있어 매 요청마다 새 연결을 만들고 있지 않은지
  • 타임아웃이 너무 길어 유휴 연결이 쌓이지 않는지

특히 업스트림이 느릴 때 타임아웃이 길면 소켓이 오래 붙잡혀 FD가 빨리 소모됩니다.

6.2 로그 로테이션과 파일 재오픈

로그 파일이 교체되었는데 프로세스가 옛 파일 핸들을 계속 잡고 있으면 디스크 공간과 FD를 함께 잡아먹습니다.

확인:

lsof -p 1234 | grep deleted | head

대응:

  • logrotate 설정에서 copytruncate 또는 서비스에 postrotate로 재오픈 시그널 전송
  • 애플리케이션이 SIGHUP 등으로 로그 재오픈을 지원하는지 확인

6.3 inotify 워치 한도(파일 감시가 많은 앱)

프론트엔드 빌드, 파일 감시 기반 데몬, 개발 서버에서 자주 봅니다.

확인:

cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_user_instances

증상은 Too many open files 혹은 No space left on device로도 나타날 수 있습니다.

6.4 언어 런타임/라이브러리의 FD 누수 추적

  • Node.js: 스트림/HTTP 요청을 닫지 않음, 에러 경로에서 destroy 누락
  • Python: 파일을 with로 닫지 않음, 세션/커넥션 정리 누락
  • Go: defer resp.Body.Close() 누락, gRPC 스트림 정리 누락

gRPC 계열은 타임아웃/데드라인 미설정으로 연결이 오래 유지되며 FD가 쌓이기도 합니다. 네트워크 타임아웃 설계는 Go gRPC deadline exceeded(코드 4) 원인·해결도 참고할 만합니다.

7) 재발 방지: 모니터링과 알람 기준

운영에서는 “터지고 나서”가 아니라 “터지기 전에” 잡아야 합니다.

추천 지표:

  • 프로세스별 FD 수
    • ls /proc/$pid/fd | wc -l을 exporter로 수집하거나, process_open_fds 같은 기본 메트릭 활용
  • 시스템 전체 FD 사용량
    • /proc/sys/fs/file-nr 기반
  • 소켓 상태 분포
    • ss -s에서 time-wait, established 급증 감지

알람 예시(개념):

  • 프로세스 FD 사용률이 LimitNOFILE의 80% 초과 지속
  • 시스템 allocated FD가 fs.file-max의 80% 초과
  • time-wait가 평소 대비 특정 배수 이상 증가

8) 한 번에 따라 하는 “현장용” 명령 모음

장애 상황에서 복붙으로 쓰기 좋게 정리합니다.

# 1) 시스템 전체 FD 한도/사용
cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr

# 2) FD 많이 쓰는 프로세스 TOP
for p in /proc/[0-9]*; do
  pid=${p#/proc/}
  comm=$(cat /proc/$pid/comm 2>/dev/null)
  fdcount=$(ls -1 /proc/$pid/fd 2>/dev/null | wc -l)
  echo "$fdcount $pid $comm"
done | sort -nr | head -20

# 3) 특정 PID 한도/FD 상세
pid=1234
cat /proc/$pid/limits | grep -i "open files"
ls -l /proc/$pid/fd | head -50

# 4) 소켓 상태 요약
ss -s
ss -tan state established | wc -l
ss -tan state time-wait | wc -l

# 5) 즉시 상향(임시)
sysctl -w fs.file-max=2097152

9) 정리

Too many open files는 “한도를 올리면 끝”인 문제처럼 보이지만, 실제로는 다음 중 하나입니다.

  • 프로세스 한도 설정이 낮다(LimitNOFILE, nofile)
  • 시스템 전체 FD 풀이 부족하다(fs.file-max)
  • FD 누수로 시간이 갈수록 쌓인다
  • 트래픽/재시도 폭증으로 소켓이 순간적으로 급증한다

응급 복구는 한도 상향과 재시작으로 가능하지만, 재발 방지는 FD가 왜 증가했는지를 종류별로 확인하고(파일 vs 소켓 vs inotify), 커넥션/타임아웃/재시도/로그 로테이션까지 함께 점검해야 완성됩니다.