HTTP 4장 - 커넥션 관리

김태성·2022년 8월 8일
0


본 게시글은 "HTTP 완벽 가이드"를 학습하며, 내용 요약 또는 몰랐던 부분을 정리하는 글 입니다.

커넥션 관리

4.1 TCP 커넥션

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

URL을 입력받은 브라우저가 수행하는 7단계 과정
URL : http://www.joes-hardware.com:80/power-tools.html

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

HTTP 커넥션은 몇몇 사용 규칙을 제외하고는 TCP 커넥션에 불과하다. TCP 커넥션은 인터넷을 안정적으로 연결해준다.

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

HTTP가 메시지를 전송할 경우, TCP 커넥션을 통해서 메시지 데이터의 내용을 순서대로 보낸다. TCP는 세그먼트라는 단위로 데이터스트림을 잘게 나누고, 세그먼트를 IP 패킷이라 불리는 봉투에 담아서 인터넷을 통해 데이터를 전달한다.

이 모든 것은 TCP/IP 소프트웨어에 의해 처리되며, 그 과정은 HTTP 프로그래머에게 보이지 않는다.
TCP는 IP 패킷(혹은 IP 데이터그램)이라고 불리는 작은 조각을 통해 데이터를 전송한다.

IP 패킷은 다음을 포함한다.

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

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

회사의 대표 전화번호는 안내 데스크로 연결되고 내선전화는 해당 직원으로 연결되듯이 IP 주소는 해당 컴퓨터에 연결되고 포트 번호는 해당 애플리케이션으로 연결된다.

TCP 커넥션은 네 가지 값으로 식별한다.
아래 네 가지 값이 모두 똑같은 중복된 커넥션은 존재할 수 없다.

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

TCP 소켓 프로그래밍

운영체제는 TCP 커넥션의 생성과 관련된 여러 기능(API)을 제공한다. 이 소켓 API는 HTTP 프로그래머에게 TCP와 IP의 세부사항들을 숨긴다. 소켓 TCP는 유닉스 운영체제용으로 먼저 개발되었지만, 지금은 소켓 API의 다양한 구현체들 덕분에 대부분의 운영체제와 프로그램 언어에서 이를 사용할 수 있게 되었다.

4.2 TCP 성능에 대한 고려

HTTP는 TCP 바로 위에 있는 계층이기 때문에 HTTP 트랜잭션의 성능은 TCP 성능에 영향을 받는다.
TCP 성능의 특성을 이해함으로써, HTTP 커넥션 최적화 요소들을 더 잘 알게 되고 더 좋은 성능의 HTTP 애플리케이션을 설계하고 구현할 수 있게 될 것이다.

HTTP 트랜잭션 지연

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

  1. 클라이언트가 URI에서 웹서버의 IP/port번호를 알아내야한다.
  2. TCP커넥션 요청을 보내고 서버의 커넥션 허가응답을 기다려아한다.
  3. 요청메시지가 인터넷을 통해 전달되는 시간
  4. 서버로부터 응답메시지를 전달받는시간

이 4가지 원인으로 지연이 발생한다. 하지만 현대는 인프라의 발전으로 크게 발생하지 않는다.

4.2.2 성능 관련 중요 요소

  • tcp 커넥션의 핸드세이크
  • 인터넷 혼잡을 제어하기 위한 slow start
  • 데이터를 한번에 보내는 nagle 알고리즘
  • piggyback과 확인응답 알고리즘
  • TIME_WAIT 지연
  • 포트 고갈

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

  1. 클라이언트가 tcp 커넥션 생성하려고 SYN flag가 있는 메시지보냄
  2. 서버가 SYN+ACK보냄
  3. 커넥션이 잘 맺어졌음을 알리기위해서 서버한테 ACK전송

크기가 작은 http요청의 경우 50%이상의 시간을 커넥션 연결을 구성하는데 쓰이게되서 지연이라고 볼 수 있다.

4.2.4 확인응답 지연

인터넷이 패킷전송을 완벽하게는 보장하지 않기 때문에, 데이터 전송 성공을 위해서 자체적인 확인 체계를 가진다.
TCP 세그먼트들은 순번/데이터무결성체크섬 가지는데, 수신자가 그것을 검증하고 확인응답 패킷을 보내줘야한다.

