1. 라이브러리
  2. TCP와 UDP
  3. TCP 심층 분석

업데이트됨 1개월 전

TCP 연결은 신뢰할 수 있도록 설계되었지만, 신뢰성에도 맹점이 있습니다. 상대방이 그냥 사라져버리면 어떻게 될까요?

전원 장애. 커널 패닉. 누군가 네트워크 케이블을 밟고 넘어짐. 원격 시스템은 연결을 정상적으로 닫을 수 없습니다. 그냥 사라진 것입니다. 그렇다면 여러분의 애플리케이션은? 여전히 그 연결을 열어둔 채로, 절대 도착하지 않을 데이터를 기다리고 있습니다.

이것이 바로 반열린 연결(half-open connection) 문제입니다. 아무런 조치가 없다면, 이 좀비 연결들은 파일 디스크립터, 메모리, 연결 풀의 슬롯을 소비하며 무한정 지속됩니다. TCP keep-alive는 이 문제를 해결하기 위해 존재합니다. 유휴 연결의 심장 박동처럼, 주기적으로 "아직 거기 있나요?"라고 확인하는 것입니다.

유휴 연결이 맹목적인 이유

TCP의 일반적인 신뢰성 메커니즘—확인 응답(acknowledgment), 재전송, 타임아웃—은 데이터를 전송하려 할 때만 동작합니다. 연결이 유휴 상태로 풀에서 사용되기를 기다리고 있다면, 타임아웃될 것이 없습니다. 연결은 완벽하게 정상으로 보이다가, 막상 사용하려는 순간 상대방이 몇 시간 전에 죽었다는 것을 알게 됩니다.

Keep-alive는 이것을 바꿉니다. 애플리케이션 데이터가 흐르지 않는 동안, 운영 체제는 주기적으로 작은 프로브를 전송합니다. 응답이 돌아오면 연결은 살아있습니다. 여러 번 시도한 후에도 침묵이 이어지면, 연결은 죽은 것으로 선언되고 정리됩니다.

프로브의 작동 방식

프로브 패킷은 일종의 트릭입니다. 의도적으로 잘못된 시퀀스 번호를 전송하여 상대방이 수정 응답을 보내도록 유도합니다. 원격 TCP 스택은 어쩔 수 없이 "사실 저는 X번째 바이트를 기다리고 있어요"라고 응답해야 합니다. 틀린 번호를 바로잡으려는 이 본능적 반응이 바로 상대가 아직 살아있음을 알려주는 신호입니다.

소켓에 TCP keep-alive가 활성화되면, 운영 체제는 마지막으로 데이터를 주고받은 후 타이머를 시작합니다. 활동 없이 타이머가 만료되면 프로브가 전송됩니다. 상대방이 응답하면 타이머가 리셋됩니다. 응답이 없으면 다음 프로브가 전송됩니다. 충분한 수의 프로브가 실패하면 연결은 죽은 것으로 표시됩니다.

프로브는 매우 작습니다—TCP 헤더만 있고 페이로드가 없습니다. 정상적인 데이터 흐름을 방해하지 않으며, 연결이 침묵 상태일 때만 활성화됩니다.

기본 타이머는 터무니없습니다

이론이 현실과 만나는 지점이 여기입니다. 기본 keep-alive 설정은 지나치게 보수적입니다.

Linux에서 기본 keep-alive 시간은 7200초입니다. 첫 번째 프로브가 나가기까지 무려 두 시간입니다. 그 후 프로브 간격은 75초이며, 연결이 죽었다고 선언하기까지 9번의 프로브가 필요합니다. 계산해 보면: 죽은 연결을 감지하는 데 두 시간이 넘게 걸립니다.

이런 기본값은 대역폭이 비쌌고 인터넷이 불안정했던 시절에는 의미가 있었습니다. 하지만 백엔드 서버 장애를 몇 분 안에—아니, 몇 초 안에—알아야 하는 데이터베이스 연결 풀에서는 의미가 없습니다.

세 가지 매개변수가 동작을 제어합니다:

  • Keep-Alive Time: 첫 번째 프로브 전 대기 시간 (기본값: 7200초)
  • Keep-Alive Interval: 이후 프로브 사이의 시간 간격 (기본값: 75초)
  • Keep-Alive Probes: 연결 종료 선언 전 실패 허용 프로브 횟수 (기본값: 9)

