커넥션 관리 - TCP 커넥션의 종류

커넥션 관리 - TCP 커넥션의 종류

목차
  1. 병렬 커넥션, keep-alive 커넥션, 커넥션 파이프라인을 활용한 HTTP의 최적화
  2. 커넥션 관리를 위해 따라야 할 규칙들

1. 병렬 커넥션 parallel

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

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

  • 하나의 커넥션으로 객체들을 로드할 때의 대역폭 제한과
    대기시간을 줄일 수 있다면
    더 빠르게 로드할 수 있을 것이다.
  • 위 그림은 병렬 커넥션 방식을 개략적으로 보여준다.
  • html을 머저 내려받고, 남은 3개의 트랜잭션이
    각각 별도의 커넥션에서 동시에 처리된다.
  • 이미지들을 병렬로 내려받아 커넥션 지연이 겹쳐짐으로써 총 지연시간이 줄어든다.

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

  • 클라의 네트워크 대역폭이 좁을 때는 대부분 시간을 데이터 전송하는 데만 쓸 것이다.
  • 다수의 커넥션은 메모리를 많이 소모하고, 자체적인 성능 문제를 발생시킨다.
  • 브라우저는 실제로 병렬 커넥션을 사용하긴 하지만, 적은 수(대부분 4개, 최신 브라우저는 6~8개)의 병렬 커넥션만을 허용한다.
  • 서버는 특정 클라로부터 과도한 수의 커넥션이 맺어졌을 경우, 그것을 임의로 끊어버릴 수 있다.

1.3 병렬 커넥션은 더 빠르게 느껴질 수 있다.

  • 병렬 커넥션이 실제로 페이지를 더 빠르게 내려받는 것은 아니지만, 화면에 여러 개의 객체가 동시에 보이면서 내려받고 있는 상황을 볼 수 있기 때문에 사용자는 더 빠르게 내려받고 있는 것처럼 느낄 수 있다.
  • 사실 사람들은 페이지의 총 다운로드 시간이 더 걸린다 하더라도, 화면 전체에서 여러 작업이 일어나는 것으 ㄹ눈으로 확인할 수 있으면 그것을 더 빠르다고 여긴다.

2. 지속 커넥션 persistent

웹 클라는 보통 같은 사이트에 여러 개의 커넥션을 맺는다. 즉 같은 서버에 계속 요청

  • 사이트 지역성이라 부른다. site locality

처리가 완료된 후에도 계속 연결된 상태로 있는 TCP 커넥션을 지속 커넥션이라고 부른다.

  • HTTP/1.1을 지원하는 기기는 처리가 완료된 후에도
    TCP 커넥션을 유지하여 앞으로 있을 HTTP 요청에 재사용 할 수 있다.
  • 커넥션을 맺기 위한 준비 작업에 따르는 시간을 절약할 수 있다.
  • TCP의 느린 시작으로 인한 지연을 피할 수 있다.

2.1 지속 커넥션 vs 병렬 커넥션

병렬 커넥션의 단점

  1. 각 트랜잭션마다 새로운 커넥션을 맺고 끊기 때문에 시간과 대역폭이 소요된다.
  2. 새로운 커넥션은 TCP의 느린 시작 때문에 성능이 떨어진다.
  3. 실제로 연결할 수 있는 병렬 커넥션의 수에는 제한이 있다.

지속 커넥션의 장점

  1. 커넥션을 맺기 이한 사전 작업 지연을 줄여줌
  2. 튜닝된 커넥션을 유지
  3. 커넥션의 수를 줄여준다.

지속 커넥션의 단점

  1. 계속 연결된 상태로 있는 수많은 커넥션이 쌓이게 될 것이다.

지속 커넥션 + 병렬 커넥션은 가장 효과적

  1. HTTP/1.0+ 에는 keep-alive 커넥션이 있고,
    HTTP/1.1에는 지속 커넥션이 있다.

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

  • 초기의 지속 커넥션은 상호 운용과 관련된 설계에 문제가 있었지만,
    아직 많은 클라와 서버는 이 초기 keep-alive 커넥션을 사용하고 있다.
    • 설계상의 문제는 HTTP/1.1에서 수정되었다.

