Site icon 지락문화예술공작단

리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 – 2편

출처 : http://meetup.toast.com/posts/54

본 내용은 NHN엔터테인먼트의 정성환 님께서 작성하신 내용입니다.

연재

리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 – 1편
리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 – 3편

목차 – 2편

4. 네트워크 capacity 관련 파라미터
    4.1 maximum file count
    4.2 backlogs
    4.3 port range

4. 네트워크 capacity 관련 파라미터

4.1 maximum file count

리눅스를 비롯한 일반적인 유닉스에서 소켓은 마치 파일과 같은 취급을 받습니다.
전체 시스템에서 가질 수 있는 파일 개수가 제한이 있다면, 당연히 소켓의 전체 개수에 영향 미칠 것 입니다.

리눅스에서 전체 시스템이 가질 수 있는 최대 파일 개수 제한은 ’fs.file-max’ 커널 파라미터에서 설정 됩니다.

현재 설정값을 확인하려면, 아래와 같은 명령어를 사용합니다.

$ sysctl fs.file-max
fs.file-max = 775052

이 값은 일반적으로 적당히 큰 값이 설정되어 있으므로, 웬만하면 손 볼 일이 없을 것입니다.
다만, 시스템이 굉장히 많은 파일과 소켓을 사용하는 경우, 이 값에 의해 시스템이 오동작 할 수 있으니 참고바랍니다.
(이 값을 넘어가면 open() 시스템 콜에서 ’Too many open files’와 같은 에러가 발생 될 것 입니다.)

다시 말하자면, 시스템 전체에 대한 허용 소켓 개수는 ’fs-file-max’ 커널 파라미터 설정값이 적당히 높게 설정되어 있으므로 큰 문제가 안됩니다.
사실, 어떤 프로세스가 가질 수 있는 소켓 개수 제약은 그보다는 프로세스별 제한 설정인 user limit 값을 살펴봐야 할 것 입니다.
다음과 같은 명령어로 이를 확인할 수 있습니다.

$ 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

여기서 open files가 프로세스가 가질 수 있는 소켓 포함 파일 개수입니다.
적당량 증가시키기 위해서는 다음과 같은 명령어를 사용합니다.

$ ulimit -SHn 65535

많은 개수의 소켓을 사용하는 서버 프로그램은 구동하기 전, ulimit 명령어로 프로세스 당 최대 파일 개수를 증가시켜주어야 할 것 입니다.
(혹은, 해당 애플리케이션 로직내에서 setrlimit() 시스템 콜로 이를 증가시키는 방법도 있습니다.)

fs.file-max’와 유사한 이름의 ’fs.file-nr’이라는 커널 파라미터가 있는데, 사실 이 파라미터는 일반적인 파라미터가 아니라 현재 열려 있는 파일 현황을 나타냅니다.
아래와 같은 명령어로 현재 현황을 알 수 있습니다.

$ sysctl fs.file-nr
fs.file-nr = 5024    0    775052

세 값은 각각 현재 열려 있는 파일의 수, 현재 열려 있으나 사용되지 않는 파일의 수, 열 수 있는 파일의 최대 개수를 뜻합니다. 물론 시스템 전체에 대한 수치입니다.

4.2 backlogs

네트워크 패킷은 그 생성가 전달, 그리고 소모에 이르기까지 많은 처리 과정을 거치게 됩니다. 각각의 처리 과정을 파이프라고 본다면, 모든 처리 과정 앞에는 각각 queue가 존재한다고 할 수 있을 것 입니다. 네트워크 패킷 처리량이 갑자기 급증했을 때, 이 queue의 크기가 이보다 작다면 넘치는 패킷에 대해서는 처리되지 않고 버려질 것 입니다.

서버 커널 설정값에 있어 out-bound queue 보다는 in-bound queue가 더 민감한데, 왜냐하면 out-bound로 보내지는 패킷량은 서버 애플리케이션에서 조절할 수 있기 때문입니다. (각각의 요청은 그 처리 시간이 다르기에 out-bound시 적당히 랜덤하게 분배되는 효과도 있습니다.)
또, in-bound queue가 넘처서 버려지는 패킷은 애플리케이션에서 전혀 알 수 없기 때문에 대규모 패킷 처리가 필요한 서버에서는 적당히 in-bound queue 길이를 증가시켜야 합니다.

먼저 ’net.core.netdev_max_backlog’ 커널 파라미터에 대해 알아봅시다.
이 파라미터는 각 네트워크 장치 별로 커널이 처리하도록 쌓아두는 queue의 크기를 설정합니다. 커널의 패킷 처리 속도가 이 queue에 추가되는 패킷의 인입 속도보다 떨어진다면 미처 queue에 추가되지 못한 패킷들은 버려질 것입니다.

이 커널 파라미터도 trade-off 관계가 메모리 사용량 밖에 없으므로, 적당히 증가시켜두는 것도 괜찮습니다.
다음과 같은 명령어로 설정값을 적당히 증가 시킬 수 있습니다.

$ sysctl -w net.core.netdev_max_backlog="30000"

구글에서 찾을 수 있는 몇 커널 파리미터 튜닝 관련 글에는, 이 커널 파라미터가 listen backlog라고 잘못 소개되기도 하는 것 같습니다.
listen backlog, 즉 listen()으로 바인딩 된 서버 소켓에서 accept()를 기다리는 소켓 개수에 관련된 커널 파라미터는 ’net.core.somaxconn’입니다.
이 값은 listen() 시스템 콜의 매개변수로 설정하는 backlog 값의 hard limit입니다. 서버 애플리케이션에서 listen()시 적당히 설정해야겠지만, 먼저 이 hard limit을 증가시켜야 할 것입니다.

