[4장] 커넥션 관리

janjanee·2022년 8월 1일
0
post-thumbnail

2020.12.13 작성글 이전


4장 커넥션 관리

4.1 TCP 커넥션

모든 HTTP 통신은, 컴퓨터와 네트워크 장비에서 널리 쓰이고 있는 패킷 교환 네트워크 프로토콜들의 계층화된 집합인 TCP/IP를 통해 이루어진다.

구글에게 아래와 같은 요청을 했다고 가정해보자.

http://www.google.com:80/power-tools.html

웹 브라우저가 TCP 커넥션을 통해서 웹 서버에 요청을 보내는 순서는 다음과 같다.

(1) 브라우저가 www.google.com 호스명을 추출한다. (2) 브라우저가 호스트 명에 대한 IP 주소를 찾는다 (3) 브라우저가 포트 번호(80)를 얻는다 (4) 브라우저가 IP의 80포트로 TCP 커넥션을 생성한다. (5) 브라우저가 서버로 HTTP GET 요청 메시지를 보낸다. (6) 브라우저가 서버에서 온 HTTP 응답 메시지를 읽는다. (7) 브라우저가 커넥션을 끊는다.

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

TCP는 HTTP에게 신뢰할 만한 통신 방식을 제공한다. TCP 커넥션의 한쪽에 있는 바이트들은 반대쪽으로 순서에 맞게 정확히 전달된다.

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

TCP는 IP 패킷이라고 불리는 작은 조각을 통해 데이터를 전송한다.

무제 001

HTTP는 'IP, TCP, HTTP'로 구성된 프로토콜 스택에서 최상위 계층이다. HTTP에 보안 기능을 더한 HTTPS는 TLS 혹은 SSL이라 불리기도 하며, HTTP와 TCP 사이에 있는 암호화 계층이다

TCP는 세그먼트라는 단위로 데이터 스트림을 잘게 나누고, 세그먼트를 IP 패킷이라고 불리는 봉투에 담아서 인터넷을 통해 데이터를 전달한다.

이 모든 것은 TCP/IP 소프트웨어에 의해 처리되며, 그 과정은 HTTP 프로그래머에게 보이지 않는다.

각 TCP 세그먼트는 하나의 IP 주소에서 다른 IP 주소로 IP 패킷에 담겨 전달된다.

  • IP 패킷 헤더(보통 20바이트)
  • TCP 세그먼트 헤더(보통 20바이트)
  • TCP 데이터 조각(0 혹은 그 이상의 바이트)

무제 002

4.1.3 TCP 커넥션 유지하기

컴퓨터는 항상 TCP 커넥션을 여러 개 가지고 있다. TCP는 포트 번호를 통해서 여러 개의 커넥션을 유지한다.

IP 주소는 해당 컴퓨터에 연결되고 포트 번호는 해당 애플리케이션으로 연결된다. TCP 커넥션은 네 가지 값으로 식별한다.

<발신지 IP 주소, 발신지 포트, 수신지 IP 주소, 수신지 포트>

무제 003 어떤 커넥션들은 같은 목적지 포트 번호를 가리킬 수 있다.(C/D) 같은 발신지 IP 주소를 가리키는 커넥션들 (B/C)도 있고, 같은 목적지 IP 주소를 가리키는 커넥션들 (A/B, C/D)도 있다.

하지만 네 가지 커넥션 구성요소를 모두 똑같이 가리키고 있는 커넥션은 있을 수 없다.

4.1.4 TCP 소켓 프로그래밍

소켓 API를 사용하면, TCP 종단(endpoint) 데이터 구조를 생성하고, 원격 서버의 TCP 종단에 그 종단 데이터 구조를 연결하여 데이터 스트림을 읽고 쓸 수 있다.

TCP API는 기본적인 네트워크 프로토콜의 핸드셰이킹, 그리고 TCP 데이터 스트림과 IP 패킷간의 분할 및 재조립에 대한 모든 세부사항을 외부로부터 숨긴다.

다음은 클라이언트와 서버 간에 HTTP 트랜젝션을 수행하기 위한 소켓 API의 사용 방법 슈도코드이다.

무제 004

4.2 TCP의 성능에 대한 고려

4.2.1 HTTP 트랜젝션 지연

