지난 글에 이어서 2편도 스터디하면서 정리했다.
리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 - 2편
리눅스를 비롯한 유닉스에서 소켓은 파일처럼 취급한다.
시스템 전체에서 가질 수 있는 최대 파일의 갯수가 정해져 있으면, 소켓의 갯수에도 영향을 미칠 것이다. (소켓 = 파일 처럼 생각하니까)
fs.file-max 파라미터로 리눅스에서 가질 수 있는 최대 파일의 갯수를 지정할 수 있다.
$ sysctl fs.file-max
fs.file-max = 775052
그런데 이 값은 왠만하면 크게 잡혀있어서 운영 중 문제가 생길 여지는 크지 않다.
만약 이 파라미터 값이 충분치 않을 경우 Too many open files 라는 에러가 발생한다.
오픈 파일 갯수에 대한 주요 파라미터는 fs.file-max보다는 프로세스 단에서 파일 갯수를 제한하는 ulimit 설정이다.
아래 설정값 중 open files 파라미터 값이 각 프로세스가 열 수 있는 파일(소켓 포함) 갯수이다.
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 30473
max locked memory (kbytes, -l) 64
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) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 30473
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
ulimit 설정 방법은 전에 Shell Limits 파라미터 설정 글에서 살펴본 바 있다.
내 나름대로는 fs.file-max와 open files 파라미터의 관계를 아래 그림처럼 이해했다.
+ fs.file-nr 파라미터는 현재 오픈된 파일 현황을 확인하는 값이다.
$ sysctl fs.file-nr
fs.file-nr = 5024 0 775052
순서대로 '현재 오픈된 파일 수' / '오픈 된 파일 중 사용하지 않는 파일 수' / '오픈 가능 파일 수' 를 의미한다.
대기하는 queue의 크기를 설정하는 파라미터로 생각하면 된다.
서버 커널 설정값에 있어 inbound queue가 outbound queue보다 중요하다.
outbound queue는 애플리케이션 단에서 조절하여 보낼 수 있지만,
inbound queue가 너무 작아서 채 대기열에 놓지 못하고 버려진 요청은 서버 입장에서 다시 알아낼 방법이 없다.
따라서 대규모 패킷 처리가 필요한 서버에서는 반드시 inbound queue 설정이 필요하다.
net.core.netdev_max_backlog
각 네트워크 장치 별로 커널이 처리하도록 쌓아두는 queue의 크기를 설정하는 파라미터
메모리 사용량만 trade-off 관계이므로 적당히 꽤 늘려두는 것도 좋다.
$ sysctl -w net.core.netdev_max_backlog="30000"
이 backlogs 부분은 이해가 잘 되지 않았다.
아래 내용 중 제가 잘못 이해한 부분이 있다면 알려주시면 감사하겠습니다.
somaxconn
listen 상태의 목적지 포트로 가기 위해 클라이언트 소켓은 accept()가 호출되기를 기다린다.
그때 클라이언트 소켓이 대기하는 queue의 크기를 설정하는 값
queue 값이 너무 작으면 클라이언트 요청이 accept()를 기다리다가 drop 되어버린다. 꽤 크게 주는 것이 좋을 것 같다.
$ sysctl -w net.core.somaxconn="10000"
TCP 연결은 출발지(source) 주소, 출발지 포트, 목적지(destination) 주소, 목적지 포트를 그 구분자로 한다.
클라이언트가 서버에 연결할 때 특별히 지정하지 않으면 랜덤한 포트로 나가서 목적지 서버 포트에 도달한다.
이런 포트를 ephemeral port라고 한다.
즉 클라이언트 소켓은 TCP연결을 맺을 때 반드시 하나의 포트 소모하며, 포트는 유한한 자원이기 때문에 동시에 유지할 수 있는 클라이언트 소켓 역시 제한적이다.
다만, 서버 측은 TCP연결을 맺을 때 추가적인 포트를 소모하지 않는다. 목적지 서버 포트 하나만 열어주면 된다.
그래서 일반적으로 서버 포트와 클라이언트 연결 숫자는 크게 문제되지 않는다.
그러나 Proxy 서버는 이야기가 다르다.
Proxy 서버가 가지는 클라이언트 소켓(목적지로 나가기 위한 포트)이 100개 밖에 존재하지 않는다고 가정해보자.
사용자로부터 10000개의 요청이 들어왔을 때, 100개의 요청만 백엔드 서버로 전달될 것이며 나머지 9900개의 요청은 대기 상태에 머무른다.
net.ipv4.ip_local_port_range
어떤 시스템에 동시에 가질 수 있는 클라이언트 소켓 수를 결정하는 커널 파라미터
커널은 ephemeral port를 생성할 때 이 범위내 사용하지 않는 포트를 골라 할당한다.
위의 경우에는 net.ipv4.ip_local_port_range = 100으로 설정된 값을, 수정해야 할 것이다.
$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 61000
연속된 두 숫자는 ephemeral port의 시작과 끝을 나타낸다.
최대한 넓은 ephemeral port 범위를 가지려면 아래처럼 할 수 있다.
$ sysctl -w net.ipv4.ip_local_port_range="1024 65535"
우리 시스템에도 proxy서버가 존재하는데, 이 서버에는 net.ipv4.ip_local_port_range 파라미터 설정을 특히 유의해야겠다.