2.2.1 Keep-Alive 동작

  • HTTP/1.1에서는 keep-alive는 사용하지 않기로 결정

  • 아직 keep-alive 핸드셰이크가 널리 사용되고 있음.

  • HTTP/1.0 keep-alive 커넥션을 구현한 클라는 커넥션을 유지하기 위해서 요청에
    Connection: Keep-Alive 헤더를 포함시킨다.

  • 이 요청을 받은 서버는 그다음 요청도 이 커넥션을 통해 받고자 한다면, 응답 메세지에 같은 헤더를 포함시켜 응답한다.

  • 응답에 Connection: Keep-Alive가 없으면, 클라는 서버가 keep-alive를 지원하지 않으며,
    응답 메세지가 전송되고 나면 서버 커넥션을 끊을 것이라고 추정한다.

2.2.2 Keep-Alive 옵션 (헤더)

  • keep-alive 헤더는 커넥션을 유지하기를 바라는 요청일 뿐이다.
  • 클라나 서버가 keep-alive 요청을 받았다고 해서 무조건 그것을 따를 필요는 없다.
  • Connection: Keep-Alive가 있을 때만 사용 가능하다.
  • HTTP/2에서는 무시된다.

keep-alive의 동작은 Keep-Alive 헤더의 옵션들로 제어 가능하다

1
2
Keep-Alive: timeout=5, max=1000
5초 동안 1000개의 커넥션을 유지하라는 내용
  1. timeout
    • 커넥션이 얼마간 유지될 것인지를 의미한다.
    • 이대로 동작한다는 보장은 없다.
  2. max
    • 커넥션이 몇 개의 HTTP 트렌젝션을 처리할 때까지 유지될 것인지를 의미한다.
    • 이대로 동작한다는 보장은 없다.

mdn 참고

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

  • HTTP/1.0과 HTTP/2.0에서는 사용되지 않는다.

Connection: Keep-Alive

  • Connection: Keep-Alive가 있을 때만 사용 가능하다.
  • Connection: Keep-Alive가 없을 경우 서버는 요청을 처리한 후 커넥션을 끊을 것이다.
  • 클라는 Connection: Keep-Alive가 없을 경우 서버가 응답 후에 커넥션을 끊을 것임을 알 수 있다.

Content-Length과 커넥션

  • 커넥션이 끊어지기 전에 엔터티 본문의 길이를 알 수 있어야 커넥션을 유지할 수 있다.
    • 엔터티 본문이 정확한 Content-Length 값과 함께 멀티파트 미디어 형식을 가지거나,
    • 청크 전송 인코딩 chunked transfer encoding으로 인코드 되어야 함을 뜻한다.
  • keep-alive 커넥션에서 잘못된 Content-Length 값을 보내는 것은 좋지 않다.
    • 트랜잭션이 끝나는 시점에 기존 메세지의 끝과 새로운 메세지의 시작점을 정확히 알 수 없기 때문이다.

Connection 헤더

  • 프락시와 게이트웨이는 Connection 헤더의 규칙을 철저히 지켜야 한다. (명시된 헤더는 다 제거되기 때문)
  • keep-alive 커넥션은 connection 헤더를 인식하지 못하는 프락시 서버와 맺어지면 안 된다.
  • HTTP/1.0 을 따르는 기기로부터 받는 모든 Connection 헤더 필드는 무시해야 한다.
    오래된 프락시 서버로부터 실수로 전달될 수 있기 때문.

2.2.4 Keep-Alive와 dumb(멍충한) 프락시

