kakasoo

[HTTP] 11. 병렬 커넥션과 지속 커넥션 본문

프로그래밍/HTTP

[HTTP] 11. 병렬 커넥션과 지속 커넥션

카카수(kakasoo) 2020. 10. 24. 14:55
반응형

4.3 HTTP 커넥션 관리

다시 본론으로 돌아가 HTTP에 대해서 알아보자. 여기서부터는 커넥션을 생성하고 최적화하는 HTTP 기술을 설명한다.

4.3.1 흔히 잘못 이해하는 Connection 헤더

HTTP는 클라이언트와 서버 사이에 프락시 서버, 캐시 서버 등이 놓이는 것을 허용한다. 이 경우 메시지는 중개 서버들을 거쳐서 오간다. 이 때, 특정 경우에 한하여 두 대상에서만 전달할 값이 있을 수 있다.

예컨대, HTTP Connection 헤더 필드는 커넥션 토큰을 쉼표로 구분하여 갖고 있는데, 그 값들은 다른 커넥션에 전달되지 않는다. 이 때 Connection: close 라고 명시할 수 있다.

Connection 헤더에는 다음 세 가지의 토큰이 전달될 수 있다.

  1. HTTP 헤더 필드 명은 이 커넥션에만 해당하는 헤더들을 나열한다.
  2. 임시적인 토큰 RKQT은 커녞쎤예 대한 비표준 옵션을 의미한다.
  3. close 값은 커넥션이 , 작업이 완료되면 종료되어야 함을 의미한다.

이 부분은 추후 부록 C를 보고 재 정리하도록 한다.

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

한 페이지를 만들기 위해서 4번의 트랙잭션이 필요한 사이트가 있다고 할 때, 각 트랙잭션이 새로운 커넥션을 필요로 한다면 커넥션을 맺는 데 필요한 지연과, 느린 시작 지연이 발생한다.

여기에 사용자의 심리적인 지연 ( 사람은 이미지가 순차적으로 뜨는 것보다, 조금 더 느리더라도 한꺼번에 뜨는 것을 선호하고, 심지어 후자가 더 빠르다고 느낀다.) 도 있다.

여기서 HTTP 커넥션 성능을 향상 시킬 기술들을 알아보자.

  • 병렬(parallel) 커넥션 : 여러 개의 TCP 커넥션을 통한 동시 HTTP 요청
  • 지속(persistence) 커넥션 : 커넥션을 맺고 끊는 데서 발생하는 지연을 없애기 위한 TCP 커넥션의 재사용
  • 파이프라인(pipelined) 커넥션 : 공유 TCP 커넥션을 통한 병렬 HTTP 요청
  • 다중(multiplexed) 커넥션 : 요청과 응답에 대한 중재 (아직 실험적인 기술이다.)

4.4 병렬 커넥션

브라우저는 HTML 페이지에 이어 객체들을 하나씩 내려받는 방식으로 웹 페이지를 보여줄 수 있다. 하지만 이 방식은 느리다.

병렬 커넥션은 클라이언트가 여러 개의 커넥션을 맺음으로서 G한 번예 여러 개의 트랙잭션을 처리할 수 있게 한다.

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

앞에 이미 서술되어 있고, 내용 자체도 당연할 수 있는 내용이므로 생략.

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

클라이언트와 서버 간의 대역폭이 좁을 때에는 병렬 커넥션보다 그냥 보내는 게 더 빠를 수도 있다. 이 부분은 그림을 참고하는 것이 이해가 훨씬 빠를 것이다.

애초에, 위의 문제가 없다고 하더라도 서버는, 하나의 클라이언트에게 병렬적인 커넥션 제공을 못하는 경우가 더 많다. 이 경우 다른 사용자들을 무시해야 하기 때문이다.

서버는 한 클라이언트에게 4개 정도의 커넥션만을 제공할 것이고, 다른 사용자가 몰리면 임의로 잘라버릴 수 있다.

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

