웹 성능을 최적화할 때 자주 등장하는 개념 중 하나가 HTTP Keep-Alive다. Keep-Alive는 TCP 연결을 유지해 여러 개의 HTTP 요청을 처리할 수 있도록 도와주지만, 모든 환경에서 무조건 좋은 선택이 되는 것은 아니다.
이번 글에서 Keep-Alive의 개념, 동작 원리, 장단점을 분석해 이것이 성능 최적화의 필수 요소인지, 아니면 특정 환경에서는 피해야 할 설정인지 살펴보자.
Keep-Alive 가 뭔데?
기본적으로 HTTP 는 요청과 응답이 끝나면 연결을 끊는 방식으로 동작한다. 하지만 Keep-Alive 가 활성화되면 한번 맺은 TCP 연결을 재사용할 수 있다.
🌱 Keep-Alive 가 비활성화된 기본 HTTP 동작은 어떻게 하는거지?
1. 클라이언트가 서버에 요청을 보낸다.
2. 서버가 응답을 반환하고 TCP 연결을 종료한다.
3. 다음 요청이 오면 새로운 TCP 연결을 생성한다.
🌱 Keep-Alive 가 활성화 되면 어떻게 동작할까?
1. 클라이언트가 서버에 요청을 보낸다.
2. 서버가 응답을 반환하지만 TCP 연결을 유지한다.
3. 이후 추가 요청이 같은 연결을 재사용한다.
이 방식은 HTTP 1.1 부터 기본적으로 활성화되어 있으며, 수동으로 비활성화 하려면 Connection: close 를 명시해야 한다.
설정 방법
1. HTTP 1.0
- 기본적으로 연결이 요청-응답 후 종료됨
- Connection: Keep-Alive 헤더를 추가하면 지속적인 연결 유지 가능
GET / HTTP/1.0
Connection: Keep-Alive
위 헤더가 포함되면 서버는 TCP 연결을 닫지 않고 유지하려고 한다.
기본적으로 HTTP/1.0 에서는 요청이 끝나면 연결이 닫히므로 반드시 명시적으로 Connection: Keep-Alive 를 추가해야 한다.
2. HTTP 1.1
- 기본적으로 Keep-Alive 가 활성화됨
- 연결을 종료하려면 Connection: close 헤더를 명시해야함
GET / HTTP/1.1
Connection: close
⊕ 서버 기준으로 설정하는 방법도 존재한다.
서버에서도 Keep-Alive 를 관리할 수 있으며, 주요 설정 옵션은 다음과 같다.
- Keep-Alive Timeout: 연결을 유지하는 시간 (ex. 5초)
- Max Requests: 하나의 연결에서 처리할 수 있는 최대 요청 개수
- Buffer Size: 데이터 전송 시 사용되는 버퍼크기
어떻게 연결을 유지하는 걸까?
Keep-Alive 가 활성화되면 클라이언트와 서버는 같은 TCP 연결을 재사용하는데 이를 유지하는 메커니즘은 다음과 같다.
1) 클라이언트가 첫 요청을 보낼 때
- 클라이언트가 Connection: Keep-Alive 를 포함하여 HTTP 요청을 보낸다. (HTTP/1.0 인 경우)
- 서버가 이를 받아들이고, 응답 헤더에 Connection: Keep-Alive 를 포함하여 응답한다.
- TCP 연결은 닫히지 않고 계속 유지된다.
2) 추가 요청을 같은 TCP 연결에서 전송
- 클라이언트는 새로운 HTTP 요청을 보낼 때 기존 TCP 연결을 재사용한다.
- 서버는 Keep-Alive 설정이 적용된 경우 같은 연결에서 응답을 보낸다.
3) Keep-Alive Timeout 시간이 지나면 연결 종료
- 클라이언트가 일정 시간동안 요청을 보내지 않고 타임아웃 시간이 지나면 서버는 연결을 닫는다.
- 이후 클라이언트가 새로운 요청을 보내면 다시 새로운 TCP 연결이 생성된다.
그래서 뭐가 좋은데?
✅ 성능 최적화
- TCP 연결을 새로 맺는 과정 (3-way handshake) 이 생략되면서 요청-응답 속도가 향상된다.
- 브라우저에서 여러개의 리소스를 요청할 때 (ex. CSS, JS, 이미지) 빠르게 처리가능하다.
✅ 네트워크 및 서버 부하 감소
- TCP 연결을 재사용하므로 불필요한 연결 생성 비용이 줄어든다.
- 초당 많은 요청을 처리하는 서버에서 효과적이다.
✅ 페이지 로딩 속도 개선
- Keep-Alive 가 없으면 브라우저가 리소스를 가져올 때마다 새로운 연결을 맺어야 하지만 Keep-Alive 를 사용하면 병렬 다운로드가 더 효율적으로 이뤄진다.
그럼 Keep-Alive 의 사용이 무조건 좋은 것 아냐?
그렇다면 Keep-Alive 의 사용이 무조건 좋아보이는데 사용하지 않을 이유가 있을까? 심지어 HTTP/1.1 에서는 Keep-alive 의 사용이 기본설정이다.
하지만 우리가 사용하고 개발하는 서비스는 모두 같은 인프라 환경을 가지고 있지 않다. 따라서 다음에 환경에서는 Keep-Alive 의 사용이 비효율적일 수 있다.
☑️ 서버 리소스 낭비
너무 많은 Keep-Alive 연결을 유지하면 메모리와 CPU 사용량이 증가할 수 있다. 따라서 timeout 값을 적절하게 조정하여 사용해야한다.
☑️ 로드 밸런서와의 충돌 (쿠버네티스 환경 포함)
단일 인스턴스로 운영되고 있는 환경이 아니라면,
Keep-Alive 가 활성화되면 클라이언트가 항상 같은 서버 인스턴스(=파드) 로 요청을 보낼 가능성이 높아진다.
쿠버네티스는 기본적으로 여러 개의 파드로 부하를 분산해야하지만 Keep-Alive 로 인해 특정 인스턴스에 부하가 집중될 수 있다.
그래서 위 단점을 개선하고자 한다면
1️⃣ timeout 을 짧게 설정
2️⃣ 로드 밸런서에서 연결을 강제로 끊고 새로운 서버로 라우팅, 쿠버네티스 환경이라면 keep alive 활성화가 되더라도 트래픽이 분산될 수 있도록 아키텍쳐 개선이 필요
3️⃣ HTTP/2 사용 (멀티플렉싱)
하는 등의 해결책이 있을 수 있다.
⊕ 추가적으로 HTTP/2.0 에서 멀티플렉싱에 대해서 좀 더 알아보자
HTTP/1.1 에도 문제점이있다. HTTP/1.1 에서는 한 개의 TCP 연결에서 하나의 요청-응답만 처리할 수 있다. 따라서 여러개의 요청을 동시에 처리하려면 추가적인 TCP 연결을 만들어야한다. 브라우저는 성능을 위해 여러 개의 TCP 연결을 병렬로 사용하지만 이는 오버헤드가 발생하게된다. 따라서 HOL Blocking 문제가 발생한다.
* HOL Blocking(Head-of-Line Blocking) : 하나의 요청이 지연되면 같은 연결을 공유하는 다른 요청들도 영향을 받음
HTTP/2.0 에서의 멀티플렉싱은 하나의 연결에서 여러개의 요청을 동시처리한다.
요청과 응답을 스트림 단위로 처리하여 각 요청이 독립적으로 병렬처리된다. 따라서 한 요청이 지연되더라도 다른 요청은 영향을 받지 않는다. 별도의 추가적인 TCP 연결을 만들 필요가 없다.
=> 각 요청이 독립적인 스트림으로 전송되므로 동시에 응답을 받을 수 있다.!
따라서 HTTP/2.0 환경에서는 Keep-Alive 를 따로 설정할 필요가 없다.
쿠버네티스 환경에서 Keep-Alive 를 사용했을 때 실제로 어떤 일이 일어날 수 있을까?
무서운 실무이야기를 해보자면... 이 keep-alive 옵션으로 인해 운영에서 장애를 겪은 적이있다.
현재 쿠버네티스 환경에서 운영하고 있고 클라이언트(웹) 으로 부터 요청을 받는 server 가 있고 또 그 요청을 받아서 IDC 에 있는 디비와 connection 하는 내부 서버가 존재한다.
HTTP/1.1 을 사용하고 있고 서버와 내부서버들은 같은 클러스터에 존재하고 있다. (server 의 경우도 인스턴스가 여러대 있지만 간략한 설명을 위해 생략했다)
0. 클라이언트로 부터 요청을 받음
1. HTTP/1.1 를 사용하여 기본적으로 Keep-Alive 설정이 켜져 있는 상황이고 timeout 은 30초로 설정되어 있음
- 쿠버네티스 환경에서 부하분산이 제대로 이루어지지않아 내부서버 1 로만 요청이 몰리게 됨
2. 데이터 베이스와 커넥션
3. slow query 혹은 trigger lock 의 이슈로 DB 와 의 커넥션이 길어지게 됨
4. 하나의 요청이 지연되면서 같은 연결을 공유하는 요청들도 영향을 받게되고 DB 자체의 부하가 생기게 되면서 모든 요청들의 대한 이슈가 생김
- 설상가상으로 클라이언트에서는 요청의 대한 응답이 오지 않았을 경우 30초마다 retry 실행
5. 부하분산이 제대로 이루어지지 않아 지속적인 요청과 타임아웃으로 인한 과부하 & 헬스체크 실패로 내부서버1 파드가 죽어버림
=> 데이터 베이스 문제였기 때문에 애플리케이션 단에서 바로 해결할 수는 없었지만 keep-Alive 활성화 설정으로 인해 내부 서버 2로의 fail over 가 제대로 이루어지지 않았음을 볼 수 있음
위와 같은 사례로 인해 현재 내부서버간에는 일단 keep-alive 옵션을 꺼두고 인프라 개선을 앞두고 있는 상황이다. (언제 개선할지는 모르겠지만ㅠㅠ) 그리고 HTTP/2 멀티플렉싱을 알게되었는데 요것도 한번 검토해볼 예정이다.
▶️ 결론
따라서, Keep-Alive 가 항상 최적의 선택은 아니다.
쿠버 네티스 환경에서는 Keep-Alive 가 단일 인스턴스에 부하를 집중시킬 가능성이 있기 때문에 로드 밸런싱과 함께 고려해야한다.
일반 웹 서버에서는 성능 최적화에 유리하지만 쿠버네티스처럼 부하 분산이 중요한 환경에서는 부작용이 발생할 수 있다.
최신 웹에서는 HTTP/2 를 사용하면 Keep-Alive 설정을 고민할 필요가 없지만 아직 그래도 HTTP/1.1 을 사용하는 환경이 더 많은 것 같다 (우리회사도...)