Connection 헤더의 무조건 전달

  1. 웹 클라는 프락시에 Connection: Keep-Alive 헤더와 함께 메세지를 보내고, 커넥션을 유지하기를 요청한다.
    클라는 커넥션을 유지하자는 요청이 받아들여졌는지 확인하기 위해 응답을 기다린다.
  2. 멍청한 프락시는 Connection 헤더를 이해하지 못해서 다음 서버에 메세지를 그대로 전달한다.
    Connection헤더는 홉별 헤더다.(hop-by-hop): 오직 한 개의 전송 링크에만 적용되며, 다음 서버로 전달되어서는 안 된다.
  3. Connection: Keep-Alive를 받은 웹 서버는 커넥션을 유지하자고 요청하는 것으로 잘못 판단하게 된다.
    클라가 아닌 프락시와 커넥션을 유지하는 것에 동의하고 Connection: Keep-Alive 헤더를 포함하여 응답한다.
    하지만 프락시는 Connection를 모른다.
  4. 프락시는 Connection: Keep-Alive 헤더를 포함하고 있는 응답메세지를 클라에게 전달한다.
    클라는 이 헤더를 통해 프락시가 커넥션을 유지하는 것에 동의했다고 추정한다.
    클라와 서버는 커넥션을 유지한다고 생각하지만, 정작 프락시는 모름.
  5. Connection를 모르는 프락시는 모든 데이터를 클라에게 전달하고 서버가 커넥션을 끊기를 기다린다.
    하지만 서버는 커넥션을 유지하고, 프락시는 끊어지기 전까지 기다린다.
  6. 클라는 유지되고 있다고 생각되는 커넥션으로 다음 요청을 보내는데, 프락시는 예상치 못한 요청이 들어온 거라 프락시로부터 무시되고,
    브라우저는 아무런 응답 없이 로드 중이라는 표시만 나온다.
  7. 브라우저는 자신이나 서버가 타임아웃이 나서 커넥션이 끊길 때까지 기다린다.

프락시와 홉별 헤더
위의 잘못된 통신을 피하려면

  • 프락시는 Connection 헤더와 Connection 헤더에 명시된 헤더들은 절대 전달하면 안 된다.
  • 프락시는 Connection 헤더, Keep-Alive 헤더도 전달하면 안 된다.
  • 홉별 헤더들 역시 전달하거나 캐시하면 안 된다.
    • Proxy-Authenticate, Proxy-Connection, Transfer-Encoding, Upgrade

2.2.5 Proxy-Connection 살펴보기

  • Proxy-Connection은 비표준 헤더이다.
  • 프록시가 종종 여러 계층으로 배치되어 있기 때문에 2.6와 같은 문제를 야기한다.
    • 클라는 요청 시 Proxy-Connection 헤더 필드를 보내지 않도록 권장한다.
  1. Connection 헤더를 모르는 프락시에게 Connection 대신에 Proxy-Connection 확장 헤더를 프락시에게 전달한다.
  2. 웹 서버는 Proxy-Connection를 무시한다.
  3. 영리한 프락시라면, 의미 없는 Proxy-Connection헤더를 Connection 헤더로 바꿈으로써 원하던 효과를 얻게 된다.

RFC 7230
Proxy-Connection vs Connection header


2.3 HTTP/1.1의 지속 커넥션

  • 설계가 더 개선된 지속 커넥션을 지원한다.
  • default로 활성화되어있다.
  • HTTP/1.1 어플리케이션은 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection:close 헤더를 명시해야 한다.
  • HTTP/1.1 클라는 응답에 Connection:close 헤더가 없으면 응답 후에도 커넥션을 계속 유지하는 것으로 추정한다.
    • 클라와 서버는 언제든지 커넥션을 끊을 수 있다.
    • Connection:close를 보내지 않는 것이 서버가 커넥션을 영원히 유지하겠다는 것은 아니다.

2.3.1 지속 커넥션의 제한과 규칙

  • 클라가 요청에 Connection:close헤더를 포함해 보내면,
    클라는 그 커넥션으로 추가적인 요청을 보낼 수 없다.
  • 클라가 해당 커넥션으로 추가적인 요청을 보내지 않을 것이라면, 마지막 요청에 Connection:close 헤더를 보내야 한다..
  • 커넥션이 끊어지기 전에 엔터티 본문의 길이를 알 수 있어야 커넥션을 유지할 수 있다.
    • 엔터티 본문이 정확한 Content-Length 값을 가지거나,
    • 청크 전송 인코딩 chunked transfer encoding으로 인코드 되어야 함을 뜻한다.
  • Connection 헤더의 값과 상관없이 언제든지 커넥션을 끊을 수 있다.

3. 파이프라인 커넥션 pipelined

  • HTTP/1.1은 지속 커넥션을 통해서 요청을 파이프라이팅할 수 있다.
    • keep-alive 커넥션의 성능을 더 높여준다.
  • 여러 개의 요청은 응답이 도착하기 전까지 큐에 쌓인다.
  • 대기 시간이 긴 네트워크 상황에서 네트워크 상의 왕복으로 인한 시간을 줄여서 성능을 높여준다.