프로덕션 시스템에서는 보통 소켓 옵션(TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT)을 통해 이 값들을 재정의합니다. 일반적인 설정은 60300초의 비활성 상태 후 프로브를 시작하고, 프로브 간격은 1030초로 설정합니다. 이를 통해 죽은 연결을 몇 시간이 아닌 몇 분 안에 감지할 수 있습니다.

Keep-Alive가 잘하는 것들

죽은 피어 감지: 핵심 사용 사례입니다. 전원이 나가거나, 패닉 상태가 되거나, 플러그가 뽑힌 서버는 FIN이나 RST를 보낼 수 없습니다. Keep-alive 프로브가 그 부재를 발견합니다.

자원 고갈 방지: 반열린 연결은 쌓입니다. 수천 개의 연결을 처리하는 서버에서는 좀비 연결이 조금만 있어도 파일 디스크립터, 메모리, 연결 제한에 압박을 줍니다.

NAT 매핑 유지: NAT 장치와 상태 기반 방화벽은 연결을 추적합니다. 연결이 너무 오래 유휴 상태로 있으면, 중간 장치가 이를 잊어버립니다. 양쪽 엔드포인트는 연결되어 있다고 생각하지만 이후 패킷은 드롭됩니다. Keep-alive 프로브는 매핑을 살아있게 유지하기에 충분한 트래픽을 만들어냅니다.

풀링된 연결 검증: 연결 풀에서 애플리케이션 코드로 연결을 넘기기 전에, 실제로 작동하는지 확인하고 싶습니다. Keep-alive는 수동적인 검증을 제공합니다—연결이 풀에서 살아남았다면, 아마 사용 가능할 것입니다.

Keep-Alive가 알려주지 못하는 것들

TCP keep-alive는 한 가지만 확인합니다. 원격 커널의 TCP 스택이 응답하고 있다는 것. 그게 전부입니다.

애플리케이션이 정상인지는 알려주지 않습니다. 데이터베이스는 완전히 교착 상태에 빠져 쿼리를 실행할 수 없는 상태에서도 TCP 프로브에 응답할 수 있습니다. 웹 서버는 작업자 프로세스가 무한 루프에 갇혀 있어도 keep-alive를 확인 응답할 수 있습니다.

이것이 근본적인 한계입니다. Keep-alive는 전송 계층에서 동작합니다. 네트워크는 볼 수 있지만, 애플리케이션은 볼 수 없습니다.

중단된 프로세스, 고갈된 스레드 풀, 교착 상태 같은 애플리케이션 수준의 장애를 감지하려면 애플리케이션 수준의 하트비트가 필요합니다. 주기적으로 SELECT 1을 실행하는 데이터베이스 클라이언트는 전체 스택을 검증합니다. 네트워크 연결, 데이터베이스 가용성, 쿼리 실행, 락 획득까지. TCP keep-alive는 이 중 어느 것도 검증하지 않습니다.

두 가지를 함께 쓰는 것이 올바른 접근입니다. 네트워크 장애와 죽은 머신을 잡기 위한 기준선으로 TCP keep-alive를 사용하고, 그 외의 모든 것을 잡기 위해 애플리케이션 하트비트를 사용하세요.

트레이드오프

네트워크 오버헤드: 프로브는 대역폭을 소비합니다. 수천 개의 유휴 연결에 대해서는 트래픽이 누적됩니다. 현대 네트워크에서는 거의 문제가 되지 않지만, 제한된 환경에서는 고려할 가치가 있습니다.

배터리 소모: 모바일 장치에서 잦은 프로브는 네트워크 인터페이스가 절전 모드로 들어가는 것을 막습니다. 모바일 애플리케이션은 종종 keep-alive 빈도를 비활성화하거나 크게 줄입니다.

오탐(False positive): 공격적인 타이밍은 저절로 해결되었을 일시적인 네트워크 문제 중에 연결이 죽었다고 선언할 위험이 있습니다. 10초 프로브 간격에 3번의 프로브라면, 30초의 네트워크 중단이 연결을 끊어버립니다—31초에 연결이 복구되더라도.

설정의 복잡성: 올바른 설정은 장애 모드, 네트워크 특성, 오래된 연결에 대한 허용 수준에 따라 달라집니다. 보편적인 정답은 없습니다.

