본문 바로가기
서버연구 블로그 서버연구 블로그

Linux File Descriptor란 무엇이며 왜 서버 장애를 일으킬까?

읽는 시간 약 17분

서버에서 Too many open files라는 메시지를 처음 보면 조금 이상하게 느껴질 수 있습니다.

디스크 공간도 남아 있고 메모리도 충분한데, Nginx가 새로운 연결을 받지 못하거나 애플리케이션에서 파일을 열지 못합니다. 심하면 서비스가 살아 있는데도 신규 요청만 계속 실패합니다.

이런 상황에서 확인해야 하는 것이 File Descriptor, 줄여서 FD입니다.

File Descriptor는 단순히 파일을 몇 개 열었는지 세는 숫자가 아닙니다. Linux에서는 일반 파일뿐 아니라 네트워크 소켓, 파이프, 로그, 디바이스 등 여러 자원을 FD를 통해 다룹니다.

그래서 웹서버처럼 연결을 많이 처리하는 시스템에서는 FD가 부족해지는 순간 생각보다 넓은 범위의 장애가 발생할 수 있습니다.

File Descriptor는 실제 파일만 의미하지 않는다

Linux 프로세스가 어떤 파일을 열면 Kernel은 그 자원에 접근하기 위한 번호를 프로세스에 돌려줍니다.

그 번호가 File Descriptor입니다.

프로세스를 실행하면 기본적으로 다음 세 개가 열립니다.

0  stdin
1  stdout
2  stderr

그리고 파일이나 네트워크 연결을 열 때마다 새로운 FD가 추가됩니다.

예를 들어 웹서버라면 이런 것들이 모두 FD와 관계가 있습니다.

설정 파일
로그 파일
Client Socket
Upstream Socket
Pipe
eventfd
epoll
Unix Socket

그래서 서버에서

“열어둔 파일도 별로 없는데 왜 File Descriptor가 부족하지?”

라는 상황이 생길 수 있습니다.

실제 원인은 파일이 아니라 수천 개의 네트워크 연결일 수도 있습니다.

실행 중인 프로세스는 FD를 얼마나 쓰고 있을까

PID가 18432인 프로세스를 확인한다고 해보겠습니다.

가장 간단한 방법은 /proc을 보는 것입니다.

ls /proc/18432/fd | wc -l

결과가 4872 라면 현재 이 프로세스가 약 4,872개의 FD를 사용하고 있다는 뜻입니다.

어떤 자원을 열고 있는지 조금 더 자세히 보고 싶다면

ls -l /proc/18432/fd 를 사용할 수 있습니다.

실제 출력에서는 이런 모습을 볼 수 있습니다.

0 -> /dev/null
1 -> /var/log/myapp.log
2 -> /var/log/myapp-error.log
37 -> socket:[924571]
38 -> socket:[924572]
39 -> socket:[924573]

뒤쪽에 socket:이 계속 나온다면 일반 파일보다 네트워크 연결에서 FD를 많이 사용하고 있을 가능성이 높습니다.

lsof도 편합니다.

lsof -nP -p 18432

다만 FD가 수만 개씩 열린 프로세스에서 lsof를 반복적으로 실행하면 그 자체도 부담이 될 수 있으니 장애 상황에서는 필요한 만큼만 확인하는 편이 좋습니다.

문제는 FD에 한도가 있다는 것이다

한 프로세스가 FD를 무한정 열 수 있는 것은 아닙니다.

현재 shell의 제한은 다음처럼 확인합니다.

ulimit -n                                         

예를 들어

1024

가 나온다면 현재 shell 환경에서 open file limit가 1,024라는 뜻입니다.

조금 더 정확하게 보려면 실행 중인 프로세스의 제한을 직접 확인합니다.

cat /proc/18432/limits | grep "open files"

예를 들면

Max open files            1024                 65535

처럼 표시됩니다.

앞쪽은 Soft Limit, 뒤쪽은 Hard Limit입니다.

프로세스가 Soft Limit에 도달하면 새로운 FD를 열지 못할 수 있습니다.

그때 자주 보게 되는 오류가 바로

Too many open files

입니다.

Linux에서는 보통 이 상황을 EMFILE 오류로 표현합니다.

FD가 가득 차면 서버는 어떻게 보일까

재미있는 점은 프로세스가 바로 죽지 않을 수도 있다는 것입니다.