말 그대로의 의미다. 위에서 말한 것과 같이, 사람들은 병렬적으로 동작하는 것을 더 빠르다고 느낄 수가 있다.

4.5 지속 커넥션

보통 클라이언트는, 하나의 사이트와 여러 개의 커넥션을 맺는다. 리소스나 하이퍼링크가 그 사이트에 있는 경우가 많기 때문이다. 이러한 속성을 사이트 지역성이라고 부른다.

따라서 TCP 커넥션을 유지하여 앞으로 필요할 때마다 재사용할 수 있다. 이러한 것을 지속 커넥션이라고 부른다. 이 경우 커넥션은 클라이언트나 서버가 끊고자 하기 전에는 계속 유지된다.

지속 커넥션은 TCP의 느린 시작으로 인한 지연을 피함으로써 더 빠르게 데이터를 전송한다.

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

병렬 커넥션에는 3가지의 단점이 있다.

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

지속 커넥션은 이에 비해 몇 개의 장점이 있다. 커넥션을 맺기 위한 작업과 지연을 줄이고, 튜닝된 커넥션을 유지하고, 커넥션의 수는 줄여준다.

하지만 커넥션을 잘못 관리하면 커넥션은 점차 쌓이게 될 것이고, 이는 로컬 리소스와 클라이언트, 서버의 리소스에 불필요한 소모를 방치하게 된다.

지속 커넥션은 병렬 커넥션과 함께 사용될 때가 가장 효과적이다. 오늘날 웹 애플리케이션은 적은 수의 병렬 커넥션만 가지고, 그것을 유지한다.

지속 커넥션에는 다시 2가지의 타입이 있는데, 하나는 HTTP/1.0+의 keep-alive 커넥션이고, 다른 하나는 HTTP/1.1의 지속 커넥션이다. (종류와 명칭 명이 같은 것으로 보인다.)

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

많은 HTTP/1.0 브라우저와 서버들은 다소 실험적이었던 keep-alive 커넥션이라는 지속 커넥션을 지원하기 위해서 확장되었다. 아직 초기 상태라 상호운용과 관련된 설계에 문제가 있었음에도 널리 사용됐다.

현재도 많은 클라이언트와 서버가 이 초기 keep-alive를 사용하고 있다. 이 문제는 HTTP/1.1 부터 해결되었다. (지속 커넥션이 개발된 이유다.)

이 keep-alive의 장점 역시 그림을 봐야 이해하기가 쉽다. 다만 글로써 설명하건대, 커넥션을 맺고 끊는데 필요한 작업이 없어서 시간이 단축된 그림이라고 말하겠다.

4.5.3 Keep-Alive 동작

keep-alive는 HTTP/1.1 명세에서는 제외되었다. 이제 사용하지 않기로 결정된 것이다. 앞서 keep-alive의 문제가 해결되었다고 했는데, 그건 keep-alive를 대체할 기술이 개발되었기 때문이다.

후술하도록 하고, 그럼에도 불구하고 우리는 keep-alive를 배워야 하는데, 그 이유는 앞서 말했다시피 아직도 keep-alive가 많이 사용되고 있기 때문이다.

HTTP 애플리케이션은 이를 처리할 수 있어야 한다.

HTTP/1.0 keep-alive 커넥션을 구현한 클라이언트는 커넥션을 유지하기 위해 서버에 Connection:Keep-Alive 헤더를 보낸다. 서버는 또같은 것을 헤더에 포함시켜서 돌려주어야 한다.

이를 통해 커넥션을 끊지 않을 거라는 것을 추정할 수 있게 되고, 반대로 서버가 헤더를 포함시키지 않고 돌려줬다면 이에 대해 지원해주지 않는 서버, 즉 커넥션이 끊길 거라고 추정할 수 있게 된다.

4.5.4 Keep-Alive 옵션

