본문 바로가기
  • ANALOG CODE
  • AnalogCode
개발

리눅스 ulimit open files 테스트

by 아날로그코더 2023. 3. 18.
반응형

리눅스에서 하나의 프로세스에서 열 수 있는 파일 갯수에 제한이 있다. 네트워크 서버를 만들거나 파일을 동시에 많이 열어야 하는 프로그램을 만든다면 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 로 카운트를 한다는 것이다.

 

 

 

 

반응형

댓글