새벽을 밝히는 붉은 달

[OSError] Too many open files 본문

Develop

[OSError] Too many open files

자윰 2023. 9. 17. 01:40

며칠 전에 운영하던 서버에서 Too many open files 에러로 인해 노트북을 사용할 수 없다는 이슈가 있었다. 만약 내가 백엔드 직무였고 서비스와 직접 맞닿아 있는 부분을 운영하고 있었다면 이미 마주쳐봤을 것 같지만, 나는 사내 대상으로 운영하는 서버여서 접속자 수나 커넥션 등에 대해서는 신경을 쓰지 않다가 이번에 처음으로 만나게 된 에러라 잊지 않기 위해 기록한다.

1. 원인

최대로 열 수 있는 파일 디스크립터의 수를 NOFILE이라고 부른다. 이 NOFILE에는 limit이 있는데, 프로세스가 그 제한을 넘었기 때문에 발생하는 에러이다. 

2. 해결 방안

NOFILE에 대한 limit을 늘려주면 된다. 이때, 나 혼자 사용하는 것이 아닌 멀티유저로 운영되는 서버의 경우, 각 프로세스의 limit은 프로세스를 실행하는 계정의 limit을 따라가기 때문에 프로세스 limit만 늘리는 것이 아니라, 계정과 프로세스 모두 변경해주어야 문제가 되었던 프로세스를 다시 실행했을 때 같은 문제가 생기지 않는다고 한다.

 

limit을 변경하기에 앞서, limit에는 Soft와 Hard 두 가지가 존재한다.

 

Soft limit 은 root가 아닌 계정에서도 설정이 가능하다. Soft Limit 값은 일시적으로 넘어도 시스템 상에서 경고 이메일만 보낼 뿐  큰 문제가 되지 않는다. 이에 반해 Hard Limit은 root 계정에서만 설정이 가능하며, 프로세스가 이 limit을 넘어서 리소스를 사용할 수 없다.  만약 Too many open files가 계속 발생하고 기능이 아예 동작하지 않는다면 프로세스에 기본적으로 할당된 hard limit을 넘었다고 판단해야 한다.

3. 해결 커맨드

ulimit이란 커맨드를 사용할텐데, ulimit 커맨드는 user limit의 줄임말로 유저가 사용할 수 있는 리소스에 대해 조회 및 설정이 가능한 명령어이다. 다음과 같이 ulimit -a 를 사용하면 user에 대해 설정된 limit 값을 조회할 수 있다.

고랭 공식 이미지의 ulimit. 기본 open files는 1048576으로 꽤 크게 잡혀있는 것을 볼 수 있다.

위 명령어에 -R, -c 등의 옵션을 통해 해당값만 조회할 수 있는데, 이때 S를 같이 붙여서 조회하면 Soft limit을, H를 같이 붙여서 조회하면 Hard limit을 조회할 수 있다.

 

나는 open files를 변경하고 싶은 것이기 때문에, 아래 커맨드를 통해 Hard limit과 Soft limit 을 확인할 수 있다. (-Hn이 Hard limit, -Sn이 Soft limit)

$ ulimit -Hn
4096

$ ulimit -Sn
1024

만약 아래와 같이 입력을 해주면 Hard limit과 Soft limit이 둘 다 변하게 된다. 다만, 아래 명령어로 변경한 limit은 세션이 끊길 경우 default 값으로 다시 설정되게 된다.

$ ulimit -n 10000

영구적으로 NOFILE을 늘리려면 /etc/security/limits.conf 파일을 수정해야한다. 아래 예시와 같이 user1, user2 등 유저별로 수정을 해주는 것이 가능하다. 만약 모든 유저에 대해서 변경을 해주고 싶다면, 와일드카드 *을 적어주면 된다. 

$ vim /etc/security/limits.conf

user1 soft nofile 524288
user1 hard nofile 524288

user2 soft nofile 524288
user2 hard nofile 524288

저장 후 세션을 종료하고, 재접속하면 다음 명령어를 통해 특정 유저의 최대 오픈 파일 개수가 변경이 되었는지 확인할 수 있다.

$ su -c "ulimit -Hn" user1

3-1. 실행중인 프로세스의 limit 변경

운영중인 환경에서는 프로세스를 죽였다가 살리기 힘든 때가 있는데, 그런 경우 다음과 같은 방법을 통해 프로세스의 NOFILE limit을 변경할 수 있다.

 

먼저, 변경하고자 하는 프로세스의 PID를 알아낸다.

$ ps -ef | grep foobar | awk '{print $2}'
1234

해당 프로세스의 PID를 통해 NOFILE의 Soft limit, Hard limit을 조회한다.