3.1 파이프라인의 제약사항

  • HTTP 클라는 커넥션이 지속 커넥션인지 확인하기 전까지 파이프라인을 이어서는 안 된다.
  • 응답은 요청 순서와 같게 와야 한다. 순서 없이 오면 순서에 맞게 정렬시킬 방법이 없다.
  • HTTP 클라는 POST 요청같이(비멱등) 반복해서 보낼 경우, 문제가 생기는 요청은 파이프라인을 통해 보내면 안 된다.
    • 에러가 발생하면 파이프라인을 통한 요청 중에 어떤 것들이 서버에서 처리되었는지 클라는 알 방법이 없다.

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

4.1 마음대로 커넥션 끊기

  • 보통 커넥션은 메세지를 다 보낸 다음 끊지만,
    에러가 있는 상황에서는 헤더의 중간이나, 다른 엉뚱한 곳에서 끊길 수 있다.

4.2 Content-Length와 Truncation(끊기)

  • 각 HTTP 응답은 본문의 정확한 크기 값을 가지는 Content-Length 헤더를 가지고 있어야 한다.
  • 클라나 프락시가 커넥션이 끊어졌다는 HTTP 응답을 받은 후,
    실제 전달된 엔터티의 길이와 Content-Length의 값이 일치하지 않거나
    Content-Length 자체가 존재하지 않으면, 수신자는 데이터의 정확한 길이를 서버에게 물어봐야 한다.

4.3 커넥션 끊기의 허용, 재시도

  • 커넥션은 에러가 없더라도 언제든 끊을 수 있다.
  • 클라가 트랜잭션을 수행 중에 전송 커넥션이 끊기게 되면, 클라는 그 트랜잭션을 재시도 하더라도, 문제가 없다면(멱등성) 커넥션을 다시 맺고 한 번 더 전송을 시도해야 한다.
    • 이건 파이프라인 커넥션에서는 어렵다.
    • 클라는 여러 요청을 큐에 쌓아 놓을 수 있지만,
      서버는 아직 처리되지 않고 스케줄이 조정되어야 하는 요청들을 남겨둔 채로 커넥션을 끊어버릴 수도 있다.

4.4 우아한 커넥션 끊기

  • TCP 커넥션은 그림처럼 양방향이다.
  • TCP 커넥션의 양쪽에는 데이터를 읽거나 쓰기 위한 입력 큐와 출력 큐가 있다.

전체 끊기와 절반 끊기

  • 전체 끊기: close()를 호출하면 TCP 커넥션의 입력 채널과 출력 채널의 커넥션을 모두 끊는다.
  • 절반 끊기: shutdonw()을 호출하면 입력 채널이나 출력 채널 중에 하나만을 개별적으로 끊는다.

TCP 끊기와 리셋 에러

잘 모르겠다.

  • 보통은 커넥션의 출력 채널을 끊는 것이 안전하다.
    • 커넥션의 반대편에 있는 기기는 모든 데이터를 버퍼로부터 읽고 나서, 데이터 전송이 끝남과 동시에 당신이 커넥션을 끊었다는 것을 알게 될 것이다.
  • 클라에서 더는 데이터를 보내지 않을 것임을 확신할 수 없는 이상, 커넥션의 입력 채널을 끊는 것은 위험하다.
  • 서버의 운영체제는 TCP ‘connection reset by peer’ 메세지를 클라에게 보낼 것이다.
    • 대부분의 운영체제는 이것을 심각한 에러로 취급하여 버퍼에 저장된, 아직 읽히지 않은 데이터를 모두 삭제한다.
    • 파이프라인 커넥션에서는 이러한 상황은 더 악화된다.

우아하게 커넥션 끊기

  • 우아하게 커넥션 끊기를 구현하는 것은 어플리케이션 자신의 출력 채널을 먼저 끊고, 다른 쪽에 있는 기기의 출력 채널이 끊기는 것을 기다리는 것이다.
  • 양쪽에서 더는 데이터를 전송하지 않을 것이라고 알려주면 커넥션은 리셋의 위험 없이 온전히 종료된다.
  • 상대방이 절반 끊기를 구현했다는 보장도 없어서, 출력 채널에 절반 끊기를 하고 난 후에도 데이터나 스트림의 끝을 식별하기 위해 입력 채널에 대해 상태 검사를 주기적으로 해야 한다.




참고자료

📚