본문 바로가기
도서기록/HTTP 완벽가이드

4장 커넥션 관리

by 코엘리 2024. 12. 20.
반응형

4.1 TCP 커넥션

  • 전 세계 모든 HTTP 통신은 TCP/IP 통신을 통해 이루어진다.
  • 일단 커넥션이 맺어지게 되면 메시지들은 손실/손상 되지 않고 안전하게 전달된다.

일반 URL 로부터 데이터를 가져오는 순서는 대략적으로 다음과 같다.

  1. 브라우저가 URL에 대하여 호스트명을 추출한다.
  2. 브라우저가 이 호스트 명에 대한 IP 주소를 찾는다.
  3. 브라우저가 포트 번호를 얻는다.
  4. 브라우저가 IP와 포트번호로 TCP 커넥션을 생성한다.
  5. 브라우저가 서버로 HTTP 요청 메시지를 보낸다.
  6. 브라우저가 HTTP 응답 메시지를 읽는다.
  7. 브라우저가 커넥션을 끊는다.

4.1.1 신뢰할 수 있는 데이터 전송 통로인 TCP

  • HTTP 커넥션은 몇몇 사용규칙을 제외하고는 TCP 커넥션에 불과하다.
  • TCP 는 HTTP 에게 신뢰할 만한 통신 방식을 제공한다.
  • TCP 커넥션의 한쪽에 있는 바이트들은 반대쪽으로 순서에 맞게 정확하게 전달이 된다.

4.1.2 TCP 스트림은 세그먼트로 나뉘어 IP 패킷을 통해 전송된다.

  • TCP는 IP 패킷을 통해 데이터를 전송한다.
  • HTTP 가 메시지를 전송하고자 할 경우 현재 연결된 TCP 커넥션을 통해서 메시지 데이터 내용을 순서대로 보낸다.
  • TCP는 세그먼트라는 단위로 데이터 스트림을 잘게 나누고, 세그먼트를 IP 패킷이라고 불리는 봉투에 담아서 데이터를 전달한다.
  • 각 TCP 세그먼트는 하나의 IP 주소에서 다른 IP 주소로 IP 패킷에 담아 전달된다.
  • IP 패킷은 IP 패킷 헤더, TCP 세그먼트 헤더, TCP 데이터 조각을 포함한다.
    • IP 헤더는 발신지와 목적지 IP 주소, 크기, 기타 플래그를 갖는다.
    • TCP 세그먼트 헤더는 TCP 포트 번호, TCP 제어 플래그, 그리고 데이터의 순서와 무결성을 검사하기 위해 사용되는 숫자 값을 포함한다.

4.1.3 TCP 커넥션 유지하기

  • 컴퓨터는 항상 TCP 커넥션을 여러개 가지고 있는데, TCP 커넥션은 <발신지 IP 주소, 발신지 포트, 수신지 IP 주소, 수신지 포트> 네 가지 값으로 식별한다.

4.1.4 TCP 소켓 프로그래밍

  • 소켓 API는 TCP 종단 데이터 구조를 생성하고, 원격 서버의 TCP 종단에 그 종단 데이터 구조를 연결하여 데이터 스트림을 읽고 쓸 수 있다.
    클라이언트 서버
    s1. 새로운 소켓을 만든다
    s2. 80 포트로 소켓을 묶는다
    s3. 소켓 커넥션을 허가한다
    s4. 커넥션을 기다린다
    c1. ip주소와 port를 얻는다
    c2. 새로운 소켓을 생선한다
    c3. 서버의 IP:포트로 연결한다
    s5. 애플리케이션 커넥션 통지
    s6. 요청을 읽기 시작한다
    c4. 성공적으로 연결
    c5. http 요청 보낸다
    s7. http 요청 메시지를 처리한다
    c6. http 응답 기다린다
    s8. http 응답을 보낸다
    c7. http 응답을 처리한다
    c8. 커넥션을 닫는다 s9. 커넥션을 닫는다

4.2 TCP 성능에 대한 고려

  • HTTP는 TCP 바로 위에 있는 계층이기 때문에 HTTP 트랜잭션의 성능은 TCP 성능에 영향을 받는다.