예를 들어 Nginx나 애플리케이션 프로세스가 FD 한도에 도달했다고 해보겠습니다.

기존 연결은 어느 정도 계속 처리됩니다.

하지만 새로운 socket을 만들지 못합니다.

그러면 사용자는

기존 접속 → 정상
새로운 접속 → 실패

같은 이상한 현상을 볼 수 있습니다.

애플리케이션에서는 다음과 비슷한 오류가 쌓일 수 있습니다.

accept() failed (24: Too many open files)

또는

java.io.FileNotFoundException:
Too many open files

이 상태에서 프로세스가 ps에 보인다고 해서 정상이라고 판단하면 안 됩니다.

프로세스는 살아 있지만 새로운 자원을 더 이상 열 수 없는 상태일 수 있기 때문입니다.

ulimit만 올렸는데 해결되지 않는 이유

FD 문제를 검색하면 가장 많이 보이는 해결방법이 이것입니다.

ulimit -n 65535

테스트 환경에서는 효과가 있을 수 있습니다.

하지만 운영 서버에서는 이것만 바꾸고 끝내면 문제가 해결되지 않는 경우가 많습니다.

특히 systemd가 서비스를 실행하고 있다면 SSH 접속 후 확인한 ulimit 값과 실제 서비스의 제한이 다를 수 있습니다.

실제 서비스 값을 확인합니다.

systemctl show nginx | grep LimitNOFILE

또는

systemctl show myapp | grep LimitNOFILE

예를 들어

LimitNOFILE=65535
LimitNOFILESoft=65535

처럼 확인할 수 있습니다.

즉,

내 shell ulimit
≠
systemd가 실행한 서비스의 FD limit

일 수 있습니다.

그래서 터미널에서 ulimit -n만 보고 서비스 설정도 같다고 생각하면 엉뚱한 곳을 보고 있을 수 있습니다.

제한을 올리기 전에 FD가 왜 증가했는지 봐야 한다

이 부분이 실제 장애 분석에서는 더 중요합니다.

정상적으로 동작하던 서비스의 FD가 갑자기 5천 개, 1만 개, 3만 개로 계속 증가한다면 단순히 Limit가 낮은 것이 아닐 수도 있습니다.

FD Leak 가능성을 생각해야 합니다.

정상적인 프로그램이라면 자원을 사용한 뒤 FD를 닫습니다.

open
↓
사용
↓
close

그런데 특정 오류 경로에서 close()가 실행되지 않는다면 요청이 들어올 때마다 FD가 하나씩 남을 수 있습니다.

예를 들어

09:00   FD 1,200
10:00   FD 2,850
11:00   FD 5,420
12:00   FD 9,730

처럼 서비스 트래픽과 함께 FD 수가 계속 올라가고 다시 내려오지 않는다면 단순한 순간 부하와는 모습이 다릅니다.

이 상태에서 limit를

10,000 → 50,000

으로 올리면 장애 발생 시간을 늦출 뿐입니다.

결국 50,000도 채울 수 있습니다.

그래서 FD 장애는 현재 사용량보다 증가 추세를 같이 보는 것이 좋습니다.

CLOSE_WAIT가 계속 쌓이면 그냥 넘기면 안 된다

네트워크 서버에서 FD 증가와 함께 자주 확인하는 것이 TCP 상태입니다.

ss -tan | awk '{print $1}' | sort | uniq -c

환경에 따라 다음처럼 보일 수 있습니다.

120 ESTAB
32 LISTEN
6847 CLOSE-WAIT

CLOSE_WAIT가 비정상적으로 많다면 눈여겨봐야 합니다.

상대방은 이미 연결을 종료했는데 로컬 애플리케이션이 해당 socket을 아직 닫지 않은 상태이기 때문입니다.

어떤 프로세스와 연결돼 있는지도 확인할 수 있습니다.

ss -tanp state close-wait

이 상태가 계속 누적되면 애플리케이션이 socket을 제대로 정리하지 않는 문제와 연결될 수 있고, 그 socket들이 FD를 계속 차지할 수 있습니다.

이럴 때 FD limit만 올리는 것은 근본적인 해결이 아닙니다.

애플리케이션에서 연결 종료 처리가 제대로 되고 있는지 확인해야 합니다.

TIME_WAIT와 FD는 같은 문제로 보면 안 된다

여기서 많이 혼동하는 것이 TIME_WAIT입니다.