Keep-Alive 헤더는 커넥션을 유지해달라는 요청이다. 서버는 이를 무조건 따를 필요가 없다. 무시해도 되고, 나중에 잘라 버려도 된다. 또 커넥션 수를 제한하는 옵션도 가능하다.

  • keep-alive의 동작은 헤더의 쉼표로 구분된 옵션으로 제어한다.
  • timeout 파라미터는 커넥션이 유지되는 시간을 의미한다. 물론 보장할 수는 없다.
  • max 파라미터는 커넥션이 몇 개의 트랜잭션을 교환할 때까지 유지할 것인가를 의미한다. 물론 보장할 수는 없다.
  • Keep-Alive 헤더는 진단이나 디버깅을 목적으로 하는, 임의의 속성들을 지원하기도 한다. 이는 이름 = 값의 형태로 작성된다.
Connection: Keep-Alive
Keep-Alive: max=5, timeout=120

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

앞서 말한 것과 중복되는 내용도 있다. 일단 기본적으로, HTTP/1.1 명세에는 없다는 점, 클라이언트가 헤더를 보내고, 서버가 응답한다는 점. 응답하지 않을 수도 있고, 보장해주지도 않는다는 점.

  • 커넥션이 끊어지기 전에 엔터티 본문의 길이를 알아야 커넥션을 유지할 수 있다. 커넥션이 끝날 때, 기존 메시지의 끝과 새 메시지의 시작점을 알아야 하기 때문이다.
  • 프락시와 게이트웨이는 Connection 헤더의 모든 규칙을 철저히 지켜야 한다. 단, 캐시에 넣기 전에는 Connection 헤더에 명시된 모든 헤더 필드와 Connection 헤더를 제거해야 한다.
  • keep-alive 커넥션은 Connection 헤더를 인식하지 못하는 프락시를 피해야 한다. (현실적으로는 어렵다.)
  • 기술적으로 HTTP/1.0을 따르는 모든 기기로부터 Connection 헤더 필드가 오면 무시하는 것이 옳다. 실수로 전달된 것일 가능성도 있기 때문이다.
  • 클라이언트는 응답 전체를 모두 받기 전에 커넥션이 끊어질 경우, 요청을 다시 보낼 수 있게 준비되어 있어야 한다.

4.5.6 Keep-Alive와 멍청한(dumb) 프락시

멍청한 프락시에서 keep-alive를 사용할 때 생기는 문제.

Connection 헤더의 무조건 전달

웹 클라이언트의 요청에 Connection 헤더가 있으면, 서버가 그리고 이 요구사항을 들어줄 생각이 있으면 똑같은 헤더를 반환한다, 이전에 말한 내용이다.

문제는 프락시가, 헤더를 이해하지도 못하면서 있는 그대로 다음 프락시에게 전달해준다는 점이다. 중간에 이런 멍청한 프락시 하나가 끼어있는 네트워크가 있다고 해보자.

  1. 클라이언트는 Connection 헤더를 프락시 서버에게 보낸다. Connection 헤더는 hop-by-hop 헤더다. 다른 곳에 전달되어서는 안 된다.
  2. 멍청한 프락시는 자신에게 Connection을 유지해달라는 요청인지도 모르고 다음 프락시에게 보낸다. (다음 프락시는 서버라고 가정하자.)
  3. 서버는 멍청한 프락시에게 Connection 헤더를 받고, Connection을 유지해달라는 의미로 해석한다. 그래서 유지해준다. 즉 Connection 헤더를 돌려준다!
  4. 멍청한 프락시는 응답을 받고 다시 클라이언트에게 응답을 해줘야 한다. 그런데 서버가 준 응답에는 keep-alive가 있다. 그걸 이해하지도 못하면서 그대로 클라이언트에게 돌려준다!
  5. 클라이언트는 keep-alive가 성공했다고 생각한다!
  6. 멍청한 프락시는 모든 임무가 완수되었다고 생각하고 서버 측이 커넥션을 끊기를 기다린다. 그런데 서버는 끊지 않는다. 그래서 계속 기다린다.
  7. 이 때 만일 클라이언트가 메세지를 보내면, 멍청한 프락시는 한 커넥션에서 2개 이상의 트랜잭션이 생기는 것을 이해 못하기 때문에 새로운 요청을 무시한다.
  8. 브라우저는 자신(클라이언트)이나 서버가 타임아웃이 걸려서 커넥션이 끊길 때까지 기다린다.