트랜젝션을 처리하는 시간은 TCP 커넥션을 설정하고, 요청을 전송하고, 응답 메시지를 보내는 것에 비하면 상당히 짧다. 대부분의 HTTP 지연은 TCP 네트워크 지연 때문에 발생한다.

  • 커넥션이 맺어지면 클라이언트는 HTTP 요청을 새로 생성된 TCP 파이프를 통해 전송한다. 웹 서버는 데이터가 도착하는 대로 TCP 커넥션에서 요청 메시지를 읽고 처리한다. 요청 메시지가 인터넷을 통해 전달되고 서버에 의해서 처리되는 데 까지 시간이 걸린다.
  • 웹 서버가 HTTP 응답을 보내는 것 역시 시간이 소요된다.

TCP 네트워크 지연은 하드웨어의 성능, 네트워크와 서버의 전송 속도, 요청과 응답 메시지의 크기, 클라이언트와 서버 간의 거리에 따라 크게 달라진다. 또한 TCP 프로토콜의 기술적인 복잡성도 지연에 큰 영향을 끼친다.

4.2.2 성능 관련 중요 요소

  • TCP 커넥션의 핸드셰이크 설정
  • 인터넷의 혼잡을 제어하기 위한 TCP의 느린 시작
  • 데이터를 한데 모아 한번에 전송하기 위한 네이글 알고리즘
  • TCP의 편승(piggyback) 확인응답을 위한 확인응답 지연 알고리즘
  • TIME_WAIT 지연과 포트 고갈

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

TCP 커넥션 핸드셰이크 순서는 다음과 같다.

  1. 클라이언트는 새로운 TCP 커넥션을 생성하기 위해 작은 TCP 패킷을 서버에게 보낸다. 그 패킷은 SYN라는 특별한

    플래그를 가진다.

  2. 서버가 그 커넥션을 받아들인 후, 커넥션 요청이 받아들여졌음을 의미하는 SYN, ACK플래그를 포함한

    TCP 패킷을 클라이언트에게 보낸다

  3. 마지막으로 클라이언트는 커넥션이 잘 맺어졌음을 알리기 위해서 서버에게 다시 확인응답 신호를 보낸다. 오늘날의

    TCP는 클라이언트가 이 확인응답 패킷과 함께 데이터를 보낼 수 있다.

HTTP 프로그래머는 이 패킷들을 보지 못한다. 평범한 경우에는, SYN / SYN + ACK 핸드셰이크가 눈에 띄는 지연을 발생시킨다.

4.2.4 확인응답 지연

TCP는 성공적인 데이터 전송을 보장하기 위해서 자체적인 확인 체계를 가진다.

각 TCP 세그먼트는 순번과 데이터 무결성 체크섬을 가진다. 송신자가 특정 시간 안에 확인 응답 메시지를 받지 못하면 패킷이 파기되었거나 오류가 있는 것으로 판단하고 데이터를 다시 전송한다.

TCP는 송출 데이터 패킷과 확인응답을 하나로 묶음으로써 네트워크를 좀 더 효율적으로 사용한다. 확인 응답이 같은 방향으로 가는 데이터 패킷에 편승되는 경우를 늘리기 위해서, 많은 TCP 스택은 확인응답 지연 알고리즘을 구현한다.

4.2.5 TCP 느린 시작

TCP 커넥션은 시간이 지나면서 자체적으로 튜닝 되어서 처음에는 커넥션의 최대 속도를 제한하고 데이터가 성공적으로 전송됨에 따라서 속도 제한을 높여나간다.

TCP 느린 시작은 TCP가 한 번에 전송할 수 있는 패킷의 수를 제한한다. 패킷이 성공적으로 전달되는 각 시점에 송신자는 추가로 2개의 패킷을 더 전송할 수 있는 권한을 얻는다.

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

네이글 알고리즘은 네트워크 효율을 위해서, 패킷을 전송하기 전에 많은 양의 TCP 데이터를 한 개의 덩어리로 합친다. 그러나, 네이글 알고리즘은 HTTP 성능 관련해 여러 문제를 발생시킨다.

  1. 앞으로 생기지 않을지 모르는 추가적인 데이터를 기다리며 지연될 것이다.
  2. 확인응답 지연과 함께 쓰일 경우, 형편없이 동작한다.

HTTP 애플리케이션은 성능 향상을 위해서 HTTP 스택에 TCP_NODELAY 파라미터 값을 설정하여 네이글 알고리즘을 비활성화하기도 한다. 이 설정을 했다면, 작은 크기의 패킷이 너무 많이 생기지 않도록 큰 크기의 데이터 덩어리를 만들어야 한다.

4.2.7 TIME_WAIT의 누적과 포트 고갈

