기본 콘텐츠로 건너뛰기

무엇이 다른가

TCP KeepAlive (퍼옴)

개요: TCP Keepalive 해설
세부 설명:


Sun Microsystems는 TCP keepalive 매개 변수(tcp_keepalive_interval)를 15분 미만으로 설정하는 것은 바람직하지 않다고 봅니다.
이제 TCP keepalive를 설명한 후에, 왜 그것이 바람직하지 않은지 몇 가지 이유를 설명할 것입니다.

우선, keepalive가 TCP의 필수 요소가 아니라는 점을 말하고 싶습니다. keepalive는 TCP 규격에 나오는 옵션 기능의 하나이며, 제조업체의 재량에 따라 포함시킬 수 있습니다. Sun은 TCP에 이 기능을 포함시키기로 결정했습니다. 하지만 TCP 규격에서는 keepalive 기능을 포함시키는 경우 간격을 최소한 2시간 이상으로 기본 설정해야 한다고 규정합니다. 뿐만 아니라 이 기능을 옵션 기능으로 만들어야 합니다. 바로 그 점 때문에 TCP keepalive를 설정할 것인지 여부를 각 프로그램에게 맡기게 되는 것입니다.
프로그램이 TCP keepalive를 명시적으로 실행하지 않으면, 탐색 패킷(probe)을 보내지 않을 것입니다. TCP keepalive는 setsockopt()을 사용하여 소켓 옵션(SO_KEEPALIVE)을 설정하면 사용할 수 있게 됩니다.

소켓 옵션이 설정되면 tcp_keepalive_interval로 지정된 시간 동안 연결이 유휴 상태가 되었을 때 keepalive 탐색 패킷을 보냅니다.
응답 메시지가 수신될 때까지 또는 tcp_ip_abort_interval로 지정된 시간이 다 경과할 때까지 탐색 패킷을 보냅니다. 응답은 연결 상대측을 지연시키는 요소의 영향을 받습니다. 연결 상대측이 연결을 닫거나 다시 부팅을 하면 응답 메시지는 RST(reset packet)가 됩니다. 수신 주소에 도달할 수 없다는 ICMP 메시지를 수신하게 될 가능성도 있습니다. 라우터가 고장나거나 케이블 연결이 끊긴 경우에 그런 상황이 발생합니다. 그 외에도 많은 가능한 상황이 있습니다. 탐색 패킷 자체는 tcp_rexmit_interval로 지정된 간격으로 보내집니다.

keepalive를 15분 미만으로 설정해서는 안된다고 제안하는 이유 중의 하나는 바로 이것입니다. 그렇게 설정하면 TCP가 장애를 일으킬 가능성이 다분히 있습니다. tcp_rexmit_interval의 값은 3초로 기본 설정됩니다. 20초 정도로 높게 설정할 수도 있습니다. 그런데, tcp_keepalive_interval을 tcp_rexmit_interval 보다 작은 값으로 줄이면, 재전송하기 전에 keepalive 탐색 패킷을 보낼 것입니다. 하지만, 네트워크가 느리거나 팻 상태가 되면 재전송이 매우 중요합니다. 어쩌면 통신 상대측 시스템이 느려서 아직 응답하지 않은 것일 수도 있습니다. 재전송을 보내는 이유가 바로 이것입니다. 이것은 누군가에게 조금 전에 내가 한 말을 들었느냐고 묻는 것과 같습니다. 상대방이 없다고 판단되면 대화를 미리 중단하거나 상대방이 있는지 알아 보려고 시간을 낭비하게 될 것입니다. 상대측이 여전히 대화에 참여하고 있다면 그에게 직접 조금 전에 내가 한 말을 들었느냐고 다시 묻게 될 것입니다. 이렇게 되면 네트워크가 팻 상태가 됩니다. 네트워크가 팻 상태가 되면 될수록 제대로 작동이 되려면 재전송을 더 많이 해야 합니다. 그렇게 하여 TCP가 장애를 일으키게 됩니다.
(이제 tcp_rexmit_interval가 3개의 매개 변수, 즉 tcp_rexmit_interval_initial, tcp_rexmit_interval_max, 그리고 tcp_rexmit_interval_min로 나누어져 있다는 점을 지적해야겠습니다. 이 점에 대한 해설은 keepalive 해설에서 벗어난 것입니다. 여기서 사용하는 예에서는 주로 tcp_rexmit_interval_initial을 다룰 것입니다.)