프락시와 홉별 헤더

위의 Connection 헤더의 무조건 전달과 같은 오류를 피하기 위해서는, 프락시는 Connection 헤더와, Connection 헤더가 명시된 헤더들을 절대 전달하면 안 된다.

Connection 헤더가 들어오면, Connection 헤더 뿐만 아니라, Keep-Alive 란 이름의 헤더도 전달하면 안 된다는 의미이다.

또한 hop-by-hop 헤더들 역시 전달하거나 캐시해서는 안 된다.

4.5.7 Proxy-Connection 살펴보기

넷스케이프(현재 모질라와 파이어폭스의 전신 쯤 되는 회사)의 브라우저 및 프락시 개발자는 모든 웹 애플리케이션이 HTTP 최신 버전을 지원하지 않아도 위 문제를 해결할 방법을 모색하였다.

그 방법은, Proxy-Connection 이라는 별도 헤더를 사용해보는 것이다. (모든 상황에서 해결책이 되줄 순 없다.)

이는 멍청한 프락시들이, 위 헤더를 그대로 전달하더라도 서버 측에서 무시하게 만드는 방법이다.

대신, 영리한 프락시들에게는 저 헤더가 의미하는 바를 가르쳐줘서(?) Proxy-Connection을 받으면 Connection으로 변환해서 서버에게 전달하게 하는 것이다.

문제는 이 영리한 프락시 이전이나 이후, 둘 중 하나에 멍청한 프락시가 있을 경우다. 이 경우에는 앞서 서술한 문제가 그대로 재현된다.

Proxy-Connection은 Connection 헤더의 무조건 전달을, 단일 프락시인 경우에만 해결할 수 있는 방법이다.

4.5.8 HTTP/1.1의 지속 커넥션

HTTP/1.1은 keep-alive 커넥션을 지원하지 않는 대신 설계적으로 더 개선된 지속 커넥션을 지원한다. 목적은 같음에도 더 잘 동작한다.

HTTP/1.1에서는 지속 커넥션은 기본으로 활성화된다. HTTP/1.1은 별도 설정이 없는 한 모든 커넥션을 지속 커넥션으로 취급한다. HTTP/1.1은 반대로, 끊어야 할 때 Connection:close를 보내도록 한다.

물론 클라이언트나 서버는 임의로 끊을 수도 있다.

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

  • 클라이언트가 요청에 Connection:close를 했으면, 이후에는 더 요청을 보낼 수 없다.
  • 커넥션에 있는 모든 메시지가 자신의 길이 정보를 정확히 가지고 있을 경우에만 커넥션을 지속할 수 있다. (Content-Length)
  • HTTP/1.1 프락시는 클라이언트와 서버 각각에 별도의 지속 커넥션을 맺고 관리해야 한다.
  • HTTP/1.1 프락시는 클라이언트의 지원 범위를 알고 있지 않은 한 지속 커넥션을 맺어서는 안 된다.
  • HTTP/1.1 클라이언트는 Connection 헤더와는 무관하게 언제든지 연결을 끊을 수 있다.
  • 클라이언트는 전체 응답을 받기 전에 커넥션이 끊어지면 다시 보낼 수 있어야 한다.
  • 클라이언트는 과부하를 방지하기 위해서라도 사용자 1명 당 2개의 커넥션만을 준비한다. 마찬가지로 프락시나 서버도 그러하다.
반응형