TCP 커넥션의 종단에서 TCP 커넥션을 끊으면, 종단에서는 커넥션의 IP 주소와 포트 번호를 메모리의 작은 제어영역에 기록해 놓는다. 이 정보는 같은 주소와 포트번호를 사용하는 새로운 TCP 커넥션이 일정 시간 동안에는 생성되지 않게 하기 위한것이다. 보통 세그먼트의 최대 생명주기 두 배 정도('2MSL')의 시간 동안만 유지된다.

이전 커넥션과 관련된 패킷이 그 커넥션과 같은 주소와 포트 번호를 가지는 새로운 커넥션에 삽입되는 문제를 방지한다. 실제로 이 알고리즘은 특정 커넥션이 생성되고 닫힌 다음, 그와 같은 IP 주소와 포트 번호를 가지는 커넥션이 2분 이내에 또 생성되는 것을 막아준다.

4.3 HTTP 커넥션 관리

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

HTTP Connection 헤더 필드는 커넥션 토큰을 쉼표로 구분하여 가지고 있으며, 그 값들은 다른 커넥션에 전달되지 않는다.

예를 들어, 다음 메시지를 보낸 다음 끊어져야 할 커넥션은 Connection: close 라고 명시할 수 있다.

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

  • HTTP 헤더 필드 명은, 이 커넥션에만 해당되는 헤더들을 나열한다
  • 임시적인 토큰 값은, 커넥션에 대한 비표준 옵션을 의미한다
  • close 값은, 커넥션 작업이 완료되면 종료되어야 함을 의미한다
HTTP/1.1 200 OK
Cache-control: max-age=3600
Connection: meter, close, bill-my-credit-card
Meter: max-uses=3, max-refuses=6, dont-report

위의 메시지에서 Connection 헤더는, Meter 헤더를 다른 커넥션으로 전달하면 안 되고, 'bill-my-credit-card' 옵션을 적용할 것이며, 이 트랜젝션이 끝나면 커넥션이 끊긴다고 설명되어있다.

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

3개의 이미지가 있는 웹페이지가 있다고 하자. 브라우저가 이 페이지를 보여주려면 4개의 HTTP 트랜젝션을 만들어야한다. 하나는 해당 HTML을 받기 위해, 나머지 세 개는 첨부된 이미지를 받기 위한 것이다.

각 트랜젝션이 새로운 커넥션을 필요로 한다면, 커넥션을 맺는데 발생하는 지연과 함께 느린 시작 지연이 발생할 것이다.

다음의 네 가지 기술은 HTTP 커넥션의 성능을 향상시킬 수 있는 기술이다.

  • 병렬 커넥션

    여러 개의 TCP 커넥션을 통한 동시 HTTP 요청

  • 지속 커넥션

    커넥션을 맺고 끊는 데서 발생하는 지연을 제거하기 위한 TCP 커넥션의 재활용

  • 파이프라인 커넥션

    공유 TCP 커넥션을 통한 병렬 HTTP 요청

  • 다중 커넥션

    요청과 응답들에 대한 중재

4.4 병렬 커넥션

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

위의 그림과 같이, HTTP는 클라이언트가 여러 개의 커넥션을 맺음으로써 여러 개의 HTTP 트랜젝션을 병렬로 처리할 수 있게 한다. 네 개의 이미지를, 할당받은 각 TCP 커넥션상의 트랜젝션을 통해 병렬로 내려받는다.

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

HTML 페이지를 먼저 내려받고 남은 세 개의 트랜젝션이 각각 별도의 커넥션에서 동시에 처리된다. 이미지를 병렬로 내려받아 커넥션 지연이 겹쳐짐으로써 총 지연시간이 줄어든다

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

일반적으로 더 빠르기는 하나, 항상 그렇지는 않다. 클라이언트의 네트워크 대역폭이 좁을 때는 대부분의 시간을 데이터 전송하는 데만 쓸 것이다. 여러 개의 객체를 병렬로 내려받는 경우, 이 제한된 대역폭 내에서 각 객체를 전송받는 것은 느리기 때문에 성능상 장점 거의 없음.

또한, 다수의 커넥션은 메모리를 많이 소모하고 자체적인 성능 문제를 발생시킨다. 브라우저는 실제로 병렬 커넥션을 사용하긴 하지만 적은 수(6~8)의 병렬 커넥션만을 허용한다. 서버는 특정 클라이언트로부터 과도한 수의 커넥션이 맺어지면, 그것을 임의로 끊을 수 있다.

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