4.2.1 HTTP 트랜잭션 지연

  • 트랜잭션을 처리하는 시간은 TCP 커넥션 설정, 요청 전송, 응답 전송에 비하면 짧다. -> HTTP 지연은 TCP 네트워크 지연 때문에 발생한다.

4.2.2 성능 관련 주요 요소

TCP 관련 지연들에 대해서 아래에서 정리한다.

4.2.3 TCP 커넥션 핸드셰이크 지연

새로운 TCP 커넥션을 열 때면 켜넥션 조건을 맞추기 위해 연속으로 IP 패킷을 교환한다.

  1. 클라이언트는 새로운 커넥션 생성을 위해 작은 TCP 패킷을 서버에 보낸다. 이는 'SYN'이라는 패킷이며 생성 요청을 뜻한다.
  2. 서버가 그 커넥션을 받으면 커넥션 요청이 받아들여졌음을 의미하는 'SYN'과 'ACK' 플래그를 포함한 TCP 패킷을 클라이언트에게 보낸다.
  3. 마지막으로 커넥션이 잘 맺어졌음을 알리기 위해 클라이언트는 서버에게 확인응답 신호를 보낸다.

HTTP 트랜잭션이 아주 큰 데이터를 주고받지 않는 평범한 경우에는 SYN/SYN+ACK 핸드셰이크가 눈에 띄는 지연을 발생시킨다.

4.2.4 확인응답 지연

  • 인터넷 자체가 패킷 전송을 보장하지 않기 때문에 TCP는 자체적인 확인 체계를 가진다.
  • 각 TCP 세그먼트는 순번과 데이터 무결성 체크섬을 가진다.
  • 각 세그먼트의 수신자는 세그먼트 수신시 작은 확인응답 패킷을 송신자에게 반환하는데, 송신자가 이 응답 메시지를 받지 못하면 패킷에 오류가 있다고 판단하고 데이터를 다시 전송한다.
  • 확인응답은 크기가 작아서 TCP는 같은 방향으로 송출되는 데이터 패킷에 확인응답을 '편승(piggyback)'시킨다.
    • 확인응답이 편승되는 경우를 늘리기 위하여 많은 TCP 스택은 확인응답 지연 알고리즘을 구현하는데, 이는 특정시간(0.1s-0.2s)동안 버퍼에 저장해두고 확인응답을 편승시키기 위한 송출 데이터 패킷을 찾는다. 만약 송출 데이터 패킷을 찾지 못하면 별도 패킷으로 전송된다.
  • HTTP 동작 방식은 요청/응답 두 가지 형식으로만 이루어져 편승기회가 작다. 따라서 지연이 자주 발생한다.

4.2.5 TCP 느린 시작(slow start)

  • TCP 의 데이터 전송 속도는 TCP 커넥션이 만들어진 지 얼마나 지났는지에 따라 달라진다.
  • TCP 커넥션은 처음에는 커넥션의 최대 속도를 제한하고, 데이터가 성공적으로 전송됨에 따라서 속도 제한을 높여간다.
    • TCP 느린 시작은 TCP 가 한 번에 전송할 수 있는 패킷을 제한한다.
  • 새로운 커넥션은 '튜닝'된 커넥션보다 느리기 때문에 HTTP는 이미 존재하는 커넥션을 재사용하는 기능이 있다.

4.2.6 네이글(Nagle)알고리즘과 TCP_NODELAY

  • 네트워크 효율을 위해서 패킷을 전송하기 전에 많은 양의 TCP 데이터를 한 개의 덩어리로 합친다.
  • 세그먼트가 최대 크기가 되지 않으면 전송을 하지 않는다. 다만 다른 모든 패킷이 확인응답을 받았을 경우에는 예외이다.
  • 성능 관련 문제
    • 크기가 작은 HTTP 메시지는 패킷을 채우지 못하여 지연된다.
    • 확인응답 지연과 함께 스인다면, 네이글 알고리즘은 확인 응답이 도착할 때까지 데이터를 전송하지 않고 확인응답 지연 알고리즘은 확인 응답을 100-200 밀리초 지연시킨다.

