킵얼라이브
고친 사람 github-actions[bot]
킵얼라이브는 네트워크 연결이 끊기지 않게 붙잡아 두는 일을 부르는 이름입니다. 이 이름은 서로 다른 두 가지 일에 함께 쓰입니다. 하나는 한동안 조용한 연결에 작은 신호를 보내 상대가 아직 있는지 묻는 일입니다. 다른 하나는 응답을 보낸 뒤에도 연결을 닫지 않고 다음 요청에 다시 쓰는 일입니다.
쉽고 빠른 이해
무슨 일을 하나 — 연결을 끊지 않고 붙잡아 두는 일입니다. 뜻이 둘입니다. 첫째 뜻은 조용한 연결에 「아직 있나요」를 묻는 작은 신호를 보내는 일입니다. 둘째 뜻은 웹 요청 하나를 끝낸 연결을 닫지 않고 다음 요청에 다시 쓰는 일입니다.
왜 하나 — 첫째 뜻이 없으면 상대가 전원이 꺼져 사라져도 이쪽은 연결이 살아 있다고 믿고 붙들고 있습니다. 둘째 뜻이 없으면 요청마다 연결을 새로 맺느라 오가는 시간이 늘어납니다.
어떻게 도나
- 첫째 뜻 — 연결이 한동안 조용하면 작은 신호를 하나 보냅니다
- 답이 오면 연결을 둡니다. 몇 번 물어도 답이 없으면 연결을 끊습니다
- 둘째 뜻 — 응답을 보낸 뒤에도 연결을 열어 둡니다. 같은 상대의 다음 요청을 그 연결로 받습니다
대가 — 첫째 뜻은 신호만큼 네트워크를 더 씁니다. 길이 잠깐 막힌 탓에 멀쩡한 연결을 끊기도 합니다. 둘째 뜻은 쉬는 연결에도 서버의 메모리를 내줍니다. 오래 열어 둔 연결은 중간 장비(방화벽이나 공유기처럼 두 컴퓨터 사이에 선 장비)가 몰래 지워 다음 요청이 실패하기도 합니다. 오래 쉬는 연결이 이런 장비를 지난다면 첫째 뜻을 짧은 간격으로 켜서 막습니다.
상세
이 절은 킵얼라이브의 뜻들을 표로 가른 뒤 조용한 연결에 작은 신호가 오가는 과정, 한 연결을 다시 쓰는 모습, 두 뜻이 만나는 장애, 뜻을 가르는 단서를 차례로 봅니다.
두 뜻, 그리고 첫째 뜻이 사는 두 계층
뜻을 가르려면 두 프로토콜 이름이 필요합니다. TCP(Transmission Control Protocol, 전송 제어 프로토콜)는 두 컴퓨터 사이에 연결을 세워 바이트를 순서대로 나르는 규약입니다. HTTP(HyperText Transfer Protocol)는 그 연결 위에서 웹 요청과 응답을 주고받는 규약입니다.
킵얼라이브는 어느 계층에서 부르느냐에 따라 하는 일이 달라집니다. 뜻은 둘인데 첫째 뜻이 두 계층에 있어서 아래 표는 세 줄입니다.
| 맥락 | 킵얼라이브가 하는 일 | 막으려는 것 |
|---|---|---|
| TCP | 조용한 연결에 작은 신호를 보내 답을 받는다 | 사라진 상대를 모르고 붙들고 있는 것 |
| HTTP | 응답 뒤에도 연결을 닫지 않고 다음 요청에 쓴다 | 요청마다 연결을 새로 맺는 시간 |
| 애플리케이션 프로토콜 | 프로토콜 안에서 짧은 확인 메시지를 주고받는다 | TCP 킵얼라이브가 못 잡는 프로그램의 멈춤 |
표의 첫째 줄과 셋째 줄은 같은 첫째 뜻을 다른 계층에서 합니다. 둘 다 상대가 살아 있는지 묻습니다. 둘째 줄의 HTTP 킵얼라이브만 뜻이 다릅니다. 이쪽은 아무것도 묻지 않고 연결을 열어 둘 뿐입니다.
조용한 연결이 곤란한 까닭
전화를 걸어 놓고 한참 말이 없으면 「여보세요, 듣고 계세요?」 하고 묻습니다. 대답이 오면 통화를 잇습니다. 몇 번 물어도 조용하면 끊습니다. TCP 킵얼라이브가 연결에 하는 일이 이 물음입니다.
TCP 연결에서는 보낼 데이터가 있을 때만 무언가가 오갑니다. 양쪽 다 보낼 것이 없으면 연결 위로는 아무것도 흐르지 않습니다. 그사이 상대에게 무슨 일이 생겨도 이쪽은 알 방법이 없습니다.
TCP 가 한 번에 주고받는 덩어리를 세그먼트라고 부릅니다. 데이터도, 연결을 맺고 닫는 신호도 모두 세그먼트에 실려 오갑니다.
연결을 닫을 때는 FIN(finish) 표시를 단 세그먼트로 끝 인사를 보냅니다. 상대 컴퓨터가 갑자기 전원이 꺼지거나 네트워크 선이 빠지면 이 인사를 보낼 틈이 없습니다.
인사를 못 받은 쪽은 연결이 아직 살아 있다고 믿습니다. 한쪽만 열려 있다고 믿는 이런 연결을 반쯤 열린 연결이라고 부릅니다. 상대의 응답만 기다리던 프로그램은 오지 않을 데이터를 끝없이 기다립니다.
서버는 연결마다 소켓을 하나씩 쥡니다. 소켓은 프로그램이 연결 하나를 읽고 쓸 때 쥐는 손잡이입니다. 반쯤 열린 연결이 쌓이면 아무도 쓰지 않는 소켓과 메모리가 함께 쌓입니다.
곤란한 경우가 하나 더 있습니다. 두 컴퓨터 사이에 선 장비가 조용한 연결을 지우는 경우입니다. 세그먼트는 네트워크를 지날 때 패킷에 담겨 갑니다. 중간 장비가 들여다보는 것은 이 패킷입니다.
방화벽은 드나드는 패킷을 규칙에 따라 들이거나 막는 장비입니다. 들일 연결과 막을 연결을 가려 안쪽 컴퓨터를 지킵니다.
NAT(Network Address Translation, 네트워크 주소 변환)는 안쪽의 사설 주소를 바깥 주소로 바꿔 적어 주는 장비입니다. 집의 공유기가 흔히 이 일을 합니다.
두 장비는 지나가는 연결을 표에 한 줄씩 적어 둡니다. 이 표가 연결 추적 표입니다. 표에 줄이 있는 연결의 패킷만 제대로 지나갑니다.
표는 한없이 커질 수 없으므로 오래 조용한 줄부터 지웁니다. 줄을 지우기까지 기다리는 시간을 유휴 타임아웃이라고 합니다.
줄이 지워진 뒤에 도착한 패킷은 맞는 줄이 없어 버려집니다. 양 끝의 컴퓨터는 멀쩡한데 연결만 조용히 죽습니다. 어느 쪽도 이 사실을 알림받지 못합니다.
TCP 킵얼라이브가 도는 방식
TCP 킵얼라이브는 조용한 시간이 정해 둔 길이를 넘으면 상대에게 작은 세그먼트 하나를 보냅니다. 이 세그먼트를 탐침(probe)이라고 부릅니다. 탐침에는 새 데이터가 없습니다.
탐침이 상대 프로그램을 건드리지 않는 까닭은 TCP 의 번호에 있습니다. TCP 는 보내는 바이트마다 차례로 번호를 매깁니다. 이 번호가 시퀀스 번호입니다. 받는 쪽은 이미 받은 번호의 바이트가 다시 오면 버립니다.
받는 쪽은 어디까지 받았는지를 보낸 쪽에 알립니다. 이 알림을 담은 세그먼트가 확인 응답입니다. 보낸 쪽은 확인 응답을 보고 상대가 어느 번호까지 받았는지 압니다.
탐침은 상대가 이미 확인 응답을 보낸 번호를 하나 되풀이해 싣습니다. 받는 쪽 TCP 는 이미 받은 바이트라 버리므로 프로그램에는 아무것도 올라가지 않습니다. 대신 「그건 이미 받았다」는 확인 응답을 다시 돌려줍니다.
탐침을 보낸 뒤 벌어지는 일은 셋으로 갈립니다. 상대가 살아 있으면 확인 응답이 옵니다. 이쪽은 조용한 시간을 처음부터 다시 셉니다.
상대 컴퓨터가 꺼졌다가 다시 켜졌으면 RST(reset) 세그먼트가 옵니다. RST 는 「그런 연결은 모른다」는 뜻의 초기화 신호입니다. 다시 켜진 컴퓨터는 옛 연결을 기억하지 못하기 때문입니다. 이쪽은 이 신호를 받고 연결을 닫습니다.
아무 답도 없으면 잠깐 두고 탐침을 몇 번 더 보냅니다. 탐침 하나가 길에서 사라졌을 수도 있기 때문입니다. 끝내 답이 없으면 상대가 없다고 보고 연결을 닫습니다.
flowchart TD
A["연결이 정해 둔 시간 동안 조용하다"] --> B["탐침을 보낸다"]
B --> C{"무엇이 돌아오나"}
C -->|확인 응답| D["살아 있다 · 시간을 다시 센다"]
C -->|RST| E["상대가 연결을 잊었다 · 닫는다"]
C -->|아무것도 안 온다| F["탐침을 몇 번 더 보낸다"]
F -->|확인 응답이 온다| D
F -->|끝내 답이 없다| G["상대가 없다 · 닫는다"]
어느 쪽으로 닫히든 프로그램은 그 소켓을 다음에 읽거나 쓸 때 오류를 받습니다. 끝없이 기다리던 프로그램도 이 오류로 깨어납니다. 붙들고 있던 반쯤 열린 연결이 이렇게 정리됩니다.
탐침에는 쓸모가 하나 더 있습니다. 탐침도 패킷에 담겨 방화벽과 NAT 를 지나가므로 그때 연결 추적 표의 줄을 새로 고칩니다. 탐침 간격이 유휴 타임아웃보다 짧으면 그 줄은 지워지지 않습니다.
기본으로 꺼져 있는 TCP 킵얼라이브
TCP 킵얼라이브는 TCP 의 선택 기능입니다. 켜 두지 않으면 돌지 않습니다. 켜더라도 첫 탐침을 보내기 전에 기다리는 시간은 기본으로 두 시간 이상입니다.
이렇게 보수적으로 잡은 까닭은 탐침에 드는 비용입니다. 탐침은 아무 데이터도 없는 트래픽을 만듭니다. 길이 잠깐 막혔을 뿐인데 탐침이 몇 번 연달아 사라지면 멀쩡한 연결을 끊게 됩니다.
두 시간은 중간 장비가 줄을 지우는 시간보다 긴 경우가 흔합니다. 그래서 TCP 킵얼라이브를 켜기만 해서는 방화벽이나 NAT 에서 연결이 지워지는 일을 못 막습니다. 기다리는 시간을 유휴 타임아웃보다 짧게 줄여야 탐침이 줄을 살려 둡니다.
킵얼라이브를 켜는 일은 프로그램이 연결마다 합니다. 소켓 옵션으로 켜고 끕니다. 소켓 옵션은 소켓 하나의 동작을 바꾸는 설정값입니다. 아래는 자바에서 이 옵션을 읽고 켜는 모습입니다.
Socket s = new Socket(host, port);
s.getKeepAlive(); // false
s.setKeepAlive(true);
s.getKeepAlive(); // true
처음 읽은 값이 false 입니다. 켜지 않은 연결에서는 킵얼라이브가 돌지 않는다는 뜻입니다. 기다리는 시간과 탐침 간격, 몇 번까지 보낼지는 대개 운영체제 설정이 정합니다. 운영체제에 따라 연결마다 따로 바꾸는 옵션도 있습니다.
애플리케이션 킵얼라이브
TCP 킵얼라이브로는 모자랄 때가 있습니다. 탐침에 답하는 것은 운영체제 안의 TCP 이지 프로그램이 아닙니다. 프로그램이 멈춰 요청을 못 받고 있어도 운영체제는 확인 응답을 꼬박꼬박 돌려줍니다.
기다리는 시간이 대개 운영체제 설정에 묶여 있다는 것도 걸립니다. 프로그램마다 알맞은 간격이 다릅니다. 운영체제 설정은 모든 프로그램에 하나입니다.
그래서 프로토콜이 자기 안에 확인 메시지를 따로 둡니다. 이 프로토콜들이 주고받는 메시지 한 단위를 프레임이라고 합니다. 웹소켓의 핑(ping)·퐁(pong) 프레임과 HTTP/2의 핑 프레임이 그런 확인 메시지입니다.
한쪽이 핑을 보내면 받은 쪽이 같은 프로토콜 안에서 답합니다. 답하는 주체가 운영체제가 아니라 그 프로토콜을 처리하는 프로그램입니다. 그래서 프로그램이 멈추면 답도 멈춥니다. 보낸 쪽은 답이 끊긴 것을 보고 멈춤을 알아챕니다.
프로토콜 안에서 주고받는 이런 확인을 애플리케이션 킵얼라이브라고 부릅니다. 간격도 운영체제가 아니라 그 프로토콜을 쓰는 라이브러리 설정에서 정합니다.
여러 서버가 서로 살아 있는지 알리는 하트비트도 같은 일을 합니다. 살피는 대상이 연결 하나가 아니라 서버 한 대일 뿐입니다.
SCTP(Stream Control Transmission Protocol, 스트림 제어 전송 프로토콜)는 TCP 대신 쓰는 전송 프로토콜의 하나입니다. SCTP 는 상대에게 확인 신호를 보내는 일을 처음부터 프로토콜 안에 넣어 두었습니다. 프로그램이 확인 메시지를 따로 만들지 않아도 전송 계층에서 오갑니다.
HTTP 킵얼라이브
이제 뜻이 바뀝니다. HTTP 에서 킵얼라이브는 아무것도 묻지 않습니다. 응답을 보낸 뒤 연결을 닫지 않고 남겨 둘 뿐입니다.
HTTP 요청을 보내려면 먼저 TCP 연결을 맺어야 합니다. TCP 는 연결을 맺을 때 세그먼트 셋을 주고받습니다. 이 절차가 3방향 핸드셰이크입니다.
신호가 상대까지 갔다가 돌아오는 데 걸리는 시간을 왕복 시간이라고 부릅니다. 핸드셰이크 때문에 요청을 보내기 전에 왕복 시간이 한 번 더 듭니다. 암호화 연결인 TLS(Transport Layer Security, 전송 계층 보안)를 쓰면 그 위에 왕복이 더 붙습니다.
요청마다 연결을 새로 맺으면 이 왕복을 요청마다 치릅니다. 한 페이지에 딸린 파일이 수십 개면 그 비용도 수십 번입니다. 연결을 열어 두고 다음 요청에 다시 쓰면 맺는 비용은 첫 요청 한 번뿐입니다.
HTTP/1.0 에서는 응답 하나를 보내면 연결을 닫는 것이 기본이었습니다. 연결을 열어 두고 싶은 클라이언트는 요청에 Connection: keep-alive 헤더를 달아 부탁했습니다. HTTP 에서 킵얼라이브라고 부르는 것이 이 헤더 값입니다.
HTTP/1.1 부터는 열어 두는 쪽이 기본입니다. 닫고 싶은 쪽이 Connection: close 를 보냅니다. 이렇게 한 연결로 요청 여럿을 나르는 방식의 정식 이름은 지속 연결(persistent connection)입니다.
sequenceDiagram
participant 클라이언트
participant 서버
Note over 클라이언트,서버: 3방향 핸드셰이크 · 한 번만
클라이언트->>서버: 요청 1
서버-->>클라이언트: 응답 1
Note over 클라이언트,서버: 연결을 닫지 않는다
클라이언트->>서버: 요청 2
서버-->>클라이언트: 응답 2
Note over 서버: 한동안 요청이 없으면 서버가 닫는다
그림에서 핸드셰이크는 맨 위에 한 번뿐입니다. 요청 2 는 핸드셰이크 없이 열린 연결로 바로 나갑니다.
열어 둔 연결에는 대가가 있습니다. 서버는 쉬는 연결에도 소켓과 메모리를 내줍니다. 그래서 서버는 한동안 요청이 없는 연결을 스스로 닫습니다. 이 기다림도 유휴 타임아웃입니다.
이 뜻의 킵얼라이브는 신호를 보내지 않습니다. 열어 둔 연결이 아직 쓸 만한지 확인하지 않습니다. 이 빈틈이 다음 소절의 장애로 이어집니다.
두 뜻이 만나는 장애
커넥션 풀은 연결을 미리 맺어 두고 요청마다 빌려 쓰는 장치입니다. 데이터베이스 드라이버와 HTTP 클라이언트가 이 풀을 둡니다. 연결을 다시 써서 맺는 비용을 아낀다는 점에서 HTTP 킵얼라이브와 목적이 같습니다.
풀 안의 연결은 다음 요청이 올 때까지 쉽니다. 새벽처럼 요청이 드문 때에는 오래 쉬기도 합니다. 그사이 방화벽이나 NAT 가 표에서 그 줄을 지웁니다. 상대 서버가 유휴 타임아웃으로 먼저 닫기도 합니다.
풀은 이 일을 모릅니다. 다음 요청에 그 연결을 꺼내 쓰면 요청이 버려지거나 곧바로 연결이 끊겼다는 오류가 납니다. 한동안 조용했다가 들어온 첫 요청만 실패하는 증상이 이것입니다.
막는 방법은 셋입니다.
| 방법 | 하는 일 |
|---|---|
| TCP 킵얼라이브를 짧게 켠다 | 쉬는 연결에 탐침을 흘려 중간 장비의 줄을 살려 둔다 |
| 풀이 먼저 닫는다 | 풀이 연결을 쉬게 두는 한도를 중간 장비와 서버보다 짧게 잡는다 |
| 꺼낼 때 검사한다 | 빌려주기 전에 짧은 질의를 보내 살아 있는지 본다 |
표의 첫째 방법에서 두 뜻이 만납니다. 다시 쓰려고 열어 둔 연결이 문제를 낳습니다. 상대가 있는지 묻는 킵얼라이브가 그 문제를 막습니다.
어느 뜻인지 가르는 단서
문서나 설정에서 킵얼라이브를 만나면 함께 나오는 낱말을 봅니다. 대개 그것만으로 뜻이 갈립니다.
| 함께 나오는 말 | 뜻 |
|---|---|
| 탐침 · 유휴 시간 · 소켓 옵션 · 반쯤 열린 연결 · 운영체제 설정 | TCP 킵얼라이브 |
Connection 헤더 · 연결당 요청 수 · 연결 재사용 · 웹 서버 설정 |
HTTP 킵얼라이브 |
| 핑 · 퐁 · 프레임 · 라이브러리의 간격 설정 | 애플리케이션 킵얼라이브 |
웹 서버나 로드 밸런서 설정에 나오는 keepalive 는 대개 HTTP 쪽 뜻입니다. 쉬는 연결을 얼마나 열어 둘지, 연결 하나로 요청을 몇 개까지 받을지를 정합니다. 설정 이름에 소켓이나 TCP 가 붙어 있으면 탐침 쪽 뜻입니다.
관련 항목
킵얼라이브 탐침에 답하거나 연결을 끊는 TCP 신호
TCP · 세그먼트 · 확인 응답 · RST · FIN · 시퀀스 번호 · 재전송 · 3방향 핸드셰이크
킵얼라이브로 연결 기록을 살려 두는 중간 장비
방화벽 · NAT · 연결 추적 · 유휴 타임아웃 · 로드 밸런서 · 프록시 · 리버스 프록시
킵얼라이브가 찾아내 정리하는 연결 장애
반쯤 열린 연결 · 연결 재설정 · 타임아웃 · 패킷 손실 · 네트워크 분할
HTTP 킵얼라이브를 이루는 헤더와 연결 방식
HTTP · HTTP/1.1 · 지속 연결 · Connection 헤더 · 파이프라이닝 · HTTP/2
연결을 다시 써서 킵얼라이브가 아끼는 비용
왕복 시간 · 3방향 핸드셰이크 · TLS 핸드셰이크 · 슬로 스타트 · 지연
킵얼라이브가 열어 둔 연결을 빌려 쓰는 장치
커넥션 풀 · HTTP 클라이언트 · JDBC · HikariCP · 연결 검증
연결 대신 프로토콜 안에서 상대를 살피는 신호
하트비트 · 헬스 체크 · 웹소켓 · HTTP/2 · SCTP · gRPC
킵얼라이브를 켜고 조정하는 소켓 설정
소켓 · 소켓 옵션 · SO_KEEPALIVE · setsockopt · sysctl
다른 이름: keepalive · keep-alive · 킵 얼라이브 · 연결 유지