- Published on
Linux EMFILE(Too many open files) 원인과 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버를 운영하다 보면 갑자기 애플리케이션 로그에 Too many open files가 찍히고, 소켓 연결/파일 읽기/로그 기록까지 줄줄이 실패하는 상황을 만납니다. 이 오류는 리눅스 커널이 파일 디스크립터(File Descriptor, FD) 를 더 이상 할당할 수 없을 때 발생하며, 에러 코드로는 보통 EMFILE(프로세스 한도 초과) 또는 ENFILE(시스템 전역 한도 초과)로 나타납니다.
이 글에서는 EMFILE의 구조를 이해하고, 실제 장애에서 가장 자주 쓰는 진단 명령어, 원인별 해결책, systemd/Kubernetes에서의 영구 설정, 그리고 재발 방지 체크리스트까지 한 번에 정리합니다.
EMFILE/ENFILE의 의미: “파일”은 파일만이 아니다
리눅스에서 FD는 단순히 디스크 파일만 가리키지 않습니다.
- TCP/UDP 소켓(서버 리슨 소켓, 클라이언트 커넥션)
- 파이프/유닉스 도메인 소켓
- inotify, eventfd, epoll fd
/dev/null, 로그 파일 핸들
즉, 트래픽이 늘어 소켓이 증가하거나, 로그/파일 핸들을 닫지 않는 버그가 있으면 "파일을 많이 열었다" 로 인식됩니다.
정리하면:
- EMFILE: 해당 프로세스가 가질 수 있는 FD 개수 제한(ulimit)을 넘음
- ENFILE: 시스템 전체의 open file table(전역) 제한을 넘음
가장 빠른 1분 진단 플로우
장애 중에는 “무엇이 얼마나 열려 있는지”를 빠르게 잡는 게 중요합니다.
1) 프로세스별 FD 사용량 확인
# PID 확인
ps -ef | grep myapp
# 열린 FD 개수
ls -1 /proc/<PID>/fd | wc -l
# 어떤 FD가 열려 있는지(상위 몇 개만)
ls -l /proc/<PID>/fd | head
2) 현재 프로세스 한도(soft/hard) 확인
# 현재 쉘 기준
ulimit -n
# 특정 PID의 limits 확인
cat /proc/<PID>/limits | grep -i "open files"
3) 시스템 전역 한도 확인
cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr # allocated, unused, max
/proc/sys/fs/file-nr는 보통allocated unused max형태로 보이며,allocated가max에 근접하면 ENFILE 가능성이 커집니다.
4) 누수/삭제된 파일 핸들(디스크도 같이 찰 때) 확인
FD가 많아지면 디스크 100%와 함께 터지는 경우도 흔합니다. 특히 삭제했는데 프로세스가 계속 잡고 있는 파일은 공간이 회수되지 않습니다.
lsof -p <PID> | head
lsof -p <PID> | grep deleted | head
이 케이스는 아래 글과 원인이 맞닿아 있습니다.
원인 1: ulimit(프로세스 FD 한도)가 낮다
가장 흔한 케이스입니다. 특히 다음 상황에서 자주 터집니다.
- systemd 서비스 기본 LimitNOFILE이 낮음
- 컨테이너 런타임/쿠버네티스에서 기본 ulimit가 낮음
- SSH로 들어가서 올린 ulimit와 서비스의 ulimit가 다름
즉시 대응(현재 쉘)
ulimit -n 65535
# 이후 해당 쉘에서 실행한 프로세스에만 적용
하지만 서비스 프로세스에는 적용되지 않는 경우가 많습니다.
systemd 서비스에 영구 적용
/etc/systemd/system/myapp.service.d/override.conf를 만들고:
[Service]
LimitNOFILE=1048576
적용:
sudo systemctl daemon-reload
sudo systemctl restart myapp
# 확인
cat /proc/$(pidof myapp)/limits | grep -i "open files"
서비스가 계속 재시작되는 상황이라면(EMFILE로 크래시/헬스체크 실패) 함께 점검하면 좋습니다.
원인 2: 애플리케이션의 FD 누수(파일/소켓 close 누락)
한도를 올려도 다시 터지면 “진짜 원인”은 보통 누수입니다.
누수의 전형적인 패턴
- 예외/타임아웃 경로에서
close()가 호출되지 않음 - HTTP 클라이언트 keep-alive 풀을 무한정 키움
- DB 커넥션 풀/소켓 풀 설정이 잘못되어 커넥션이 쌓임
- 로그 롤링/파일 교체 로직에서 핸들을 놓지 않음
프로세스 내 FD 증가 추적(간단 모니터링)
PID=<PID>
watch -n 1 "ls -1 /proc/$PID/fd | wc -l"
FD 수가 트래픽과 무관하게 계속 우상향하면 누수 가능성이 큽니다.
어떤 타입의 FD가 많은지 요약
PID=<PID>
ls -l /proc/$PID/fd \
| awk '{print $NF}' \
| sed 's/->.*$//' \
| sed 's/\[.*\]//' \
| head
# 소켓이 대부분인지 확인
ls -l /proc/$PID/fd | grep -c socket:
lsof로 “어디로” 열리고 있는지 확인
lsof -p <PID> | awk '{print $5, $9}' | head
# TCP 연결이 폭증하는지
lsof -p <PID> -iTCP -sTCP:ESTABLISHED | wc -l
# 특정 파일이 반복적으로 열리는지(로그 등)
lsof -p <PID> | grep "/var/log" | head
(예시) Python에서 흔한 누수와 수정
# 나쁜 예: 파일을 열고 예외가 나면 close가 보장되지 않음
f = open("/tmp/data.txt")
process(f)
# 좋은 예: 컨텍스트 매니저로 close 보장
with open("/tmp/data.txt") as f:
process(f)
(예시) Node.js에서 소켓/요청 누수 방지 포인트
import http from 'node:http';
const agent = new http.Agent({
keepAlive: true,
maxSockets: 256,
maxFreeSockets: 64,
timeout: 30_000,
});
// 요청마다 새 Agent를 만들면 소켓이 누적될 수 있음(나쁜 패턴)
// 공용 agent를 재사용하고, 타임아웃/에러 처리를 확실히 한다.
원인 3: 시스템 전역 file-max 또는 커널 리소스 한계(ENFILE)
프로세스 ulimit는 충분한데도 시스템 전체가 바닥나면 ENFILE이 납니다. 대규모 멀티테넌트/노드에서 흔합니다.
file-max 확인 및 조정
sysctl fs.file-max
# 임시 적용
sudo sysctl -w fs.file-max=2097152
영구 적용(/etc/sysctl.conf 또는 /etc/sysctl.d/99-custom.conf):
fs.file-max = 2097152
적용:
sudo sysctl --system
함께 봐야 하는 네트워크 관련 상한
EMFILE처럼 보이지만 실제로는 네트워크 추적 테이블이 포화되어 연결이 불안정해지는 경우도 있습니다(특히 Kubernetes/EKS에서).
원인 4: “FD는 충분한데 왜 EMFILE?” — 프로세스/스레드/라이브러리의 숨은 FD 사용
일부 런타임/라이브러리는 내부적으로 많은 FD를 사용합니다.
- 파일 감시(inotify) 기반 핫리로드/워처
- metrics exporter가 소켓을 다량 생성
- gRPC/HTTP2에서 스트림과 커넥션 관리 설정 오류
- 너무 많은 워커/스레드가 각자 커넥션 풀을 가짐(풀 * 워커 수 만큼 증가)
이 경우 “커넥션 풀 크기”를 단일 프로세스 기준이 아니라 프로세스 복제 수(워커 수)까지 곱해서 계산해야 합니다.
해결 전략: 한도 상향 vs 누수 제거, 무엇이 먼저인가
운영 관점에서 권장 순서는 다음입니다.
- 즉시 장애 완화: ulimit/LimitNOFILE 상향으로 서비스 복구(가능하면)
- 원인 규명: FD가 어떤 타입으로 증가하는지(lsof, /proc)
- 근본 해결: 누수 제거, 풀/워커/keep-alive 설정 조정
- 재발 방지: 모니터링/알람 + 배포 전 부하 테스트
한도 상향은 시간을 벌어주지만, 누수가 있으면 결국 다시 터집니다.
운영 체크리스트(재발 방지)
1) 모니터링 지표
- 프로세스별 열린 FD 수
- 시스템
file-nr의 allocated 추세 - ESTABLISHED 소켓 수, TIME_WAIT 수
- 애플리케이션 레벨: 요청 수 대비 커넥션 수/풀 사용량
2) 알람 기준 예시
- 프로세스 FD 사용량이 ulimit의 70~80% 초과
file-nr allocated / file-max가 70% 초과- FD 수가 트래픽과 무관하게 지속 증가(누수 시그널)
3) 로그/코어 덤프
- EMFILE 발생 시점의 스택 트레이스 확보
lsof -p <PID>스냅샷을 장애 시 자동 수집
간단한 수집 스크립트 예시:
#!/usr/bin/env bash
PID="$1"
OUT="/tmp/fd-dump-$PID-$(date +%s)"
mkdir -p "$OUT"
cat /proc/$PID/limits > "$OUT/limits.txt"
ls -l /proc/$PID/fd > "$OUT/fd-list.txt"
lsof -p $PID > "$OUT/lsof.txt" 2>&1
lsof -p $PID -iTCP > "$OUT/lsof-tcp.txt" 2>&1
echo "saved to $OUT"
마무리
Too many open files는 단순히 “파일을 닫아라” 수준의 문제가 아니라, 프로세스 한도(EMFILE), 시스템 전역 한도(ENFILE), 애플리케이션 누수, 커넥션 풀/워커 설계가 교차하는 운영 이슈입니다.
가장 중요한 것은 (1) /proc/<pid>/fd, lsof, ulimit, file-nr로 어디가 병목인지 즉시 판별하고, (2) 한도 상향으로 시간을 번 뒤, (3) FD가 증가하는 “종류”를 특정해 누수/설정 문제를 근본적으로 제거하는 것입니다.
장애 상황에서 FD가 많아지며 디스크 공간 회수가 안 되는 현상까지 동반된다면, 위에서 소개한 deleted-but-open 케이스도 반드시 같이 확인하세요.