확인 응답은 그 크기가 작아서, 효율적으로 보내려고 같은 방향으로 나가는 패킷에 편승(piggyback)한다.
하지만 이때 편승할 패킷을 찾지못한다면 그것을 찾을때까지 지연이 발생한다.

4.2.5 TCP 느린 시작 (slow start) 

tcp 데이터 전송속도는 tcp 커넥션이 만들어진지 얼마나 지났는지에 따라서 달라질 수 있다.
tcp 커넥션은 시간이 지나면서 자체적으로 튜닝되어가지고, 처음에는 커넥션의 최대 속도를 제한하고 데이터가 성공적으로 전송됨에 따라서 속도제한을 높여간다. (이게 slow start)

이걸로 인터넷의 갑작스러운 부하/혼잡을 막을 수 있다.
http 트랜잭션에서 전송할게 많으면 모든패킷을 한번에 쏠수는 없고 1개의 패킷을 보내고 확인응답을 기다려야한다.
확인응답받으면 이제 2개의 패킷을보내고 확인응답 받는다. 다음에는 4개, 이러한 방식으로 이렇게 보낸다.

4.2.6 nagle

tcp는 헤더의 크기가 작진않기때문에, 작은 데이터를 여러개의 tcp로 보내면 네트워크의 성능은 떨어질 것이다.
nagle알고리즘은 패킷을 전송하기 전에 많은 양의 tcp데이터를 1개의 덩어리로 합친다.
이때 전송하기 충분할 만큼의 패킷이 쌓였을때 버퍼에 저장되어있던 데이터가 전송되는 로직이 있어서, 충분할 만큼의 패킷이 쌓일때까지 지연이 발생할 수 있다.
TCP_NODELAY 플래그로 nagle 알고리즘을 비활성화할 수도 있다.

4.2.7 TIME_WAIT 누적, 포트고갈

tcp 커넥션을 끊으며 엔드포인트에서는 ip주소와 포트번호를 작게 기록해놓는데, 이렇게하면 보통 2분정도 내에 같은 주소와 포트번호를 가지는 커넥션이 또 생성되는것을 막아준다. (값을 다르게 해줌)
그래서 커넥션이 닫힌후에 중복되는 패킷이 생기는 경우는 거의 없어진다.

만약 이전 커넥션의 패킷이 그 커넥션과 같은 연결값으로 생성된 커넥션에 삽입되면 패킷은 중복되고 TCP데이터가 충돌난다.
일반적으로 그럴일은 없는데, 테스트하는 상황에서 이런 문제가 생긴다.

포트개수도 제한이 있기 때문에도 더이상 유일한 커넥션을 생성할 수 없어 지연이 발생하기도 한다.

4.3 HTTP 커넥션 관리

커넥션을 생성하고 최적화하는 HTTP 기술에 대해서 알아보자

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

Connection 헤더에는 다음 3가지 헤더때문에 헷갈릴 수 있다.

  • HTTP 헤더 필드명은 이 커넥션에만 해당되는 헤더들을 나열한다.
  • 임시적인 토큰값은 커넥션에 대한 비표준 옵션을 의미한다.
  • close 값은 커넥션 작업이 완료되는 종료되어야한다.

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

커넥션 관리가 제대로 안되면 tcp 성능이 매우 안좋아진다.
3개의 이미지가 있는 웹페이지가 있으면 4개의 http 트랜잭션을 만들어야함.
html받기용 1개 + 첨부된 이미지를 받기위한 3개.

이때 순차적으로 트랜잭션을 처리하게 되면,  이미지가 동시에 그려지지않아서 심리적으로 느리게 느껴지고
물론 물리적인 지연도 있고 화면에 배치하려면 크기를 알아야해서 객체의 크기를 파악하는 동안 텅빈화면이 보여진다.

이것을 해결하기 위해서 커넥션은 4가지가 존재한다.

  1. 병렬 커넥션
  2. 지속 커넥션
  3. 파이프라인 커넥션
  4. 다중 커넥션

4.4 병렬 커넥션

