4장 커넥션 관리
4.1 TCP 커넥션
HTTP 통신은 패킷 교환 네트워크 프로토콜들의 계층화 된 집합인 TCP/IP로 이루어진다.
1) 브라우저가 URL의 호스트명을 추출한다.
2) 브라우저가 이 호스트명에 대한 IP 주소를 찾는다.
3) 브라우저가 포트 번호를 얻는다.
4) 브라우저가 IP주소의 포트번호로 TCP커넥션을 생성한다.
5) 요청 메시지와 응답 메시지를 주고 받은 후 커넥션을 끊는다.
4.1.1 신뢰할 수 있는 데이터 전송 통로인 TCP
TCP 커넥션 한 쪽의 바이트들은 반대쪽으로 순서에 맞게 정확히 전달된다.
4.1.2 TCP 스트림은 세그먼트로 나뉘어 IP 패킷을 통해 전송된다
HTTP가 메시지를 전송하고자 할 경우, TCP는 세그먼트 단위로 데이터 스트림을 잘게 나누고 세그먼트를 IP 패킷에 담아 인터넷을 통해 데이터를 전달한다. IP 패킷은 IP패킷 헤더, TCP 세그먼트헤더, TCP 데이터 조각으로 이루어진다. IP헤더는 발신지와 목적지의 IP주소 등을 포함하여 TCP 세그먼트 헤더는 TCP 포트 번호와 TCP 제어 플래그, 데이터 순서와 무결성을 검사하는 숫자 값을 포함한다.
4.1.3 TCP 커넥션 유지하기
IP 주소는 해당 컴퓨터에 연결되고 포트 번호는 해당 애플리케이션으로 연결된다. 한 컴퓨터는 <발신지 IP주소, 발신지 포트, 수신지 IP주소, 수신지 포트>로 구분되는 TCP 커넥션을 여러개 형성한다.
4.1.4 TCP 소켓 프로그래밍
운영체제는 TCP 커넥션의 생성과 관련된 여러 기능을 제공한다.
4.2 TCP의 성능에 대한 고려
HTTP는 TCP 바로 위에 있는 계층이기 때문에 HTTP 트랜잭션의 성능은 그 아래 계층인 TCP 성능에 영향을 받는다.
4.2.1 HTTP 트랜잭션 지연
트랜잭션의 처리 시작은 TCP 커넥션을 설정하고 요청을 전송하고 응답을 보내는 것에 비하면 상당히 짧다. 클라이언트가 서버가 너무 많은 데이터를 내려 받거나 복잡하고 동적인 자원들을 실행하지 않는 한 대부분의 HTTP 지연은 TCP 네트워크 지연 때문에 발생한다.
4.2.2 성능 관련 중요 요소
TCP 관련 지연에는 TCP 커넥션의 핸드셰이크 설정, 인터넷 혼잡을 제어하기 위한 TCP의 느린 시작, 데이터를 모아 한 번에 전송하기 위한 네이클 알고리즘, TCP의 편승 확인응답을 위한 확인 응답 지연 알고리즘, TIME_WAIT 지연과 포트 고갈이 있다.
4.2.3 TCP 커넥션 핸드셰이크 지연
어떤 데이터를 전송하든 새로운 TCP 커넥션을 열 때면, TCP 소프트웨어는 커넥션을 맺기 위한 조건을 맞추기 위해 연속으로 IP 패킷을 교환한다. 이때 작은 크기의 데이터 전송에 커넥션이 사용된다면 이러한 패킷 교환이 HTTP 성능을 크게 저하시킨다. 트랜잭션의 과반 이상의 시간을 TCP를 구성하는데 사용하는게 될 수 있다.
4.2.4 확인 응답 지연
각 TCP 세그먼트는 순번과 데이터 무결성 체크섬을 가진다. 각 세그먼트의 수신자는 세그먼트를 온전히 받으면 작은 확인 응답 패킷을 송신자에게 반환한다. 만약 송신자가 확인 응답 메시지를 받지 못하면 패킷이 파기되었거나 오류가 있는 것으로 판단한다. 이때 HTTP는 효율성을 위해 몇몇 확인 응답 패킷을 버퍼에 담아두었다 같은 방향으로 송출되는 데이터 패킷에 편승시킨다. 그런데 같은 방향으로 송출되는 데이터 패킷이 많지 않아 지연이 발생할 수 있다.
4.2.5 TCP 느린 시작
TCP의 데이터 전송 속도는 TCP 커넥션이 만들어진지 얼마나 지났는지에 따라 달라진다. TCP는 처음엔 커넥션의 최대 속도를 제한하고 데이터가 성공적으로 전송됨에 따라서 속도 제한을 높여나간다. 따라서 새로운 커넥션은 어느 정도 데이터를 주고 받은 튜닝된 커넥션보다 느리다.
4.2.6 네이글 알고리즘과 TCP_NODELAY
네이글 알고리즘은 네트워크 효율을 위해 패킷을 전송하기 전에 많은 양의 TCP 데이터를 한 개의 덩어리로 합친다. 네이글 알고리즘은 세그먼트가 최대 크기가 되지 않으면 전송을 하지 않는다. (다만 모든 패킷이 확인 응답을 받았을 경우 작은 패킷도 전송할 수 있다.) 그러나 이 경우 크기가 작은 HTTP 패킷은 계속해서 불확실한 추가적인 데이터를 기다리며 지연된다. 또, 네이글 알고리즘과 확인 응답 지연이 맞물려 형편없이 동착할 수 있다.
4.2.7 TIME_WAIT의 누적과 포트 고갈
//
4.3 HTTP 커넥션 관리
4.3.1 흔히 잘못 이해하는 Connection 헤더
HTTP 메시지는 클라이언트에서 서버까지 중개 서버들을 거치면서 전달된다. 어떤 경우에는 두 개의 인접한 HTTP 애플리케이션이 현재 맺고 있는 커넥션에만 적용될 옵션을 지정해야 할 때가 있다. HTTP Connection 헤더 필드는 커넥션 토큰을 쉼표로 구분하여 가지며 그 값들은 다른 커넥션에 전달되지 않는다.
4.3.2 순차적인 트랜잭션 처리에 의한 지연
3개의 이미지가 있는 웹 페이지를 사용자에게 보여주려면 해당 HTML을 받기 위한 커넥션과 이미지를 보여주는 세 개의 커넥션이 맺어질 것이다. 이때 느린 시작 지연과 함께 커넥션을 맺는데 발생하는 지연이 발생한다. 한편 하나의 이미지를 내려 받는 중에는 웹 페이지 나머지 공간에 아무런 변화가 없어 느껴지는 심리적 지연도 있다. 또, 특정 브라우저는 이미지들을 화면에 배치하려면 크기를 알아야 하기 때문에 모든 이미지를 내려받기 전까지 사용자에게 텅 빈 화면만을 보여줄 것이다.
4.4 병렬 커넥션
HTTP는 클라이언트가 여러개의 커넥션을 맺음으로써 여러 개의 트랜잭션을 병렬로 처리할 수 있게 한다.
4.4.1 병렬 커넥션은 페이지를 더 빠르게 내려받는다.
단일 커넥션의 대역폭 제한과 커넥션이 동작하고 있지 않는 시간을 활용하면, 객체가 여러개 있는 웹페이지를 더 빠르게 내려 받을 수 있을 것이다. 또, 클라이언트의 인터넷 대역폭을 한 개의 커넥션이 다 써버리는게 아니라면
4.5.나머지 객체를 내려받는 데에 나머지 대역폭을 사용할 수 있다.
4.4.2 병렬 커넥션이 항상 더 빠르지는 않다.
클라이언트의 네트워크 대역폭이 좁을 때는 대부분의 시간을 데이터를 전송하는 데만 쓸 것이다. 여러 개의 객체를 병렬로 내려받는 경우 이 제한된 대역폭 내에서 각 객체를 전송받는 것은 느리기 때문에 성능상 장점은 거의 없어진다. 또한 다수의 커넥션은 메모리를 많이 서버하고 자체적인 성능 문제를 발생시키기도 한다.
4.4.3 병렬 커넥션은 더 빠르게 느껴질 수 있다.
병렬 커넥션이 페이지를 항상 더 빠르게 로드하는 것은 아니지만 화면에 여러 개의 객체가 동시에 보이면서 내려받고 있는 상황을 볼 수 있기 때문에 사용자는 더 빠르게 내려받고 있는 것처럼 느낄 수 있다.
4.5 지속 커넥션
웹 클라이언트는 보통 같은 사이트에 여러 개의 커넥션을 맺는다. 따라서 처리가 완료된 후에도 TCP 커넥션을 유지하여 앞으로 있을 HTTP 요청에 재사용하는 것은 효율적인 커넥션이 될 수 있다. 이를 지속 커넥션이라고 한다.
4.5.1 지속 커넥션 vs 병렬 커넥션
병렬 커넥션과 다르게 커넥션을 맺기 위한 사전 작업을 줄여주고 튜닝된 커넥션을 유지한다.
4.5.2 HTTP/1.0+의 Keep-Alive 커넥션
HTTP/1.0 브라우저는 keep-alive라는 지속 커넥션을 지원한다. 이는 커넥션을 맺고 끝는데 필요한 작업이 생략되어 시간이 단축된다.
4.5.3 Keep-Alive 동작
HTTP/1.0 keep-alive 커넥션을 구현한 클라이언트는 커넥션을 유지하기 위해서 요청에 ‘Connection:Keep-Alive’ 헤더를 포함시킨다. (keep-alive는 사용하지 않기로 결정되어 HTTP/1.1 명세에서 빠졌지만 아직도 브라우저와 서버 간에 이것이 널리 사용하고 있기 때문에 HTTP 애플리케이션은 그것을 처리할 수 있게 개발해야 한다.)
4.5.4 Keep-Alive 옵션
keep-alive 헤더는 커넥션을 유지하기를 바라는 요청일 뿐으로 응답자가 무조건 이를 따를 필요는 없다.
4.5.5 Keep-Alive 커넥션 제한과 규칙
4.5.6 Keep-Alive와 멍청한 프락시
1) 클라이언트는 프락시에 keep-alive 헤더와 함께 메시지를 보내고 (프락시와) 커넥션을 유지하기를 요청한다.
2) 멍청한 프락시는 요청받은 keep-alive 헤더를 이해하지 못하고 서버에 그대로 전달한다.
3) 서버는 프락시가 커넥션을 유지하자고 요청한 것으로 잘못 판단하고 keep-alive헤더를 포함한 메시지를 클라이언트에게 전달한다.
4) 클라이언트는 프락시가 커넥션을 유지하는 것에 동의했다고 추정한다.
5) 프락시는 서버가 커넥션을 끊기를 기다리지만 서버는 커넥션을 끊지 않는다.
6) 클라이언트는 프락시에게 다음 요청을 보내지만, 프락시는 같은 커넥션에서 다른 요청이 오는 경우는 예상하지 못하여 무시되고 브라우저는 자신이나 서버가 타임아웃이 나서 끊길 때까지 기다린다.
프락시는 Connection : keep-alive 헤더를 절대 전달해서는 안 된다.
4.5.7 Proxy-Connection 살펴보기
멍청한 프락시 문제를 해결하기 위해서 Connection 헤더 대신 Proxy-Connection 확장 헤더를 프락시에게 전달하는 방법이 있다. 프락시가 Proxy-Connection 헤더를 무조건 전달하더라도 웹서버는 그것을 무시하기 때문에 별 문제가 되지 않는다. 하지만 영리한 프락시라면 Proxy-Connection 헤더를 Connection 헤더로 바꿈으로써 원하던 효과를 얻게 될 것이다. 이 방식은 클라이언트와 서버 사이에 한 개의 프락시만 있는 경우에만 동작한다. 멍청한 프락시 양옆에 영리한 프락시가 있다면 잘못된 헤더를 또 만들어낸다.
4.5.8 HTTP/1.1의 지속 커넥션
HTTP/1.1에서는 기본적으로 모든 커넥션을 지속 커넥션으로 취급하며 트랜잭션을 끊으려면 Connection : close 헤더를 명시해야 한다. (물론 자발적으로 클라이언트와 서버는 언제든지 끊을 수 있다.)
4.6 파이프라인 커넥션
여러 가지 요청은 응답이 도착하기 전까지 큐에 쌓인다. 첫 번째 요청이 네트워크를 통해 서버로 전달되면 거기에 이어 두 번째와 세 번째 요청이 전달될 수 있다. 이는 네트워크상 왕복으로 인한 시간을 줄인다.
4.7 커넥션 끊기에 대한 미스터리
커넥션 관리에는 명확한 기준이 없다.
4.7.1 ‘마음대로’ 커넥션 끊기
보통 커넥션은 메시지를 다 보낸 다음 끊지만, 에러가 있는 상황에서는 헤더의 중간이나 다른 엉뚱한 곳에서 끊길 수 있다. 또 지속 커넥션이 일정시간 요청을 전송하지 않은 임의로 커넥션을 끊을 수 있다.
4.7.2 Content-Length와 Truncation
4.7.3 커넥션 끊기의 허용, 재시도, 멱등성
커넥션은 에러가 없더라도 언제든지 끊을 수 있다. HTTP 애플리케이션은 예상치 못하게 커넥션이 끊어졌을 때에 적절히 대응할 수 있는 준비가 되어 있어야 한다. 클라이언트가 트랜잭션 수행 중 커넥션이 끊기면 커넥션을 다시 맺고 한 번 더 전송을 시도해야 한다. 이때 요청 데이터가 전송되었는데 응답이 오기 전 커넥션이 끊기면 클라이언트는 실제로 얼만큼 요청이 처리되었는지 전혀 알 수 없으므로 요청이 여러번 중복되지 않도록 주의해야한다.
* 한 번 혹은 여러번 실행됐는지에 상관없이 같은 결과를 반환한다면 그 트랜잭션을 멱등이라 한다.
4.7.4 우아한 커넥션 끊기
- 전체 끊기 : 입력과 출력 채널을 모두 끊는다.
- 절반 끊기 : 입력 출력 둘 중 하나의 채널을 끊는다. 출력 채널을 끊는 것이 더 안전하다. 만약 클라이언트가 이미 끊긴 입력 채널에 데이터를 전송하면, 서버는 에러 메시지를 보내고 대부분의 운영체제는 이것을 심각한 에러로 취급하여 버퍼에 저장된 읽히지 않은 데이터를 모두 삭제한다. (클라이언트가? 서버가?)
일반적으로 애플리케이션이 우아한 커넥션 끊기를 구현하는 것은 애플리케이션 자신의 출력 채널을 먼저 끊고 다른 쪽에 있는 기기의 출력 채널이 끊기는 것을 기다리는 것이다.
'HTTP' 카테고리의 다른 글
[HTTP 완벽 가이드] 2-1. HTTP 메시지 (0) | 2022.06.29 |
---|---|
[HTTP 완벽 가이드] 1-2. URL과 리소스 (0) | 2022.06.22 |
[HTTP 완벽 가이드] 1-1. HTTP 개관 (0) | 2022.06.16 |