4.2.7 TIME_WAIT의 누적과 포트 고갈

  • TCP 커넥션의 종단에서 TCP 커넥션을 끊으면 종단에서는 커넥션의 IP 주소와 포트 번호를 control block 이라 불리우는 메모리에 기록해 둔다.
    • 이 정보는 새로운 TCP 커넥션이 일정 시간동안 생성되지 않게 하기 위한 것이다.
    • 이전 커넥션과 관련된 패킷이 동일한 주소와 포트 번호를 가지는 새로운 커넥션에 삽입되는 문제를 방지한다.
  • 일반적으로 문제되지 않지만, 성능시험을 하는 경우 문제가 생긴다.
    • 성능 시험 시 <발신지 IP 주소, 발신지 포트, 목적지 IP 주소, 목적지 포트> 중 발신지 포트만 주로 변경할 수 있는데, 예를 들어 발신지 포트 수가 60,000개로 제한되어 있고 120초 동안 커넥션이 재사용될 수 없으면 초당 500개로 커넥션이 제한된다.

4.3 HTTP 커넥션 관리

4.3.1 Connection 헤더

  • HTTP Connection 헤더 필드는 커넥션 토큰을 쉼표로 구분하여 가지고 있으며 그 값들은 다른 커넥션에 전달되지 않는다.
  • Connection 헤더의 토큰 종류
    • HTTP 헤더 필드명: 이 커넥션에만 해당되는 헤더들 나열
    • 임시적인 토큰 값: 커넥션에 대한 비표준 옵션
    • close 값: 작업이 완료되면 커넥션이 종료되어야 함을 의미
  • Connection 헤더에 있는 모든 필드는 다른 곳에 전달되는 시점에 삭제되어야 한다.
    • Connection 헤더와 Connection 헤더에 기술되어 있는 모든 헤더를 삭제해야 한다.
  • Connection 헤더와 마찬가지로 Proxy-Authenticate, Proxy-Connection, Transfer-Encoding, Upgrade 와 같은 헤더도 다른 곳 전달 시점에 삭제되어야 한다.

4.3.2 순차적인 트랜잭션 처리에 의한 지연

  • 커넥션 관리가 제대로 이루어지지 않으면 TCP 성능이 안좋아질 수 있다.

4.4 병렬 커넥션

  • HTTP 는 클라이언트가 여러 개의 커넥션을 맺음으로써 여러 개의 HTTP 트랜잭션을 병렬로 처리할 수 있게 한다.

4.4.1 병렬 커넥션은 페이지를 더 빠르게 내려받는다.

  • 각 커넥션의 지연 시간을 겹치게 하면 총 지연시간을 줄일 수 있다. 클라이언트의 인터넷 대역폭을 한 개의 커넥션이 다 써버리는 것이 아니고, 나머지 객체를 내려받는데 남은 대역폭을 사용하여 별도의 커넥션에서 동시에 처리되게 한다.

4.4.2 병렬 커넥션이 항상 더 빠르지는 않다.

  • 클라이언트 대역폭이 좁은 경우에는 효과가 미비하다.
  • 다수의 커넥션은 메모리를 많이 소모하고 자체적인 성능 문제를 발생 시킨다.
  • 클아이언트가 수백개의 커넥션을 열어도 서버는 다른 여러 사용자 처리를 함께 받기 때문에 수백개의 커넥션을 허용하는 경우는 드물다.

4.5 지속 커넥션

  • HTTP/1.1을 지원하는 기기는 처리가 완료된 후에도 TCP 커넥션을 유지하여 앞으로 있을 HTTP 요청에 재사용할 수 있다.
  • 처리가 완료된 후에도 계속 연결된 상태로 있는 TCP 커넥션을 지속 커넥션이라고 부른다.
  • 비지속 커넥션은 각 처리가 끝날 때마다 커넥션을 끊지만, 지속 커넥션은 클라이언트나 서버가 커넥션을 끊기 전까지는 트랜잭션 간에도 커넥션을 유지한다.

4.5.1 지속 커넥션 vs. 병렬 커넥션

  • 병렬 커넥션의 단점
    • 각 트랜잭션 마다 새로운 커넥션을 맺고 끊기 때문에 시간과 대역폭이 소요된다.
    • 각각의 새로운 커넥션은 TCP 느린 시작으로 성능이 떨어진다.
    • 병렬 커넥션 수의 제한이 있다.
  • 지속 커넥션 장점
    • 커넥션을 맺기 위한 사전 작업을 줄여준다.
    • 튜닝된 커넥션을 유지하며
    • 커넥션 수를 줄여준다.