병렬 커넥션이 페이지를 항상 더 빠르게 로드하는 것은 아니지만, 사용자 입장에서 동시에 내려받는 것을 보면 더 빠르게 내려받고 있는 것 처럼 느낄 수 있다

4.5 지속 커넥션

처리가 완료된 후에도 계속 연결된 상태로 있는 TCP 커넥션을 지속 커넥션 이라고 부른다. 지속 커넥션은 클라이언트나 서버가 커넥션을 끊기 전까지는 트랜젝션 간에도 커넥션을 유지한다.

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

병렬 커넥션에는 다음과 같은 단점이 있다.

  • 각 트랜젝션마다 새로운 커넥션을 맺고 끊기 때문에 시간과 대역폭 소요
  • 각각의 새로운 커넥션은 TCP 느린 시작 때문에 성능 저하
  • 실제로 연결할 수 있는 병렬 커넥션의 수에는 제한이 있음

지속커넥션은 병렬 커넥션에 비해 몇 가지 장점이 있다.

  • 커넥션 사전 작업과 지연 감소
  • 튜닝된 커넥션(패킷을 수차례 성공적으로 전송한 결과로 한 번에 다수의 패킷을 전송할 수 있는 권한을 얻은 커넥션 상태) 유지
  • 커넥션 수를 감소시킴

하지만, 잘못 관리할 경우, 계속 연결된 상태로 있는 수많은 커넥션이 쌓여 불필요한 소모를 발생시킨다.

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

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에서 수정되었다.

HTTP4 006

위의 그림은 네 개의 HTTP 트랜젝션에 대해서, 연속적으로 네 개의 커넥션을 생성하여 처리하는 방식과 하나의 지속 커넥션으로만 처리하는 방식을 비교하였다.

커넥션을 맺고 끊는 데 필요한 작업이 없어서 시간이 단축되었다

4.5.3 Keep-Alive 동작

keep-alive는 사용하지 않기로 결정되어 HTTP/1.1 명세에서 빠졌다. 하지만 아직도 브라우저와 서버 간에 keep-alive 핸드셰이크가 널리 사용되므로, HTTP 애플리케이션은 그것을 처리할 수 있게 개발해야한다.

GET /index.html HTTP/1.0
...
Connection: Keep-Alive

HTTP/1.0 200 OK
...
Connection: Keep-Alive

커넥션을 요청하기 위해서는 헤더에 Connection:Keep-Alive 헤더를 포함시킨다. 서버가 요청을 받고 그 다음 요청도 이 커넥션을 통해 받고자 한다면, 응답 메시지에도 같은 헤더를 포함시켜 응답한다.

4.5.4 Keep-Alive 옵션

  • timeout
  • max
Connection: Keep-Alive
Keep-Alive: max=5, timeout=120

서버가 5개의 트랜젝션이 처리될 동안 커넥션을 유지하거나, 2분 동안 커넥션을 유지하라는 Keep-Alive 응답헤더다.

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

  • HTTP/1.0에서 기본으로 사용되지 않음

  • 커넥션을 계속 유지하려면 모든 메시지에 헤더를 포함해야 함

  • 엔터티 본문이 정확한 Content-Length 값과 함께 멀티파트 미디어형식을 가지거나

    청크 전송 인코딩으로 인코드 되어여 함

  • 프락시와 게이트웨이는 메시지를 전달하거나 캐시에 넣기 전 Connection 헤더에 명시된 모든 헤더 필드와

    Connection 헤더를 제거해야함

  • 프락시 서버와는 맺어지면 안된다.

  • HTTP/1.0을 따르는 기기로 부터 받는 모든 Connection 헤더 필드는 무시해야 함

4.5.6 Keep-Alive와 멍청한 프락시

Connection 헤더의 무조건 전달

프락시는 Connection 헤더를 이해하지 못하므로 서버에게 그대로 전달한다. 즉, 서버와 프락시가 keep-alive가 맺어지고 멍청한 프락시는 서버로부터 받은 응답 메시지를 클라이언트에게 전달한다. 클라이언트는 응답 메시지를 받고, 다음 요청을 보내는데 이 때, 프락시는 같은 커넥션 상에서 다른 요청이 오는 경우를 예상하지 못하기 때문에 응답 없이 기다리고만 있다.(서버 커넥션이 끊기기를)

프락시와 홉별 헤더

잘못을 피하려면, 프락시는 Connection 헤더와 명시된 헤더들은 절대 전달하면 안 된다. 또한, 다른 홉별 헤더들 역시 전달하거나 캐시하면 안 된다.

4.5.7 Proxy-Connection 살펴보기