브라우저는 순차적으로 객체를 내려받는 식으로 웹페이지를 보여줄 수 있다.
=> 많이 느리다

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

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

  • 단일커넥션의 대역폭 제한과 커넥션이 동작하지 않고 있는 시간을 활용하면 더 빠르게 내려받을 수 있다는 아이디어
  • 하나의 커넥션으로 객체들을 로드할때 대역폭 제한과 대기시간을 줄일 수 있으면 더 빠르게 로드할 수 있다는 아이디어
  • 각 커넥션의 지연 시간을 겹치게하면 총 지연시간을 줄일 수 있다는 아이디어
  • 인터넷 대역폭을 한개의 커넥션이 다 써버리는 것이 아니고 나머지 객체를 내려받는 데에 나머지 대역폭을 사용할 수 있다는 아이디어

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

  • 병렬커넥션은 네트워크 대역폭이 좁을때는 대부분 시간을 데이터 전송에 쓸것이고
  • 여러개의 객체를 병렬로 내려받는 경우 제한된 대역폭이라면 전송받는 것은 느리기 때문에 성능상의 장점이 없어짐
  • 커넥션 생성 자체 부하때문에 객체들을 순차적으로 내려받는 것보다 더 오래걸릴 수도 있음
  • 커넥션자체도 부하임 (100명의 사용자가 100개의 커넥션 맺으면 서버는 10000개 커넥션 감당해야함)

=> 그래서 브라우저는 실제로 병렬 커넥션을 사용하긴 하지만 적은수(대부분 4개정도)의 병렬커넥션만을 허용함.

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

그래서 병렬커넥션은 순차적인 것보다 빠르지 않을 수는 있지만, 여러개의 객체가 거의 동시에 로드되기 때문에
사용자가 빠르게 로드된다고 느낄 수 있다.

4.5 지속 커넥션

  • 클라이언트는 1개의 웹사이트에 여러개의 커넥션을 맺는데, 계속 똑같은 서버에 요청을 보내는것은 비효율적이다. (site locality)
  • 따라서 http1.1을 지원하는 기기는 처리가 완료된 후에 tcp커넥션을 유지하여 앞으로 있을 http 요청에 재사용할 수 있다.

=> 처리가 완료된 후에도 계속 연결된 상태로 있는 tcp 커넥션을 지속커넥션이라고 부른다.
즉, 비지속 커넥션은 각 처리가 끝날때마다 끊지만 지속 커넥션은 처리가 끝나도 커넥션을 유지한다.

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

병렬커넥션의 단점

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

지속커넥션의 장점

  1. 커넥션을 맺기위한 사전작업과 지연 줄여준다.
  2. 튜닝된 커넥션(권한이더 많아진)을 유지한다.
  3. 커넥션의 수를 줄여준다.

지속커넥션의 단점

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

그래서 병렬커넥션 + 지속커넥션 === Good
오늘날에는 적은 수의 병렬 커넥션만을 맺고 그것을 지속하는 형태가 많이 보이게 되었다.

http 1.0+에는 'keep alive' 커넥션이 있고, http1.1에는 지속커넥션이 있다.

4.5.2 http1.0+의 keep alive 커넥션

  • keep alive 커넥션이라는 지속커넥션을 사용하기위해 등장
  • 설계상 문제가 있어서 http1.1에서 빠지게됨 (Q: 어떤문제일까?)
  • 지금 남아있는거는 오늘날에도 keep alive 방법이 사용되는 곳이 있어서 그럼

4.5.3 keep alive 동작

  • 클라이언트는 커넥션 유지하려고 Connection:Keep-Alive 헤더를 포함시킴
  • 서버도 해당헤더로 응답함.
  • 만약에 서버가 해당헤더로 응답하지 않는다면 keep alive 를 지원하지 않는 것임

4.5.4 keep alive 옵션

  • keep alive는 지속커넥션을 사용하고 싶어요라는 메시지이며 클라이언트/서버 모두 무조건 따를 필요는 없고 언제든지 끊어버릴 수 있다.
    timeout: 커넥션이 얼마나 유지될 것인가
    max: 커넥션이 몇개의 http 트랜잭션을 처리할 때까지 유지될 것인가

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

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