4.5.2 HTTP/1.0+의 Keep-Alive 커넥션

  • keep-alive 커넥션의 설계상 문제는 HTTP/1.1 에서 수정되었다.

4.5.3 Keep-Alive 동작

  • keep-alive는 사용하지 않기로 결정되어 HTTP/1.1 명세에서 빠졌다.
  • HTTP/1.0 keep-alive 커넥션을 구현한 클라이언트는 커넥션을 유지하기 위해서 요청에 Connection:Keep-Alive 헤더를 포함시킨다.
  • 이 요청을 받은 서버는 이 요청에 응한다면 응답에 같은 헤더를 포함시켜 응답한다.
  • 응답에 Connection: Keep-Alive 헤더가 없으면 클라이언트는 서버가 keep-alive를 지원하지 않으며, 응답 이후 서버 커넥션을 끊을 것이라고 추정한다.

4.5.4 Keep-Alive 옵션

  • Keep Alive 헤더는 커넥션을 유지하기를 바라는 요청일 뿐이다.
  • max 파라미터는 커넥션이 몇 개의 HTTP 트랜잭션을 처리할 때까지 유지될 것인지를 의미한다.

4.5.5 Keep-Alive 커넥션 제한과 규칙

  • keep-alive 는 HTTP/1.0에서 기본으로 사용되지 않는다.
  • 커넥션이 끊어지기 전에 엔터티 본문의 길이를 알 수 있어야 커넥션을 유지할 수 있다.
    • Keep-Alive 연결을 유지하려면 서버와 클라이언트가 "이 데이터가 여기까지가 끝이다"를 정확히 알아야한다. 그렇지 않으면 다음 메시지로 넘어가야 할 시점을 알 수 없기 때문이다.
  • 기술적으로 HTTP/1.0을 따르는 모든 Connection 헤더 필드는 무시해야 한다.

4.5.6 Keep-Alive 와 멍청한 프락시

  • Connection 헤더의 무조건 전달
    • 프락시는 Connection 헤더를 이해하지 못해서 해당 헤더를 삭제하지 않고 요청 그대로 다음 프락시에 전달한다.
  1. 웹 클라이언트는 프락시에 Connection: Keep-Alive 헤더와 함께 메시지를 보낸다.
  2. 멍청한 프락시는 요청받은 HTTP 의 헤더를 이해하지 못하고 서버에 전송한다.
  3. 웹 서버는 프락시가 커넥션을 유지하자고 요청하는 것으로 판단한다.
  4. 웹 서버는 프락시와 keep-alive 커넥션이 맺어져 있는 상태로 keep-alive 규칙에 맞게 통신을 하는 것으로 판단한다.
  5. 멍청한 프락시는 서버로부터 받은 Connection: Keep-Alive 헤더를 응답 메시지에 클라이언트에게 전달한다.
  6. 프락시는 keep-alive 를 모르지만 모든 데이터를 그대로 클라이언트에게 전달하고 나서 서버가 커넥션을 끊기를 기다린다.
  7. 클라이언트가 응답 메시지를 받으면 다음 요청을 보내기 시작하는데, 프락시는 같은 커넥션상에 다른 요청이 오는 경우를 예상하지 못하기 때문에 그 요청은 무시되고 브라우저는 아무런 응답없이 로드중만 보여준다.
  8. 서버 타임 아웃이 발생한다.

4.5.7 Proxy-Connection 살펴보기

  • 클라이언트의 요청이 중개서버를 통해 이루어지는 것을 해결하기 위해서 Proxy-Connection 헤더를 사용한다.
  • 영리한 프락시는 Proxy-Connection 헤더를 Connection 헤더로 바꿈으로써 원하던 효과를 얻는다.
  • 하지만 멍청한 프락시의 양옆에 영리한 프락시가 있다면 문제는 동일하게 발생된다.