다음과 같은 명령어로 이 설정값을 증가 시킬 수 있습니다.

$ sysctl -w net.core.somaxconn="1024"

참고로, 일반적인 리눅스 배포판의 기본 설정값은 128입니다. 그런데, 아파치 웹 서버의 경우 listen()시 지정되는 backlog의 기본값이 511입니다. 하지만 이 커널 파라미터가 hard limit이기에 애플리케이션에서 511으로 지정되었더라도 실제로 할당되는 listen backlog의 수는 128개가 될 것 입니다.

또, 'net.ipv4.tcp_max_syn_backlog’라는 listen backlog와 연관된 커널 파라미터가 있습니다. ’net.core.somaxconn’이 accept()을 기다리는 ESTABLISHED 상태의 소켓(즉, connection completed)을 위한 queue라면, ’net.ipv4.tcp_max_syn_backlog’는 SYN_RECEIVED 상태의 소켓(즉, connection incompleted)을 위한 queue입니다.

이 설정값도 아래와 같이 적당히 증가 시킵니다.

$ sysctl -w net.ipv4.tcp_max_syn_backlog="1024"

한가지 유의할 점은 커널 파리미터 설정값을 상향하더라도, 실제 서버 애플리케이션에서 listen backlog를 증가시키려면 listen() 시스템 콜 호출시 매개변수 backlog에 필요한 값을 전달해야 합니다.

4.3 port range

TCP 연결을 맺을때, 클라이언트 소켓은 하나의 포트를 선점해야 합니다. TCP 연결은 출발지(source) 주소, 출발지 포트, 목적지(destination) 주소, 목적지 포트를 그 구분자로 하기 있기 때문입니다. 클라이언트에서 서버로 연결을 맺을때, 특별히 bind() 시스템 콜로 출발지 포트를 지정(bind)하지 않는다면, 커널은 임의의 포트를 할당합니다.
그리고 이러한 포트를 ephemeral port라고 통칭합니다.

즉, 클라이언트 소켓은 서버에 연결을 맺을때 포트라는 자원을 하나 소모하며, 포트는 유한한 자원이기에 한 시스템에서 동시에 가질 수 있는 클라이언트 소켓의 수는 한정적입니다.

반대로, listen()으로 클라이언트의 요청을 기다리고 있는 서버 포트는 TCP 연결을 맺을 때 추가적인 포트를 소모하지 않습니다.
때문에 일반적인 서버에서는 가질 수 있는 포트 수와 서버의 클라이언트 동시 연결 수는 크게 관계가 없습니다.

다만, 서버의 유형 중 proxy 서버에 대해서 생각해 볼 필요가 있습니다.
사용자(클라이언트)로 부터 요청을 받아, 이를 다른 백엔드 서버에 전달하는 유형의 서버를 말하는데요. 이 경우 해당 서버는 다른 백엔드 서버에 연결하기 위한 클라이언트 소켓이 필요합니다.
만약, 사용자 요청이 동시에 10,000개 들어오는데 해당 서버가 가질 수 있는 클라이언트 소켓 수가 동시에 100개라면 9,900개의 요청은 처리되지 못하고 대기해야 할 것 입니다.

어떤 시스템에 동시에 가질 수 있는 클라이언트 소켓 수를 결정하는 커널 파라미터는 ’net.ipv4.ip_local_port_range’ 입니다.
커널은 ephemeral port를 생성할 때 이 범위내 사용하지 않는 포트를 골라 할당하게 됩니다.

다음과 같은 명령어를 통해 현재 설정값을 확인할 수 있습니다.

$ sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768    61000

위에서 두 값은 각각 사용할 포트 범위의 시작과 끝을 나타냅니다. 이 시스템에서는 최대 28,232개의 ephemeral port를 할당할 수 있습니다.
최대한 넓은 ephemeral port 범위를 가지려면 아래와 같은 명령어를 사용할 수 있습니다. (well-known port는 제외)

$ sysctl -w net.ipv4.ip_local_port_range="1024 65535"

그런데, 이렇게 설정하더라도 실제 시스템에서 동시에 가질 수 있는 클라이언트 소켓 수는 이에 미치지 못할 수 있습니다.

TCP 연결은 굉장히 결합도가 낮은 네트워크 환경을 가정하고 있습니다. 때문에 네트워크 상황에 의해 패킷 순서가 뒤바뀌거나 유실 되는 등의 처리를 위해, 소켓 종료시에도 되도록 우아하게 종료(gracefully shutdown)하도록 되어 있는데요. 이 얘기인 즉슨, 소켓이 사용하는 자원을 되도록 늦게 반환한다는 것입니다. ephemeral port를 포함해서요.

특히, 클라이언트 소켓에서 close() 시스템 콜로 먼저 소켓을 닫는 경우 소켓은 TIME_WAIT 상태에 머무르게 됩니다.
이 동안 이 소켓에 할당되어 있는 ephemeral port는 사용될 수 없고 그만큼 동시에 가질 수 있는 클라이언트 소켓 수는 제한되겠죠.


_3편에서는 ’5. TIMEWAIT socket’ 로 시작하여 이번 이야기를 마무리 하겠습니다.

Exit mobile version