마지막 3번째 글도 스터디하면서 정리했다.
아직 TCP 통신의 handshake에 대한 이해가 부족하다고 느꼈다.
아래처럼 직접 그려보면서 복습해야겠다.
TIME_WAIT 상태를 이해하기 위한 사전지식이 있다.
https://luyin.tistory.com/424
https://docs.likejazz.com/time-wait/
위 개념은 클라이언트, 서버 개념이 아니다.
서버도 Active Close가 될 수 있고, 클라이언트도 Active Close가 될 수도 있다.
누가 통신을 먼저 끊자고 요청하느냐에 달린 것이다.
TIME_WAIT는 TCP 상태의 가장 마지막 단계이고, 통신 종료를 요청한 Active Close의 상태이다.
보통 2분동안 TIME_WAIT 상태에 머무르게 된다.
TIME_WAIT 상태는 일종의 종료 대기시간이다.
TIME_WAIT이 짧거나 없을 경우,, 이런 일이 발생할 수 있다.
위와 같은 이유들로 TIME_WAIT 상태는 잘못된 상태가 아니라, gracefully shutdown을 위해 필요한 상태이다.
현재 시스템에 TIME_WAIT 상태의 소켓을 확인하는 명령어는 이렇다.
$ netstat -n -t | grep 'TIME_WAIT'
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp6 0 0 10.77.57.106:10080 10.77.94.26:37901 TIME_WAIT
tcp6 0 0 10.77.57.106:10080 10.77.94.26:37900 TIME_WAIT
tcp6 0 0 10.77.57.106:10080 10.77.94.26:37905 TIME_WAIT
tcp6 0 0 10.77.57.106:10080 10.77.56.86:41811 TIME_WAIT
tcp6 0 0 10.77.57.106:10080 10.77.56.82:48168 TIME_WAIT
어떤 시스템이 가질 수 있는 TIME_WAIT 상태 소켓 개수 제한하는 파라미터 확인
$ sysctl net.ipv4.tcp_max_tw_buckets
net.ipv4.tcp_max_tw_buckets = 65536
이 설정대로라면 TIME_WAIT 상태의 소켓이 최대 65536개 존재할 수 있다는 의미이다.
만약 설정 최대값보다 더 많은 소켓이 TIME_WAIT 상태에 빠지게 된다면, 그 소켓은 즉시 파괴되어 버린다.
파괴되더라도 서버 시스템 자체에 큰 영향을 주는 것은 아니나, 데이터를 유실할 수 있기 때문에 굳이 이런 상황을 만들 이유가 없다.
소켓은 메모리와 trade-off 관계이기 때문에 메모리가 넉넉한 서버에서는 적당히 크게 설정해주어도 큰 문제 없다.
$ sysctl -w net.ipv4.tcp_max_tw_buckets="1800000"
이 블로그 작성자 형님은 직접 테스트를 하면서 net.ipv4.tcp_tw_reuse 파라미터가 무엇인지 보여주셨다.
일단, client 와 server를 준비한다.
그리고 client에는 net.ipv4.ip_local_port_range 파라미터를 32768 32768로 수정했다.
(outbound 포트를 딱 1개만 사용하도록 만든 것)
실험 1.
준비가 끝났으면 client가 server의 웹서버 페이지를 호출한다.
웹 페이지를 호출한 client의 32768 포트는 TIME_WAIT 상태가 된다.
TIME_WAIT 상태를 확인한 후 연결을 끊었다가, 즉시 동일한 호출을 다시 시도한다.
'Fail to connect 192.xx.xx.xx 요청한 주소를 배정할 수 없습니다' 라는 에러가 발생한다.
사용할 수 있는 모든 로컬 포트(1개)가 TIME_WAIT 상태이기 때문에, 더이상 쓸 로컬 포트가 없기 때문에 발생하는 것이다.
실험 2.
동일한 client에서 이번에는 net.ipv4.tcp_tw_reuse 파라미터를 1(enable)로 설정했다.
마찬가지로 실험 1과 동일하게 server의 웹 페이지를 호출했으나, 이번에는 에러가 발생하지 않았다.
tcp_tw_reuse를 enable로 설정해서 TIME_WAIT 상태의 소켓(1개)을 여러번 재사용 할 수 있게 되었기 때문이다.
결론
위의 실험에서 확인할 수 있는 결론은 이렇다.
tcp_tw_reuse 파라미터는 outbound 트래픽에 대해 로컬 포트가 모자랄 경우, TIME_WAIT 상태의 소켓을 재사용할 수 있게 해준다.
팁
그리고 만약 로컬 포트 고갈 현상이 발생할 경우 ip_local_port_range 값을 늘리기 보다는 tcp_tw_reuse 파라미터를 활성화시켜주는 것이 더 낫다고 한다.
굳이 TIME_WAIT 상태로 머물면서 메모리를 차지할 소켓을 늘리는 것 보다는, 소켓을 재사용하는 편이 시스템 리소스를 덜 사용하게 되어서 그런 것 같다.
$ sysctl -w net.ipv4.tcp_window_scaling="1"
$ sysctl -w net.core.rmem_default="253952"
$ sysctl -w net.core.wmem_default="253952"
$ sysctl -w net.core.rmem_max="16777216"
$ sysctl -w net.core.wmem_max="16777216"
$ sysctl -w net.ipv4.tcp_rmem="253952 253952 16777216"
$ sysctl -w net.ipv4.tcp_wmem="253952 253952 16777216"
$ sysctl -w net.core.netdev_max_backlog="30000"
$ sysctl -w net.core.somaxconn="1024"
$ sysctl -w net.ipv4.tcp_max_syn_backlog="1024"
$ ulimit -SHn 65535
TW_RECYCLE 옵션은 NAT 환경에서 문제가 있고, socket linger 옵션은 데이터 유실이 있을 수 있으니 사용하지 말자.
$ sysctl -w net.ipv4.tcp_max_tw_buckets="1800000"
$ sysctl -w ipv4.tcp_timestamps="1"
$ sysctl -w net.ipv4.tcp_tw_reuse="1"
모든 상황에 적용 가능한 만능 파라미터 값이라는 것은 없다고 한다.
정밀한 튜닝을 하기 위해서는 애플리케이션을 자세히 파봐야 한다.