TCP 연결이 많으면 TIME_WAIT도 많이 보일 수 있습니다.

그래서

TIME_WAIT가 많으니 File Descriptor도 그만큼 사용하고 있겠지.

라고 생각하기 쉽습니다.

하지만 일반적으로 애플리케이션이 socket을 정상적으로 닫고 FD가 반환된 이후에도 Kernel은 TCP 연결 상태를 일정 시간 TIME_WAIT로 관리할 수 있습니다.

즉,

TIME_WAIT 개수와 프로세스가 사용하는 FD 개수는 같은 숫자가 아닙니다.

반대로 CLOSE_WAIT는 애플리케이션이 아직 socket을 닫지 않은 상황과 연결될 수 있기 때문에 FD 문제를 볼 때 훨씬 직접적인 단서가 될 수 있습니다.

이 차이를 모르고 TIME_WAIT만 줄이는 튜닝부터 시작하면 실제 FD 문제는 그대로 남습니다.

Nginx에서는 worker_connections만 보면 부족하다

Nginx를 운영할 때도 FD 한도는 꽤 중요합니다.

설정에는 이런 값이 있습니다.

events {worker_connections 4096;}

많이 하는 실수가

worker_connections = 동시 사용자 수

라고 그대로 계산하는 것입니다.

실제로 Reverse Proxy 환경에서는 하나의 요청을 처리하면서 client connection뿐 아니라 upstream과의 연결도 필요할 수 있습니다.

즉, 연결 하나가 항상 FD 하나만 사용한다고 단순하게 계산하기 어렵습니다.

Nginx에서는 필요에 따라

worker_rlimit_nofile 65535;

같은 설정도 검토할 수 있습니다.

하지만 worker_connections만 크게 올리고 OS나 서비스의 FD limit가 낮다면 실제 한도는 그보다 먼저 막힐 수 있습니다.

그래서 Nginx에서는 적어도

worker_connections
worker_processes
LimitNOFILE
실제 FD 사용량
upstream 연결 구조

를 같이 봐야 합니다.

숫자 하나만 크게 만드는 튜닝은 오히려 장애 시점을 예측하기 어렵게 만들 수 있습니다.

프로세스별 한도 외에 시스템 전체 한도도 있다

프로세스 FD 한도만 존재하는 것은 아닙니다.

Linux Kernel에는 시스템 전체에서 관리할 수 있는 open file 관련 한도도 있습니다.

cat /proc/sys/fs/file-max

현재 시스템에서 할당된 file handle 상태는 다음처럼 확인할 수 있습니다.

cat /proc/sys/fs/file-nr

예를 들면

8128    0    9223372036854775807

처럼 환경에 따라 값이 표시됩니다.

여기서 봐야 할 핵심은 서버 전체에서도 file handle을 관리하고 있다는 점입니다.

프로세스별 limit에 걸리는 상황은 흔히 EMFILE, 시스템 전체의 file table 자원이 부족한 상황에서는 ENFILE과 연결될 수 있습니다.

실제 장애에서는 메시지가 비슷해 보여도 원인의 범위가 다를 수 있습니다.

한 서비스만 문제인지 서버 전체가 문제인지 나눠서 보는 이유입니다.

삭제한 로그 파일이 계속 FD를 잡고 있는 경우

앞서 디스크 100% 문제에서도 다뤘던 상황인데 FD와도 연결됩니다.

애플리케이션이 큰 로그 파일을 열고 있는 상태에서

rm application.log

로 파일을 삭제했다고 해보겠습니다.

파일 이름은 디렉터리에서 사라집니다.

하지만 프로세스가 FD를 계속 열고 있다면 실제 inode와 데이터는 즉시 정리되지 않을 수 있습니다.

이런 파일은 lsof +L1 로 확인할 수 있습니다.

예를 들어

java  18432  app  17w  REG  ...  application.log (deleted)

처럼 나온다면 프로세스가 삭제된 파일을 아직 잡고 있다는 뜻입니다.

이 상황에서는 디스크 공간이 안 돌아옴
+
FD도 계속 유지됨

이라는 두 가지 문제가 같이 나타날 수 있습니다.

그래서 로그 파일을 직접 삭제하는 것보다 logrotate와 애플리케이션의 reopen 동작을 맞춰서 관리하는 편이 안전합니다.

실제 장애에서는 이렇게 좁혀본다