4.5.5 keep alive 커넥션 제한과 규칙

  • keep-alive는 http1.0에서 기본으로 사용되지는 않음
  • 클라이언트가 먼저 Connection Keep-Alive 헤더를 보내야함
  • 클라이언트가 Connection Keep-Alive를 보내지않으면 서버는 요청을 처리하고 끊어버릴 것
  • 서버가 응답헤더에 Connection Keep-Alive를 보내지않으면, 클라이언트는 곧 커넥션이 끊길것임을 알게됨
  • 커넥션이 끊어지기 전에 엔티티 본문의 길이(Content-Length)를 알 수 있어야 기존의 메시지 끝과 새로운 메시지의 시작점을 정확히 알 수 있다.
  • 프록시와 게이트웨이는 Connection 헤더의 규칙을 철저하게 지켜야한다.
  • 프록시와 게이트웨이는 메시지전달/캐시저장시에 Connection 헤더와 관련된 정보를 제거해야한다. (Q: 왜? => 뒤에서 언급되는 멍청한  프록시문제 때문)
  • 클라이언트는 응답 전체를 모두 받기 전에 커넥션이 끊어졌을 경우, 별다른 문제가 없으면 요청을 다시 보낼 수 있게 준비되어야한다.

4.5.6 keep alive와 멍청한 프록시

  • 웹 클라이언트 요청에 Connection Keep-Alive 헤더가 있으면, 클라이언트가 현재 연결하고 있는 tcp 커넥션을 끊지 않고 계속 유지하려는 것이다.

문제: Connection 헤더의 무조건 전달
프록시는 Connection 헤더를 이해하지 못해서 해당 헤더들을 삭제하지 않고 요청 그대로를 다음 프록시에 전달

  1. 클라이언트가 Connection Keep-Alive 헤더 메시지를 보냄
  2. 멍청한 프록시가 Connection Keep-Alive 뭔지몰라서 그대로 서버에 전달 (그래서 Connection헤더를 제거하는것)
  3. 서버가 Connection Keep-Alive 헤더를 포함한 응답을 보냄
  4. 멍청한 프록시가 응답메시지를 그대로 클라이언트에게 전달
    (이제 클라이언트와 서버는 지속커넥션으로 비밀친구가되었으나, 프록시는 아무것도 모른다)
  5. 프록시는 서버가 커넥션 끊기를 기다림. 하지만 서버는 커넥션을 끊지 않음
  6. 클라이언트가 지속커넥션에 메시지를 전송하는데 프록시는 같은 커넥션상에서 다른 요청이 오는경우를 모르게때문에 로드중단
  7. 클라이언트는 자신이나 서버가 timeout 날때까지 아무것도 못함

4.5.7 Proxy-Connection 살펴보기

(공부 좀 더 필요)
넷스케이프는 멍청한 프록시문제를 해결하기 위해서 일반적으로 전달하는 Connection 헤더 대신에, 비표준인 Proxy-Connection [확장헤더]를 프록시에게 전달.
이러면 프록시가 Proxy-Connection 전달해도 비표준이기 때문에 서버는 무시함.
이제 프록시는 정말 "전달만" 할 수 있음.

즉, Proxy-Connection는 멍청한 프록시: 단일 무조건 전달 문제를 해결할 수 있음.
=> 하지만 동시에 keep-alive 도 맺어지지 않음 (Connection헤더 제거했으니까)

해결법: 영리한 프록시를 둔다.
영리한 프록시는 Proxy-Connection를 받고 Connection: Keep-Alive 헤더로 바꿔서 서버에 전달한다.
keep alive 지속커넥션이 가능해진다!

근데 이것도, 멍청한 프록시 양옆에 영리한 프록시 있으면 (프록시가 많은 구조) 문제가 생긴다.
변환된 Connection: Keep-Alive가 다시 멍청한 프록시에 갈 수 있기 때문에..
게다가 문제를 발생시키는 프록시들은 네트워크상에서 보이지 않기 때문에 Proxy-Connection 헤더를 보내지 못한다.

4.5.8 http1.1의 지속커넥션

http1.1에서는 keep-alive 커넥션은 지원하지 않고, 더 나은 설계의 지속커넥션을 지원한다.
http1.1의 지속커넥션은 기본적으로 활성화되어있다. (별도 설정없으면 모든 커넥션을 지속커넥션으로 취급하는 것이다.)
http1.1은 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection: close 헤더를 명시해야한다.

4.5.9 지속 커넥션 제한과 규칙

  • 클라이언트가 Connection: close 보내면 클라이언트는 해당 커넥션으로 추가적인 요청 못함
  • 커넥션에 있는 모든 메세지가 자신의 길이 정보를 정확히 가지고 있을 때에만 커넥션 지속 가능
  • http1.1 프록시는 클라/서버 각각에 대해 별도의 지속커넥션 맺고 관리해야함
  • http1.1기기는 Connection: close 없어도 언제든지 연결을 자체적으로 끊어버릴 수 있음
  • http1.1는 중간에 끊어지는 커넥션을 복구할 준비가 되어야있어야함
  • 부하를 대비하기 위해서 N명의 사용자가 서버로 접근하려한다면 프록시는 넉넉잡아 2N개의 커넥션을 유지해야함 (2배)