$ prlimit --nofile --output RESOURCE,SOFT,HARD --pid 1234

다음 명령어를 통해 프로세스의 NOFILE soft 및 hard limit을 변경해준다.

$ prlimit --nofile=500000 --pid=1234

이전 명령어를 통해 프로세스에 대한 limit이 변경되었는지 확인한다.

$ prlimit --nofile --output RESOURCE,SOFT,HARD --pid 1234

3-2. 주의해야할 점

사용자별로 설정된 limit의 합이 리눅스 커널에 설정된 file-max값보다 작아야한다. 만약 시스템 레벨에서 허용하는 총 NOFILE 제한을 초과하면 시스템의 안정성에 영향을 미칠 수 있다.

 

리눅스 커널에 설정된 file-max 값은 다음 명령어를 통해 조회할 수 있다.

$ cat /proc/sys/fs/file-max

만약 해당 값을 늘리거나 줄이고 싶은 경우, /etc/sysctl.conf 에 fs.file-max 값을 설정해주면 된다.

$ vim /etc/sysctl.conf 

fs.file-max = (변경하고자 하는 수)

 

번외) 파일 디스크립터(File Descriptor, FD)

리눅스는 모든 것이 파일이다 라는 말을 한 번 쯤은 들어봤을 것 같다. 리눅스의 기반이 된 유닉스 시스템에서는 우리가 알고있는 txt 등 일반적인 파일부터 디렉토리, 소켓, 디바이스, 파이프 등 모든 객체를 파일로 관리한다. 유닉스와 유닉스를 기반으로 하는 OS는 프로세스가 파일 또는 파이프, 소켓과 같은 I/O 리소스에 접근할 때 파일 디스크립터를 사용한다.

 

파일 디스크립터는 Non-negative integer의 값을 가지고 있으며, negative value는 no value 또는 에러 등을 나타내기 위해 예약되어 있다. 프로세스가 실행 중에 파일을 열면 해당 프로세스의 파일 디스크립터 숫자 중에서 사용하지 않는 값들 중 가장 작은 값을 할당한다. 해당 프로세스가 열려있는 파일에 시스템콜을 이용하여 접근할 때, 파일 디스크립터 값을 이용하여 파일을 지칭할 수 있다.

 

프로그램이 프로세스로 메모리에서 실행할 때 표준 입력(Standard Input), 표준 출력(Standard Output), 표준 에러(Standard Error)가 각각 0, 1, 2라는 값으로 기본적으로 할당된다. POSIX 표준에서는 unistd.h에 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO로 참조되며, stdio.h에서는 stdin, stdout, stderr이라는 값으로 참조된다.

https://en.wikipedia.org/wiki/File_descriptor

파일 디스크립터는 커널에 의해 유지되는 프로세스별 파일 디스크립터 테이블에 인덱싱되며, 이 테이블은 다시 모든 프로세스에 의해 open된 파일의 system-wide 테이블, 즉 파일 테이블에 인덱싱된다. 이 테이블에는 파일 또는 그외 리소스들이 어떤 모드로 열렸는지 (read, write, append.. 등) 기록한다. 파일 테이블은 실제 근본 파일을 describe하고 있는 inode 테이블이라는 세 번째 테이블로 인덱싱된다. 프로세스는 파일이나 inode 테이블에 직접 액세스할 수 없으며, 프로세스가 입출력을 하려면 시스템콜을 호출하여 파일 디스크립터를 커널에 전달하고 커널은 프로세스를 대신하여 파일에 액세스한다.

https://en.wikipedia.org/wiki/File_descriptor

 

open(), socket(), accept(), pipe(), clone() 등의 함수를 사용하면 파일 디스크립터가 생성되며, dirfd(), fileno() 등을 통해 파일 디스크립터 값을 읽어올 수 있다. 파일을 읽어오거나 소켓 프로그래밍 등에서 자주 사용하는 read(), write(), recv(), send(), lseek(), fstat() 등이 단일 파일 디스크립터에 행할 수 있는 연산들이며, close(), dup(), dup2() 등이 파일 디스크립터 테이블에 행할 수 있는 연산들이다.

Reference

https://markruler.github.io/posts/java/too-many-open-files/

https://blog.hbsmith.io/too-many-open-files-%EC%97%90%EB%9F%AC-%EB%8C%80%EC%9D%91%EB%B2%95-9b388aea4d4e

https://www.howtogeek.com/805629/too-many-open-files-linux/

https://www.lesstif.com/lpt/linux-increase-the-max-open-files-per-user-48103542.html

https://dev-ahn.tistory.com/96

https://en.wikipedia.org/wiki/File_descriptor

Comments