Java, max user processes, open files
안녕하세요? 우아한 형제들에서 결제/정산 시스템을 개발하고 있는 이동욱입니다.
올해 사내 블로그 포스팅 주제로 Linux의 open files, max user processes 설정에 대해 정리하게 되었습니다.
계기는 단순했습니다.
팀에서 서버 작업하던 중 쓰레드와 관련해서 문제가 발생했는데요.
제가 진행하던 일이 아니라서 옆에서 해결하는 과정을 지켜봤습니다.
부끄럽게도 전혀 모르는 내용이 오고 갔습니다.
복기가 필요하단 생각에 정리를 진행 하던 중, 이왕 하는김에 회사 블로그에 올리면 좀 더 자세히 공부하지 않을까 하는 마음에 선택하게 되었습니다.
(퀄리티는 에피타이저, 마음만은 메인 디쉬로 가겠습니다!)
여기에서 사용된 코드는 실제 회사에서 사용한 코드는 아니며, 포스팅을 위해 최대한 유사하게 만들어진 별도의 샘플 코드임을 먼저 말씀드립니다.
1. Max user processes
Linux에는 OS 레벨에서의 제한 설정이 있습니다.
보통 이를
(user limit) 이란 명령어로 확인하는데요.ulimit
2가지 옵션으로 대부분 확인합니다.
ulimit -a
- soft ulimit
ulimit -aH
- hard ulimit
톰캣을 이용해서 서버 운영 도중, 다음과 같이
가 발생했다고 가정하겠습니다.OutOfMemoryError
더이상 쓰레드를 생성할 수 없다는 에러인데요.
뭐가 문제였는지 하나씩 확인해보겠습니다.
맨 처음 서버를 할당 받은 초기 상태 그대로라
는 아래와 같습니다.ulimit -a
화면을 보시면
의 값이 1024로 동일하게 잡혀있습니다.open files
` 와
`max user processes
쓰레드 생성에 문제가 발생한거라
가 문제인것 같지만, 확신할 수 없으니 테스트 환경을 구축해서 실험해보겠습니다. max user processes
테스트용 서버는 AWS EC2의 t2.micro입니다.
t2.micro로 생성후
로 확인해보겠습니다.ulimit -a
t2.micro는 기본 설정이
가 3902로 잡혀있다는 것을 알 수 있습니다. open files
`가 1024,
`max user processes
자 그럼 간단하게 추측할 수 있는 것이, 현재 설정에서
가 부족해서 발생한 문제임을 알 수 있겠죠?1024 <= 동시에 생성가능한 쓰레드수 <= 3902
`라면
`max user processes
이를 확인하기 위해 간단한 스프링부트 프로젝트를 생성하겠습니다.
코드는 간단합니다.
으로 HTTP 요청이 오면 비동기로 4천개의 쓰레드를 동시에 생성하고, 20분간 유지합니다./4000
프로젝트를 테스트용 EC2에 배포하고
으로 확인해보겠습니다. curl
`과
`tail -f nohup
사진속을 보시면 3855번째에서
에러 메세지가 발생했습니다.unable to create new native thread
즉, open file 제한인 1024개를 초과해서 쓰레드가 생성된 것입니다!
max user processes만큼만 쓰레드가 생성된것을 확인할 수 있습니다.
2. Open files
두번째로 위에 있던 open files 값은 어떤 값을 가리키는지 알아보겠습니다.
open files에 관해 검색을 해보면 이 값이 프로세스가 가질 수 있는 소켓 포함 파일 개수를 나타낸다는 것을 알 수 있습니다.
자 그럼 소켓을 open files 값 보다 많이 만들면 어떻게 되는지 테스트 해보겠습니다.
테스트 방법은 간단합니다.
- RestTemplate로 다른 서버로 API요청 (소켓 생성)을 보내고, 해당 서버에서는 20분간 응답을 대기 시킵니다.
- 위 요청을 동시에 1100개를 보냅니다.
- open files의 제한이 1024개이기 때문에 1100개가 발송되기전에 open files 관련된 에러가 발생하면 실험 성공!
- Java, Spring을 실행시키면 기본적으로 몇개의 file이 오픈됩니다.
- 이를 계산하면서 하기가 애매하니, 1100개를 보내면 기본 open된 파일 + 추가 생성된 파일을 합쳐서 1024개가 넘어가는 시점에 에러가 발생한다는 계산입니다.
API 요청을 받아, 20분간 대기시켜줄 서버로 ec2를 한대 더 생성합니다.
테스트에 사용한 코드는 다음과 같습니다.
요청을 보낼 메소드
- RestTemplate 타임아웃이 먼저 나면 안되기 때문에 타임아웃을 30분으로 지정합니다.
요청을 받을 메소드
자 그리고 동시에 1100개의 요청을 보낼수 있도록 간단한 비동기 요청 스크립트를 만듭니다.
(실제 운영환경에선 Ngrinder등을 통해서 하겠지만, 여기선 간단한 테스트이니 스크립트로 대체합니다.)
준비가 다 되었으니 한번 테스트를 진행해보겠습니다!
…..
테스트를 진행하자마자 바로 에러가 발생했습니다.
로그를 확인해보니
단순 쓰레드 생성과 달리, EC2의 서버 메모리가 먼저 부족해져서 EC2 사양을 높여서 다시 실험하겠습니다.
(t2.micro는 메모리가 1GB인지라, t2.large (8GB) 로 변경합니다.)
업데이트된 EC2의
은 아래와 같습니다.ulimit
AWS EC2의 사양을 올릴수록
가 적절한 값으로 증가하는 것을 확인할 수 있습니다.
max user processes
이를 통해 알 수 있는 것은, AWS EC2를 사용하실 경우AWS 내부에서 인스턴스 사양에 맞게 적절한 값을 세팅해주니, 굳이 저희가 손댈필요는 없다는 것입니다.
max user processes
자 그럼 다시 한번 테스트를 해보겠습니다.
테스트용 서버에 프로젝트를 배포하고, 해당 프로젝트가 생성한 open file count를 아래 명령어로 확인합니다.
ls -l /proc/$PID/fd | wc -l
PID를 찾고,
생성된 open file 리스트를 확인할 수 있습니다.
준비가 다 되었으니, 1100개 요청을 보내는 스크립트를 실행해봅니다!
엇?
결과가 뭔가 이상합니다.
분명
의 값은 1024개로 되어있는데 1330개가 열려있다고 나오다니요. open files
이상하다는 생각에 스크립트를 몇번 더 실행합니다.
(한번 실행때마다 1100개의 요청이 간다고 생각하시면 됩니다.)
2번을 추가로 더 실행해서 3300개의 요청이 갔음에도 에러없이 처리되고 있습니다.
언제 터지는지 확인하기 위해 추가로 더 요청해봅니다 (총 4400개가 갑니다.)
드디어 Open File 에러가 발생했습니다!
보시면 4097개까지만 열린채로 에러가 발생한 것을 알 수 있는데요.
이전 요청이 3532개를 생성했으니 추가로 1100개를 요청하면 4600개 이상이 생성되어야하는데 왜 4097개까지만 생성된 것인지 궁금합니다.
혹시나 하는 마음에
로 soft가 아닌, hard옵션을 확인해봅니다.ulimit -aH
오?
마침 딱 4096개 입니다!
프로세스별 open file은 soft 값이 아닌 hard 값까지 생성가능한게 아닐까? 라는 추측이 됩니다.
검증하기 위해 hard의 open files를 5120개로 증가시킵니다.
(soft 옵션은 그대로 1024개 입니다.)
자 그리고 다시 요청을 진행해보겠습니다.
이번에 4096개가 넘는 요청이 가능해진다면 soft가 아닌, hard 값까지 생성 가능하다는걸 알수있겠죠?
예상대로 4000개가 넘는 API 요청 (4635)이 가능해졌습니다!
실제로
로 확인할 수 있는 soft 값으로 소켓 생성이 제한되지는 않는다는 것을 알 수 있습니다.ulimit -a
결국 소켓 생성 제한은 hard 옵션에 따라간다 라는 이야기가 됩니다….
이 글을 마무리하려던중, 깜짝 놀랄 이야기를 들었습니다.
파이썬은 안그러는데요?
추가
제보를 해주신 배민찬의 모 선임님과 함께 확인을 해봤습니다.
soft limit으로 1024, hard limit으로 4096인걸 확인하고, 파이썬 스크립트로 file을 임의로 열어봅니다.
여기서 3개를 빼야하는 이유는 stdin, stdout, stderr의 표준 입/출력이 포함됐기 때문입니다.
1021개에서 1개를 더 추가하니 바로
Error가 발생합니다!Too many open files
헉? soft 옵션이 파이썬에서 잘 적용된걸까요?
진짜 그런지 한번 더 확인해봅니다.
open files soft 값을 2000으로 증가시킨후 다시 1997개가 넘는 file을 open 해봅니다.
여기서도 마찬가지로 1997개 이상 file open시에 바로
Error가 발생합니다. Too many open files
왜 파이썬은 soft옵션까지만 file이 오픈되고, Java에선 hard 옵션까지 file이 오픈되는건지 이상했습니다.
이상하단 생각에
로 JVM 로그를 확인해보니!strace
이렇게
으로 limit을 업데이트하는 로그가 찍혀있습니다! setrlimit
왜 이런 로그가 발생했는지 오라클 Java 옵션을 찾아봤습니다.
문서에는
라는 옵션이 있었는데요, 뭔가 file limit과 관련돼 보입니다. MaxFDLimit
이 옵션이 뭔지 찾아보니 openjdk 코드에서 이 옵션이 true일 경우
으로 limit을 증가시키는 것을 확인할 수 있습니다.setrlimit
그리고 설치된 Java의
기본값이 true임을 확인할 수 있습니다.MaxFDLimit
즉, 리눅스 OS 환경에서는 JDK 실행시 자동으로 limit 사이즈를 증가시켜준다는 것을 알 수 있습니다.
결론
위 2개의 실험으로 얻은 결론입니다.
- Java에서 동시에 생성 가능한 쓰레드 수는
를 따라간다.max user processes
- Java에서 소켓 통신(HTTP API, JDBC 커넥션 등)은
옵션을 따라간다.open file
- 단, JDK 내부 코드상에서 hard limit 값이 soft limit에 update된다.
참고로 Tomcat은 8 버전부터 기본 Connector 방식을 NIO로 사용합니다.
(7 버전까지는 BIO)
그러다보니 maxConnections은 10,000, maxThreads는 200이 기본값입니다.
(BIO에서는 둘의 값이 동일해야 합니다)
이번 테스트에서 사용되는 connection 수가 1만을 넘지 않기 때문에 기본옵션으로 진행했습니다.
번외) limit 옵션 설정방법
보통 soft limit과 hard limit을 다르게 설정하진 않습니다.
둘의 값을 동일하게 적용하는데요.
서버의 open files, max user processes등 옵션을 permanent (영구) 적용하기 위해선
을 수정하면 됩니다./etc/security/limits.conf
맨 앞의
가 있는 자리는 사용자 계정을 나타냅니다.*
ec2-user로 지정하시면 ec2-user 계정에서만 옵션이 적용되는데요.
이미지처럼
로 지정하시면 모든 사용자 계정에 옵션이 적용됩니다. *
이렇게 적용후, 다시 접속해서 확인해보시면!
옵션이 잘 적용되어있음을 확인할 수 있습니다.
마무리
제가 준비한 내용은 여기까지입니다.
좋은 기회로 그 동안 막연하게 알고 있던
를 제대로 정리해볼 수 있었습니다. max user processes
`,
`open file
부족함이 많은 글임에도 끝까지 읽어주셔서 감사합니다.
다음에도 이와 같이 기본적인 내용이지만, 프로젝트에서 도움을 받는 일이 발생한다면 잘 정리해서 공유드리겠습니다.
그럼, 다음에 또 뵙겠습니다.
감사합니다!
(사용된 모든 짤은 레진코믹스의 레바툰입니다.)