결론

TCP keep-alive는 유휴 시간 동안 프로브를 보내 죽은 연결을 감지합니다. 운영 체제가 모든 것을 처리합니다—애플리케이션은 기능을 활성화하고 타이밍을 설정하기만 하면 됩니다.

대부분의 프로덕션 환경에서 기본값은 너무 보수적입니다. 장애를 빠르게 감지해야 할 때, 첫 번째 프로브까지 두 시간을 기다리는 것은 영원이나 다름없습니다. 타이머를 재정의하세요.

Keep-alive는 네트워크 장애와 죽은 머신을 잡습니다. 애플리케이션 장애는 잡지 못합니다. 포괄적인 상태 확인을 위해서는 전송 계층 keep-alive와 애플리케이션 계층 하트비트를 함께 사용하세요.

반열린 연결은 소리 없는 자원 누수입니다. 스스로를 알리지 않습니다. 무언가가 망가질 때까지 그냥 쌓입니다. Keep-alive는 그것들이 여러분을 찾기 전에, 여러분이 먼저 찾을 수 있게 해줍니다.

TCP Keep-Alive에 대한 자주 묻는 질문

TCP keep-alive를 활성화하면 성능에 영향을 미치나요?

거의 없습니다. 프로브는 작습니다—TCP 헤더만 있고—유휴 시간에만 전송됩니다. 대부분의 애플리케이션에서 오버헤드는 무시할 수준입니다. 규모가 커지면 이야기가 달라질 수 있습니다. 수천 개의 유휴 연결은 수천 개의 주기적인 프로브를 의미합니다. 그렇더라도 대역폭은 실제 애플리케이션 트래픽에 비하면 보통 미미합니다.

TCP가 기본적으로 keep-alive를 활성화하지 않는 이유는 무엇인가요?

역사적인 이유와 최소 놀라움의 원칙 때문입니다. Keep-alive는 일시적인 네트워크 문제에서 살아남았을 수도 있는 연결을 종료할 수 있습니다. 일부 애플리케이션은 장애 감지를 스스로 처리하는 것을 선호합니다. 보수적인 기본값—비활성화, 활성화 시 긴 타이머—은 명시적으로 선택하지 않은 애플리케이션에서 예기치 않은 동작을 방지합니다.

애플리케이션에서 TCP keep-alive를 어떻게 활성화하나요?

SO_KEEPALIVE 소켓 옵션을 설정하여 활성화한 다음, TCP_KEEPIDLE(첫 번째 프로브까지의 시간), TCP_KEEPINTVL(프로브 간격), TCP_KEEPCNT(사망 선언 전 실패 프로브 횟수)로 타이밍을 설정합니다. 대부분의 언어와 프레임워크는 소켓 API를 통해 이를 노출합니다. 일부 연결 라이브러리와 ORM은 더 높은 수준의 설정 옵션을 제공합니다.

TCP keep-alive가 원격 시스템의 프로세스 충돌을 감지할 수 있나요?

경우에 따라 다릅니다. 프로세스가 충돌했지만 시스템은 살아있는 경우, 커널은 더 이상 존재하지 않는 연결에 대한 프로브가 도착하면 RST 패킷을 보냅니다—이쪽에서 즉시 알 수 있습니다. 시스템 전체가 충돌하거나 네트워크 연결이 끊기는 경우, keep-alive 프로브는 응답 없이 계속 나가다가 결국 타임아웃을 트리거합니다. 첫 번째 경우는 빠르고, 두 번째는 프로브 타이밍에 따라 시간이 걸립니다.

TCP keep-alive와 애플리케이션 하트비트 중 무엇을 사용해야 하나요?

둘 다 사용하세요. TCP keep-alive는 네트워크 수준의 장애를 저렴하고 자동으로 잡습니다. 애플리케이션 하트비트는 종단 간 기능을 검증합니다—커널이 프로브에 응답하는 것을 넘어, 애플리케이션이 실제로 유용한 작업을 할 수 있는지 확인합니다. 둘은 서로를 보완합니다. Keep-alive는 안전망으로, 하트비트는 상태 확인으로.

이 페이지가 도움이 되었나요?

😔
🤨
😃
TCP Keep-Alive — 죽은 연결 감지하기 • 라이브러리 • Connected