4.5.8 HTTP/1.1 지속 커넥션

  • HTTP/1.1 에서는 keep-alive 커넥션을 지원하지 않는 대신 지속 커넥션을 지원한다.
  • 지속커넥션은 기본적으로 비슷하지만 더 발달되었다.
  • HTTP/1.1 은 기본으로 지속 커넥션을 활성화한다.
  • HTTP/1.1 애플리케이션은 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection:close 를 명시해야 한다.
  • HTTP/1.1 클라이언트는 응답에 Connection: close 헤더가 없으면 응답 후에도 커넥션을 계속 유지하자는 것으로 추정한다.

4.6 파이프라인 커넥션

  • HTTP/1.1은 지속 커넥션을 통해서 요청을 파이프라이닝 할 수 있다.
  • 여러 개의 요청은 응답이 도착하기 전까지 큐에 쌓인다.
  • HTTP 클라이언트는 커넥션이 지속 커넥션인지 확인하기 전까지는 파이프라인을 이어서는 안된다.
  • HTTP 응답은 요청 순서와 같게 와야 한다. (HTTP는 순서를 정렬시킬 방법이 없다)
  • 클라이언트는 예상치 못하게 끊긴 커넥션을 다시 맺고 요청을 보낼 수 있어야 한다.
  • HTTP 클라이언트는 POST 요청처럼 반복해서 보낼 경우 문제가 생기는 요청은 파이프라인을 통해 보내면 안된다.

4.7 커넥션 끊기에 대한 미스터리

명환한 기준이 없다.

4.7.1 '마음대로' 커넥션 끊기

  • 어떠한 HTTP 클라이언트, 서버, 혹은 프락시든 언제든지 TCP 전송 커넥션을 끊을 수 있다.

4.7.2 Content-Length 와 Truncation

  • 각 HTTP 응답은 본문의 정확한 크기 값을 가지는 Content-Length 헤더를 가지고 있어야 한다.
  • 프락시는 Content-Length 를 정정하려하지 말고 메시지를 받은 그대로 전달해야 한다.

4.7.3 커넥션 끊기의 허용,재시도,멱등성

  • 커넥션은 언제든지 끊을 수 있고, 커넥션이 끊어졌을 때 적절히 대응할 수 있는 준비가 되어 있어야 한다.
  • 클라이언트가 트랜잭션 수행 중 전송 커넥션이 끊기면 클라이언트는 다시 재전송을 시도한다.
  • 클라이언트는 멱등이 아닌 요청은 파이프라인을 통해 요청하면 안된다.
  • 비멱등인 메서드나 순서에 대해 에이전트가 요청을 다시 보낼 수 있는 기능을 제공하더라도 자동으로 재시도 하면 안된다.

4.7.4 우아한 커넥션 끊기

  • TCP 커넥션의 양쪽에는 입력 큐와 출력 큐가 있다.
  • close()를 호출하면 두 커넥션이 모두 끊긴다.
  • shotdown()을 호출하면 두 출력 중 개별적 끊기가 가능하며 이를 '절반 끊기'라고 한다.

TCP 끊기와 리셋 에러

  • 단순한 HTTP 애플리케이션은 전체 끊기만을 사용할 수 있다.
  • 예상치 못한 쓰기 에러 발생을 위해 '절반 끊기'를 종종 사용한다.
  • 보통은 커넥션의 출력 채널을 끊는 것이 안전하다.
  • 클라이언트는 더는 데이터를 보내지 않을 것임을 확신할 수 없는 이상 입력 채널을 끊는 것은 위험하다. 만약 클라이언트에서 이미 끊긴 입력 채널에 데이터를 전송하면 TCP 'connection reset by peer' 응답을 받게 된다.

우아하게 커넥션 끊기

  • 우아하게 커넥션 끊기를 구현하는 것은 애플리케이션 자신의 출력 채널을 먼저 끊고 다른 쪽에 있는 기기의 출력 채널이 끊기는 것을 기다리는 것이다.
  • 출력 채널에 절반 끊기를 하고 난 후, 데이터 스트림의 끝을 식별하기 위해 입력 채널에 대해 상태 검사를 주기적으로 해야 한다.
반응형

'도서기록 > HTTP 완벽가이드' 카테고리의 다른 글

3장 HTTP 메서드  (2) 2024.12.06