리눅스에서 하나의 프로세스에서 열 수 있는 파일 갯수에 제한이 있다. 네트워크 서버를 만들거나 파일을 동시에 많이 열어야 하는 프로그램을 만든다면 max open file 을 확인하고 변경할 수 있는 법을 알아야 한다.
ulimit
man page의 설명을 요약하면 아래와 같다.
시스템상에 있는 shell 과 shell이 생성한 프로세스들이 사용 가능한 자원을 통제한다.
이 말은 즉 shell은 유저에 의해 생성이 되므로 유저 프로세스들이 사용할 수 있는 자원을 컨트롤 한다는 말이다.
쉽게 말하면 우리가 어떤 프로세스를 실행시키면 이 프로세스가 사용할 수 있는 리소스를 통제한다는 것이다.
한번 쉘에 로그인해서 실행해보자
[user ~]$ ulimit -a
real-time non-blocking time (microseconds, -R) unlimited
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 30446
max locked memory (kbytes, -l) unlimited
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) unlimited
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
이것이 하나의 프로세스가 사용가능한 자원의 한계이다.
여기서 open files 에 집중해보자.
open files
open files (-n) 1024
현재는 1024개로 되어있다.
하나의 프로세스에서 열 수 있는 파일의 최대 갯수가 1024개라는 것이다.
100개로 변경
아래와 같이 명령어를 입력하면 open files 의 값을 100으로 변경한다.
$ ulimit -n 100
테스트를 위해 100개로 바꿔보자
그리고 NodeJS 로 파일을 100개 여는 프로그램을 만들자.
const fs = require('fs')
const fds = []
for (let i=0; i< 100; i++) {
fs.open('./a.txt', 'r', function (err, fd) {
if (err) { console.log(err) }
else {
fds.push(fd)
console.log('fds:', fds.length)
}
})
}
// 열린파일을 확인할 시간을 위해 대기
setTimeout(() => {}, 60000)
이것을 실행시키면 아래와 같은 결과가 나온다.
fds: 1
fds: 2
fds: 3
... 중간 생략 ...
fds: 80
fds: 81
fds: 82
fds: 83
[Error: EMFILE: too many open files, open './a.txt'] {
errno: -24,
code: 'EMFILE',
syscall: 'open',
path: './a.txt'
}
[Error: EMFILE: too many open files, open './a.txt'] {
errno: -24,
code: 'EMFILE',
syscall: 'open',
path: './a.txt'
}
.. 에러 반복 생략 ..
[Error: EMFILE: too many open files, open './a.txt'] {
errno: -24,
code: 'EMFILE',
syscall: 'open',
path: './a.txt'
}
83개까지는 성공하고,
나머지 17개는 too many open files 에러가 발생하면서 실패한다.
lsof 를 이용하여 열린 파일 확인
이제 새로운 쉘을 뛰워서 이 프로세스가 어떤 파일을 열고 있는지 확인해보았다.
아까 실행한 프로그램의 PID를 알아내어 아래 명렁어에 넣어주자
$ lsof -p PID
- 여기서 FD (File Descriptor) 가 0u, 1u 와 같이 숫자로 시작하는것에 집중하자.
[user]$ lsof -p 13975
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 13975 ec2-user cwd DIR 202,1 61 9792991 /home/ec2-user/test
node 13975 ec2-user rtd DIR 202,1 237 1024 /
node 13975 ec2-user txt REG 202,1 82604240 1500073 /home/ec2-user/.nvm/versions/node/v16.19.1/bin/node
node 13975 ec2-user mem REG 202,1 2385592 8523090 /usr/lib64/libc.so.6
node 13975 ec2-user mem REG 202,1 2269320 8523277 /usr/lib64/libstdc++.so.6.0.29
node 13975 ec2-user mem REG 202,1 15952 8523098 /usr/lib64/libpthread.so.0
node 13975 ec2-user mem REG 202,1 108032 8519859 /usr/lib64/libgcc_s-11-20221121.so.1
node 13975 ec2-user mem REG 202,1 905992 8523093 /usr/lib64/libm.so.6
node 13975 ec2-user mem REG 202,1 15936 8523092 /usr/lib64/libdl.so.2
node 13975 ec2-user mem REG 202,1 843128 8523086 /usr/lib64/ld-linux-x86-64.so.2
node 13975 ec2-user 0u CHR 136,0 0t0 3 /dev/pts/0
node 13975 ec2-user 1u CHR 136,0 0t0 3 /dev/pts/0
node 13975 ec2-user 2u CHR 136,0 0t0 3 /dev/pts/0
node 13975 ec2-user 3u a_inode 0,13 0 39 [eventpoll]
node 13975 ec2-user 4r FIFO 0,12 0t0 43108 pipe
node 13975 ec2-user 5w FIFO 0,12 0t0 43108 pipe
node 13975 ec2-user 6r FIFO 0,12 0t0 43109 pipe
node 13975 ec2-user 7w FIFO 0,12 0t0 43109 pipe
node 13975 ec2-user 8u a_inode 0,13 0 39 [eventfd:7]
node 13975 ec2-user 9u a_inode 0,13 0 39 [eventpoll:10,12]
node 13975 ec2-user 10r FIFO 0,12 0t0 43110 pipe
node 13975 ec2-user 11w FIFO 0,12 0t0 43110 pipe
node 13975 ec2-user 12u a_inode 0,13 0 39 [eventfd:8]
node 13975 ec2-user 13u a_inode 0,13 0 39 [eventpoll:14,16]
node 13975 ec2-user 14r FIFO 0,12 0t0 43111 pipe
node 13975 ec2-user 15w FIFO 0,12 0t0 43111 pipe
node 13975 ec2-user 16u a_inode 0,13 0 39 [eventfd:9]
node 13975 ec2-user 17r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 18r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 19r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 20r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 21r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 22r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 23r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 24r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 25r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 26r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 27r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 28r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 29r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 30r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
... 중간 생략 ...
node 13975 ec2-user 91r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 92r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 93r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 94r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 95r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 96r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 97r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 98r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
node 13975 ec2-user 99r REG 202,1 5 9792993 /home/ec2-user/test/a.txt
FD가 0u, 1u, 6r, 99r 처럼 <숫자 + u, r, w> 이렇게 되어 있는 것이 File Descriptor 번호이고 현재 열린 파일이다.
0u ~ 16u : 17개의 파일이 프로그램 실행을 위해 필요한 FD 일 것이라 추측된다.
17r ~ 99r : 83개가 프로그램 코드로 open 한 파일의 FD이다.
모두 합쳐서 정확히 100개까지 파일을 open하는 것을 볼 수가 있다.
100개가 모두 차는 바람에 프로그램상에서 a.txt라는 파일을 100번을 모두 열지 못하고
83 이후부터는 too many open files 에러를 리턴한다.
ulimit 로 확인한 open files 값이 이렇게 하나의 프로세스별 limit으로 동작하는 걸 확인하였다.
자 그럼 쉘을 종료하고 다시 들어와서 ulimit 으로 open files 값을 확인해보자.
그러면 변경된 값이 아닌 원래의 값으로 다시 돌아와 있다.
그렇다.
ulimit으로 값을 바꾸는 건 현재 세션에서만 유효한 것이다.
서버를 운영중인데 재부팅하면 사라지는 설정이 있으며 안된다.
이것을 재부팅 되더라도 고정시킬 수 있는 방법이 필요하다.
🤔🤔🤔
limits.conf - 영구 반영
이 파일을 이용하면 영구적으로 값을 적용시킬 수 있다.
/etc/security/limits.conf
파일에는 친절하게 주석으로 예시가 들어있다.
#<domain> <type> <item> <value>
# 유저명 soft 또는 hard limit 설정할 항목 설정 값
* hard nofile 65535
* soft nofile 65535
각각의 항목의 의미느 아래와 같다.
domain | * 는 모든 유저를 의미 |
type | hard / soft limit 구분 |
item | nofile : open file |
value | 설정 값 입력 |
- 위와 같이 설정하면 모든 유저에 대해 open files의 hard/soft limit 값을 65535개로 설정하겠다는 것이다.
자 이제 이렇게 설정하고 저장한다.
그럼 이제 쉘을 다시 열거나 서버를 재부팅을 하더라도 여기서 설정한 open file 의 값은 유지가 되는 걸 볼 수 있다.
테스트는 파일로만 했는데, 리눅스에스는 socket도 파일로 취급하여 FD 번호가 지정된다.
네트워크 소켓 연결도 open file 로 카운트를 한다는 것이다.
![](https://t1.daumcdn.net/keditor/emoticon/friends1/large/007.gif)
'개발' 카테고리의 다른 글
AWS ALB(Application Load Balancer) - IP로 웹사이트 접근 막기 (2) | 2023.03.20 |
---|---|
Javascript ES6 (ECMAScript 2015) 에서 추가된 기능 정확히 알자 (0) | 2023.03.20 |
InfluxDB "too many open files" 에러 (0) | 2023.03.17 |
Nuxt + Express 백엔드 API 서버 통합 (0) | 2023.03.16 |
InfluxDB 쿼리 개선 (0) | 2023.03.13 |
댓글