넷스케이프는 멍청한 프락시 문제를 해결하기 위해 일반적인 Connection 헤더 대신 비표준인 Proxy-Connection 확장 헤더를 프락시에게 전달한다.

서버는 Proxy-Connection을 무시하기 때문에 문제가 되지않는다. 그러나 영리한 프락시는 Proxy-Connection 헤더가 keep-alive를 요청하는 것임을 인식하여, 자체적으로 Connection:keep-Alive 헤더를 웹 서버로 전송한다.

그러나 여러 멍청한 프락시와 영리한 프락시가 연결되어 있으면 여전히 잘못된 헤더를 만들어내는 문제가 발생한다.

4.5.8 HTTP/1.1의 지속 커넥션

HTTP/1.1에서 keep-alive 커넥션을 지원하지 않는 대신 설계가 더 개선된 지속 커넥션을 지원한다. 별도 설정을 하지 않는 한, 모든 커넥션을 지속커넥션으로 취급한다.

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

  • 클라이언트가 요청에 Connection: close헤더를 포함하면, 그 커넥션으로 추가 요청을 보낼 수 없음

  • 커넥션에 있는 모든 메시지가 자신의 길이를 정확히 가지고 있을 때에만 커넥션 지속 가능

  • HTTP/1.1 프락시는 클라이언트와 서버 각각에 대해 별도 지속 커넥션을 맺고 관리

  • HTTP/1.1 프락시는 클라이언트의 지원 범위를 알고 있지 않은 한 지속 커넥션을 맺으면 안된다

  • 클라이언트는 전체 응답을 받기 전에 커넥션이 끊어지면, 요청을 반복해서 보내도 문제 없는 경우에는

    요청을 다시 보낼 준비가 되어야함

  • N명의 사용자가 서버로 접근하려 하면, 프락시는 서버나 상위 프락시에 넉넉잡아 2N개의 커넥션을 유지해야함

4.6 파이프라인 커넥션

HTTP/1.1은 지속 커넥션을 통해서 요청을 파이프라이닝할 수 있다.

파이프라인에는 여러 가지 제약 사항이 있다.

  • HTTP 클라이언트는 커넥션이 지속 커넥션인지 확인하기 전까지는 파이프라인을 이어서 안됨
  • HTTP 응답은 요청 순서와 같게 와야 함
  • HTTP 클라이언트는 완료되 지않은 요청이 파이프라인에 있으면 언제든지 다시 요청을 보낼 준비가 되어있어야 함
  • POST 요청같이 반복해서 보낼 경우 문제가 생기는 요청은 파이프라인을 통해 보내면 안됨

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

4.7.1 마음대로 커넥션 끊기

HTTP 애플리케이션은 언제든지 지속 커넥션을 임의로 끊을 수 있다.

4.7.2 Content-Length와 Truncation

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

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

정적인 페이지를 Get 하는 요청들은 반복 요청하더라도 결과적으로 아무런 영향을 끼치지 않는다. 반면 POST 부류의 요청은 반복될 경우 여러 번 중복될 것이므로 반복은 피해야 한다.

클라이언트는 POST와 같이 멱등이 아닌 요청은 파이프라인을 통해 요청하면 안 된다. 전송 커넥션이 예상치 못하게 끊기면 알 수 없는 결과를 초래하기 때문이다.

4.7.4 우아한 커넥션 끊기

TCP 커넥션은 양방향이다. 한쪽 출력 큐에 있는 데이터는 다른 쪽 입력 큐에 보내질 것이다.

전체 끊기와 절반 끊기

  • 전체끊기 close() : TCP 커넥션이 입력, 출력 채널의 모든 커넥션을 끊음
  • 절반끊기 shutdown() : 입력, 출력 채널 중 하나를 개별적으로 끊음

TCP 끊기와 리셋 에러

예상치 못한 쓰기 에러를 발생하는 것을 예방하기 위해 절반 끊기를 사용해야 한다. 보통은 출력 채널을 끊는 것이 안전하다.

우아하게 커넥션 끊기

일반적으로 애플리케이션 자신의 출력 채널을 먼저 끊고 다른 쪽 기기의 출력 채널이 끊기는 것을 기다리는 것이다.

하지만 상대방이 절발 끊기를 구현했다는 보장도 없고, 검사해준다는 보장도 없다. 따라서, 절반 끊기를 하고 난 후에도 데이터나 스트림의 끝을 식별하기 위해 입력 채널에 대해 상태 검사를 주기적으로 해야한다.

profile
얍얍 개발 펀치

0개의 댓글