Too many open files가 발생했다면 처음부터 Kernel Parameter를 바꾸기보다 몇 가지를 먼저 봅니다.

우선 문제가 발생한 프로세스의 PID를 찾습니다.

pgrep -f myapp

현재 FD 사용량을 확인합니다.

ls /proc/PID/fd | wc -l

그리고 limit를 비교합니다.

cat /proc/PID/limits | grep "open files"

예를 들어

현재 FD       4,980
Soft Limit    5,000

이라면 FD limit 근처까지 접근한 것이 바로 보입니다.

이후에는 어떤 FD가 많은지 확인합니다.

lsof -nP -p PID

socket이 많다면

ss -tanp로 TCP 상태까지 봅니다.

여기서 CLOSE_WAIT가 대량으로 쌓여 있다면 애플리케이션 연결 종료 로직을 의심해볼 수 있습니다.

반대로 트래픽 증가에 맞춰 ESTABLISHED가 증가했다가 다시 줄어들고 FD 수도 같이 내려간다면 정상적인 부하 증가일 가능성도 있습니다.

같은 FD 5,000개라도 어떻게 5,000개가 됐는지가 훨씬 중요합니다.

장애 직전에 이런 모습이라면 방향을 잡기 쉽다

예를 들어 서버에서 다음 흐름이 관찰됐다고 해보겠습니다.

14:00  FD 2,100
14:10  FD 3,800
14:20  FD 5,900
14:30  FD 8,200

CLOSE_WAIT 지속 증가

14:34
Too many open files

14:35
신규 API 요청 실패

이 정도 흔적이 있다면 단순히 FD 한도를 늘리는 것보다 애플리케이션에서 연결을 제대로 닫지 못하는 문제를 먼저 볼 이유가 충분합니다.

반대로

평소 FD 2,000
이벤트 트래픽 발생
FD 18,000
트래픽 종료
FD 2,300

처럼 다시 내려온다면 누수보다 정상적인 동시 연결 증가에 가까울 수 있습니다.

이런 경우에는 실제 최대 트래픽을 감당할 수 있도록 FD limit와 서버 설정을 검토하는 방향이 맞습니다.

FD 문제를 재발시키지 않으려면 사용률을 같이 본다

운영 서버에서 FD는 장애가 발생한 뒤 확인하기보다 모니터링 항목에 넣어두는 편이 좋습니다.

예를 들어 limit가 65,535라고 해서

FD > 60,000

일 때만 알람을 주는 방식은 조금 늦습니다.

현재 사용량과 Limit를 이용해

FD 사용률 = 현재 열린 FD / Soft Limit × 100

형태로 보는 것이 편합니다.

예를 들어

현재 FD     52,000
Soft Limit  65,535
사용률      약 79%

라면 아직 장애가 나지는 않았지만 확인할 시점입니다.

여기에 시간에 따른 증가량까지 같이 보면 더 좋습니다.

FD가 일정 범위 안에서 올라갔다 내려오는 것인지, 며칠 동안 계속 증가만 하는 것인지에 따라 대응이 완전히 달라지기 때문입니다.

마무리

Linux File Descriptor는 이름 때문에 단순한 파일 개수로 생각하기 쉽습니다.

하지만 실제 서버에서는 네트워크 socket을 비롯해 여러 자원이 FD를 사용합니다.

그래서 FD가 한도에 도달하면 프로세스가 살아 있어도 새로운 연결을 받지 못하거나 파일을 열지 못하면서 서비스 장애가 발생할 수 있습니다.

문제가 생겼을 때 ulimit -n부터 크게 올리는 것보다 먼저 현재 프로세스가 FD를 몇 개 사용하고 있는지, 어떤 FD가 증가했는지, CLOSE_WAIT 같은 비정상적인 연결이 누적되고 있는지를 확인하는 편이 낫습니다.

특히 FD가 시간이 지날수록 계속 증가한다면 한도를 높이는 것만으로는 해결되지 않습니다.

File Descriptor 장애에서 봐야 할 것은 ‘한도가 몇 개인가’보다 ‘누가 FD를 계속 열고 있고, 왜 닫지 못하고 있는가’입니다.

이 부분을 찾을 수 있어야 단순한 설정 변경이 아니라 실제 장애 원인까지 해결할 수 있습니다.

instone
함께 보면 좋은 글

댓글 0

첫 댓글을 남겨보세요.

error: Content is protected !!

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.