텔넷의 경우에는 더 인상적입니다. 시스템에 텔넷 방식으로 연결되면 로그인을 하여 필요한 모든 작업을 할 수 있습니다. 하지만, 종종 작업을 멈추고 생각하는 시간도 있습니다. 그렇게 생각하는 동안 원격 호스트는 대기합니다. 그렇게 기다리는 동안에는 keepalive 탐색 패킷이 도착할 때까지 로컬 시스템과 원격 시스템 사이에 전송되는 패킷이 전혀 없습니다.
원격 호스트로 로그인한 다음에 10분 이상 생각에 잠기는 경우도 종종 있습니다. 그 10분 간격 동안 라우터가 다운이 되어 다시 부팅하게 되는 일도 쉽게 일어납니다. keepalive를 높은 값으로 설정하면 라우터 충돌을 느끼지 못하고 작업을 계속하게 될 것입니다.
하지만 1분 후에 keepalive 탐색 패킷를 보내면, 라우터는 다시 부팅할 시간이 없기 때문에 연결된 측이 준비가 되기 전에 연결이 닫히게 됩니다. 그러면 다시 로그인해야 합니다. 뿐만 아니라, 일단 연결이 닫히면 TIME_WAIT 상태로 유지된다는 점을 생각해야 합니다. TIME_WAIT 상태는 한쪽 호스트가 연결을 닫았는데, 늦게 도착한 패킷 때문에 다른 한 호스트에서는 연결을 계속 열어 두게 되는 상황을 방지하기 위해 사용합니다. 하지만, 이처럼 너무 일찍 연결을 종료시키면, 시스템에서 이용할 수 있는 모든 소켓이 닫히게 될 가능성이 있습니다. 그렇게 되면 전혀 연결이 되지 않을 것입니다. 이와 같은 시나리오에서는 15분이 훨씬 더 타당한 값입니다.

keepalive를 사용하는 주된 이유는 종단 시스템 중의 하나가 다운될 때 발생할 수 있는 한쪽만 열린 연결 상태를 정리하는 것입니다. 로컬 시스템이 대화를 하고 있는 원격 시스템이 다운이 되면, 로컬 시스템은 여전히 연결을 열어두고 있을 것입니다. 하지만, 다운된 그 시스템은 그렇지 않습니다. 이것을 한쪽만 열린 연결 상태라고 합니다. 네트워크 응용 프로그램이 시간 종료값이나 TCP keepalive 소켓 옵션을 설정하지 않는 한, 그 한쪽만 열린 연결 상태는 시스템이 다시 부팅할 때까지 그대로 유지됩니다. keepalive 탐색 패킷은 한쪽만 열린 연결 상태인지 확인하는데 사용되며, 한쪽만 열린 연결 상태이면 그 연결을 닫습니다.

이상의 내용은 몇 가지 상황에 대한 해설에 불과합니다. 분명히 사용자의 현재 상황에 훨씬 더 어울리는 시나리오가 더 많이 있을 것입니다. 기본적으로 여기서 말한 내용은 TCP 규격 자체가 keepalive의 기본 설정값이 최소한 두 시간이어야 한다고 규정한다는 점입니다. 더 나아가 Sun이 제안하는 요점은 TCP가 장애를 일으켜 네트워크를 팻 상태로 만드는 것을 방지하는 데 있습니다.
이것은 사용자가 수정하는 시스템과 네트워크의 다른 호스트에 그대로 적용됩니다. 문제의 네트워크에 전세계적인 인터넷이 포함되면 좀더 극적이 되는 것일 뿐입니다. 마지막 예에서는 keepalive를 다소 낮게 설정했을 때 발생하는 문제의 유형을 사용자 수준에서 설명합니다.

여기서 제안된 내용은 기술 지원과 개발 엔지니어링 분야에서 그리고 Sun Microsystems 전체에서 TCP를 다루는 작업을 하는 엔지니어들에게서 장기간에 걸쳐 보완된 것입니다.

TCP keepalive에 관하여 좀더 알고 싶다면 RFC 1122와 RFC 1123을 권합니다. Addison Wesley에서 발행한 책인 "TCP/IP Illustrated Volume I", ISBN: 0-201-63346-9의 필자인 Stevens도 멋지게 설명합니다.


제품 영역: Gen. Network
제품: TCP/IP
SUNOS 릴리즈: 해당 없음
하드웨어: 해당 없음

댓글

이 블로그의 인기 게시물

SQLite에서 파일 크기 줄이기