4.6 파이프라인 커넥션

http1.1은 지속커넥션을 통해서 요청을 파이프라이닝 가능

  • http클라이언트는 커넥션이 지속 커넥션인지 확인하기 전까지는 파이프라인을 이어서는 안됨.
  • http 응답은 요청순서와 같게 와야함. (http 메시지에 순번이 매겨지지 않기 때문)
  • 커넥션이 강제로 끊어져도 완료되지 않은 요청이 파이프라인에 있으면 언제든 다시 요청 보낼 수 있어야함
  • http 클라이언트는 post 요청같이 반복해서 보낼 경우에 문제가 생길 수 있는 요청은 파이프라인을 통해 보내면안됨.(어떤것이 서버에서 처리되었는지 모르니까)

4.7 커넥션 끊기 미스터리

커넥션 관리에 대해서는 명확한 기준이 없음.

4.7.1 마음대로 커넥션 끊기

  • 보통 커넥션은 메시지를 다 보낸다음에 끊는데, 에러가 있는 상황에서는 헤더의 중간이나 다른 엉뚱한 곳에서 끊길 수 있음(timeout등등)
  • 당연히 커넥션을 끊는 시점에 클라이언트가 요청을 보낸다면 문제가 생길 수 있다!

4.7.2 Content-Length와 Truncation

  • 각 http 응답은 엔티티본문의 길이를 가지는 Content-Length 헤더를 가져야한다.
  • Content-Length 헤더를 일부러 잘못줘서 요청을 끊는 방법을 쓸수도 있어서, http응답받은후에 실제 전달된 엔티티길이와 Content-length값이 일치하지 않거나 없으면 서버한테 물어봐야한다.

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

  • http 어플리케이션은 예상치 못하게 커넥션이 끊어졌을때에 적절히 대응할 수 있는 준비가 되어야한다.
  • 끊어졌으면 커넥션을 다시맺고 재전송을 하는 과정이 필요하며, 파이프라인의 큐에 요청들이 있는데 끊어버리거나하면 복잡하다.
  • Post같은 서버의 상태를 바꾸는 작업들은 재시도하기 위험하다. (멱등하지 않음)
    (여러번 실행되었는데 한번실행된것과 같은 결과를 반환하면 멱등한것)
    GET, HEAD, PUT, DELETE, TRACE, OPTIONS는 멱등하다.

즉, 멱등하지 않은 요청은 재요청하면 안된다. 대부분 브라우저는 캐시된 Post 요청 페이지를 다시로드하려할때, 요청을 다시 보내기를 원하는지 묻는 대화상자를 보여준다

4.7.4 우아한 커넥션 끊기 

전체끊기
어플리케이션은 tcp입력채널과 출력채널 중 1개만 끊거나 둘다 끊을 수 있다.

절반끊기
입력채널이나 출력채널 중 하나를 개별적으로 끊을 수 있다.

TCP끊기
단순한 http는 전체끊기만 상요할 수 있는데 어플리케이션이 각기다른 http클라이언트, 서버, 프락시와 통신할때 지속커넥션을 계속할때, 예상치 못한 쓰기에러를 방지하기 위해 절반끊기가 필요할 수도 있다.

  • 보통 출력 채널을 끊는 것이 안전하다.
  • 클라이언트가 데이터를 더이상 보내지않음을 확신하지 않는이상, 입력채널을 끊어버리면 tcp에러가 뜨고 os에서 심각한 에러로 받아들여가지고 버퍼를 다 비워버린다. (문제가 더 커질 수 있음)

우아하게 커넥션 끊기
일반적으로 이상적인 커넥션끊기는 자신의 출력채널을 먼저 끊고 다른 쪽에 있는 기기의 출력 채널이 끊기기를 기다리는 것이다.
양쪽에서 서로 더이상 전송하지 않겠다라고 알려주면 위험없이 종료된다.
안타깝게도 항상 이렇지만은 않기 때문에, 출력채널에 절반끊기를 쓰고 입력채널에 대해서 상태검사를 주기적으로 해야한다.

profile
@flip_404

0개의 댓글