간단한 개인 프로젝트를 하고 있는데, SQLite DB파일 크기가 매우 커져서 테이블에 필요 없는 레코드를 날렸다. 그런데 날리고도 파일크기가 그대로라서 여기저기 뒤져보니 VACUUM 커맨드를 사용하란다. 사용법은 매우 간단하다. 그저 "VACUUM;"이라고 날려주면 동작한다. (참조: http://sqlite.org/lang_vacuum.html ) 다만, 동작이 매우 느려서 자주 쓸만한 것은 아니다. 실제로 100MB짜리 파일을 7KB로 줄이는데 수 분이 걸렸다. 소스를 봐야겠지만, DB를 EXPORT한 뒤에, 파일을 지우고 다시 IMPORT하는게 아닐까 하는 의구심이 든다. 매번 하기 귀찮으면 "PRAGMA auto_vacuum=1;"를 하면, 새로운 빈 페이지(DELETE나 DROP TABLE 같은...)가 생길 때마다, VACUUM을 실행한다. 다만, SQLite구조 문제로 테이블을 생성하기 전에 미리 날려야하는 안타까움이 있다. (참조: http://sqlite.org/pragma.html#pragma_auto_vacuum )

OpenSSL supports Multi-threading

내가 스레드를 별로 좋아하지 않는데, 별 수 없이 써야할 경우가 종종 있다. 그와 별개로 OpenSSL 쓰기를 좋아하는데, OpenSSL을 멀티 스레딩 환경에서 쓰면 자칫 알 수 없는 이유로 죽곤한다. 이유는 OpenSSL 각종 알고리즘엔진이 멀티 스레딩 환경을 고려하지 않은 엔진이라, 경합이 발생하여 충돌이 발생하기 때문이다. 참조: https://www.openssl.org/docs/crypto/threads.html 멀티 스레딩 지원은 0.9.5b-dev부터 지원하였으니, 이하 버전은 포기하자. (응?) (사실 그 전에도 약간은 지원했으나, CentOS5 기준 0.9.8이니, 이전 버전은 포기하는게 정답이다) 맨페이지를 보면 "crypto/threads/mttest.c 파일에 예제가 있어요 뿌잉뿌잉~"이라는데 나중에 찾아보기 귀찮으니 블로그에 옮겨 놓...으려고 봤는데, 주석도 길고 모든 OS에 대한 전처리기도 있고, C++11도 나왔는데 구닥다리 코드를 쓸 생각이 없으니 내 맘대로 다시 구성했다. #include <openssl/crypto.h> #define OPENSSL_THREAD_DEFINES #include <openssl/opensslconf.h> #if !defined(OPENSSL_THREADS) # error "OpenSSL version is not supported multi-thread" #endif // C++11 mutex support #include <mutex> // Global locks for OpenSSL static std::mutex* g_locks(nullptr); // Locking callback function for OpenSSL static void funcLock(int mode, int type, char* file, int line) { if ( mode bitand CRYPTO_LOCK ) g_locks[

Bash Array, Map 정리

Bash에서 Array, Map에 대한 정리. (매번 찾기 귀찮) 찾아보진 않았지만, Bash에서 Array든 Map이든 동일하게 Map(C++에서 Unordered Map)으로 동작하는 것 같다. 왜냐하면, Array의 Index가 연속하지 않아도 동작한다. 그저 Key가 0 이상의 정수인 Map이랑 비슷하게 동작한다. 예) 1, 2, 3, 9, 10 Array # 생성 declare -a empty_array declare -a ar=(haha hoho baba "long string haha hoho") # 접근 echo "ar[0]=${ar[0]}" echo "all as array=${ar[@]}" # 큰따옴표 안에서 각 원소를 따로따로 전달한다. echo "all as one=${ar[*]}" # 큰따옴표 안에서 각 원소를 문자열 하나로 합쳐 전달한다. echo "indexes=${!ar[@]}" echo "indexes=${!ar[*]}" echo "length=${#ar[@]}" echo "length=${#ar[*]}" echo "last=${ar[-1]}" echo "last=${ar[@]: -1}" # 콜론 뒤에 빈 칸이 꼭 필요하다. 옛 방식 # 현재 상황 declare -p ar #(출력) declare -a ar=([0]="haha" [1]="hoho" [2]="baba" [3]="long string haha hoho") ar[100]=hello # 인덱스를 건너 뛰어도 동작한다. declare -p ar #(출력) declare -a ar=([0]="haha" [1]="hoho" [2]="baba" [3]=&