[HTTP] 커넥션 관리

2021. 9. 17. 19:31

[TOC]

 

 

04 커넥션 관리

  • HTTP는 어떻게 TCP 커넥션을 사용하는가
  • TCP 커넥션의 지연, 병목, 막힘
  • 병렬 커넥션, keep-alive 커넥션, 커넥션 파이프라인을 활용한 HTTP의 최적화
  • 커넥션 관리를 위해 따라야 할 규칙들

 

 

4.1 TCP 커넥션

  • 전 세계 모든 HTTP 통신은 TCP/IP를 통해 이루어짐
  • http://www.naver.com:80/ 과 같은 URL을 브라우저가 받았을 때의 순서
    • 브라우저가 www.naver.com이라는 호스트명을 추출
    • 브라우저가 이 호스트 명에 대한 IP주소 찾음(DNS)
    • 브라우저가 포트 번호(80)를 얻음
    • 브라우저가 IP주소와 포트를 활용해 TCP 커넥션을 생성
    • 브라우저가 서버로 HTTP GET 요청을 보낸다.
    • 브라우저가 서버에서 온 HTTP 응답 메시지를 읽는다
    • 브라우저가 커넥션을 끊는다.

img

 

 

TCP

  • HTTP 커넥션은 결국 TCP 기반이기 때문에 TCP의 내용을 잘 알아야 한다.
  • TCP는 IP 패킷(혹은 IP 데이터그램)이라고 불리는 작은 조각을 통해 데이터를 전송함
  • HTTP가 메시지를 전송하고자 할 경우, 현재 연결되어 있는 TCP 커넥션을 통해서 메시지 데이터의 내용을 순서대로 보냄
  • 이 모든 것은 TCP/IP 소프트웨어에 의해 처리됨
  • 각 TCP 세그먼트는 하나의 IP 주소에서 다른 IP주소로 IP 패킷에 담겨 전달된다.
    • IP 패킷 구성
      • IP 패킷 헤더(보통 20 byte)
      • TCP 세그먼트 헤더(보통 20 byte)
      • TCP 데이터 조각(0 혹은 그 이상의 바이트)
  • 컴퓨터는 항상 TCP 커넥션을 여러 개 가지고 있다.
    • 포트 번호를 통해서 이런 여러개의 커넥션을 유지함
  • TCP 커넥션은 네 가지 값으로 식별한다.
    • <발신지 IP 주소, 발신지 포트, 수신지 IP 주소, 수진지 포트>

 

 

TCP 소켓 프로그래밍

  • 운영체제는 TCP 커넥션 생성과 관련된 여러 기능을 제공
소켓 API 호출 설명
s = socket(<parameters>) 연결이 되지 않은 익명의 새로운 소켓 생성
bind(s, <local IP:port>) 소켓에 로컬 포트 번호와 인터페이스 할당
connect(s, <remote IP:port>) 로컬의 소켓과 원격의 호스트 및 포트 사이에 TCP 커넥션 생성
listen(s,...) 커넥션을 받아들이기 위해 로컬 소켓에 허용함을 표시
s2 = accept(s) 누군가 로컬 포트에 커넥션을 맺기를 기다림
n = read(s, buffer, n) 소켓으로부터 버퍼에 n바이트 읽기 시도
n = write(s, buffer, n) 소켓으로부터 버퍼에 n바이트 쓰기 시도
close(s) TCP 커넥션을 완전히 끊음
shutdown(s, <side>) TCP 커넥션의 입출력만 닫음
getsockopt(s, ...) 내부 소켓 설정 옵션값을 읽음
setsockopt(s, ...) 내부 소켓 설정 옵션값을 변경
  • 소켓 API 사용하면 TCP 종단 데이터 구조 생성

 

 

4.2 TCP 성능에 대한 고려

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

 

4.2.1 HTTP 트랜잭션 지연

  • 트랜잭션을 처리하는 시간은 데이터가 너무 크지 않은 이상 그렇게 걸리지 않는다.
  • 지연이 일어나는 대부분의 경우는
    • URI에 기술되어 있는 호스트에 최근 방문한 적이 없을 때, DNS 인프라를 이용해 IP 주소를 알아내는 과정에서 시간이 걸린다.
    • 클라이언트가 서버에 TCP 커넥션 요청을 보내고 서버가 응답을 회신하기를 기다릴 때, HTTP 트랜잭션 요청이 많다면 이 부분도 지연될 수 있다.
    • TCP 커넥션 연결 이후 트랜잭션 요청 후 응답되는 과정까지 시간이 걸린다.
  • 요약
    • 하드웨어의 성능
    • 네트워크와 서버의 전송 속도
    • 요청과 응답 메시지 크기
    • 클라이언트와 서버간의 거리
    • TCP 프로토콜의 기술적인 복잡성

 

4.2.2 성능 관련 중요 요소

  • HTTP 프로그래머에게 영향을 주는 가장 일반적인 TCP 관련 지연
    • TCP 커넥션의 hand-shake 설정
    • 인터넷 혼잡을 제어하기 위한 TCP의 느린 시작(slow-start)
    • 데이터를 한데 모아 한 번에 전송하기 위한 네이글(nagle) 알고리즘
    • TCP의 편승(piggyback) 확인응답(acknowledgment)ㅇ을 위한 확인응답 지연 알고리즘
    • TIME_WAIT 지연과 포트 고갈

 

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

  • 보통 새로운 TCP 커넥션을 맺을 때 연속으로 IP 패킷을 교환한다.
  • 작은 크기의 데이터 전송에 커넥션이 사용된다면 이런 패킷 교환은 HTTP 성능을 크게 저하시킬 수 있다.
  • 핸드셰이크 순서
    • 클라이언트는 SYN이라는 특별한 플래그를 가진 작은 TCP 패킷을 서버로 전송
      • SYN : 커넥션 생성 요청
    • 서버가 커넥션을 받아 몇가지 커넥션 매개변수를 산출 한 뒤 SYNACK 플래그를 포함한 TCP 패킷을 클라이언트에 보냄
    • 클라이언트는 커넥션이 잘 맺어졌음을 알리기 위해 서버에게 다시 확인 응답 신호를 보냄
      • 요새는 이런 확인 응답 패킷과 함께 데이터 전송 가능
  • HTTP 프로그래머는 이 패킷들을 보지 못한다.
  • 전송하고자 하는 데이터가 크지 않을 때 SYN / SYN + ACK 핸드셰이크는 눈에 띄는 지연을 발생시킴

 

4.2.4 확인 응답 지연

  • 인터넷이 패킷 전송을 완벽히 보장하지 않기 때문에 TCP는 자체적인 확인 체계를 가진다.
    • 라우터는 과부하가 걸렸을때 패킷을 마음대로 파기할 수 있기 때문이다.
  • 각 TCP 세그먼트는 순번과 데이터 무결성 checksum을 가진다.
  • 각 세그먼트의 수신자는 세그먼트를 온전히 받으면 작은 확인응답 패킷을 송신자에게 반환한다.
  • 만약 특정 시간안에 확인응답 메시지를 받지 못하면 데이터를 다시 전송한다.
  • 확인응답은 그 크기가 작기 때문에 TCP는 같은 방향으로 송출되는 데이터 패킷에 확인 응답을 편승(piggyback) 시킨다.
    • 이렇게 하나로 묶으면서 네트워크를 좀 더 효율적으로 사용한다.
  • TCP 스택은 확인 응답 지연 알고리즘을 구현해 송출하기 전에 편승시킬 송출 데이터 패킷을 찾는다.
  • 그러나 요청과 응답 두가지 형식으로만 이루어지는 HTTP 동작 방식은, 확인 응답이 송출 데이터 패킷에 편승할 기회를 감소시킨다.

 

 

4.2.5 TCP 느린 시작

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

 

 

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

  • TCP 세그먼트는 어느정도의 데이터를 차지하기 때문에 일정 데이터가 쌓이지 않으면 전송을 하지 않는 것이 네이글 알고리즘이다.
  • 네이글 알고리즘은 크기가 작은 HTTP 메시지는 패킷을 채우지 못하기 때문에, 데이터를 기다리며 지연될 것이다.
  • 네이글 알고리즘은 확인응답 지연과 함께 쓰일 경우 형편없이 작동한다.
  • HTTP 어플리케이션은 성능향승을 위해 HTTP 스택에 TCP_NODELAY 파라미터 값을 설정해 네이글 알고리즘을 비활성화 한다.
    • 이 파라미터 값을 설정했다면 너무 작은 크기의 패킷이 생성되지 않게 주의하자.

 

 

4.2.7 TIME_WAIT의 누적과 포트 고갈

  • TIME_WAIT 포트 고갈은 성능 측정 시에 심각한 성능 저하를 발생시키지만, 보통 실제 상황에서는 문제를 발생시키지 않는다.
  • TCP 종단에서는 커넥션이 끊어졌을 때 일정시간동안 커넥션의 IP주소와 포트 번호를 저장한다.
    • 중복 커넥션 생성을 방지하기 위함이다.
    • 성능 측정시에 이 저장하는 시간 (약 2분) 때문에 커넥션이 제한된다.

 

 

4.3 HTTP 커넥션 관리

  • 여기서부터는 커넥션을 생성하고 최적화하는 HTTP 기술을 설명한다.

 

 

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

  • 클라이언트에서 서버까지 HTTP가 전송될 때 캐시나 프록시 같은 중개 서버를 지나기도 한다.
  • 어떤 경우에 인접한 두개의 어플리케이션의 커넥션에만 적용되는 옵션을 설정할 때가 있다.
  • 이 옵션들은 다른 커넥션에 전송될 필요가 없기 때문에 끊어야 한다.
  • Connection 헤더에서 다음의 세가지 종류의 토큰이 전달 될 수 있다.
    • HTTP 헤더 필드 명은, 이 커넥션에만 해당되는 헤더들을 나열한다.
    • 임시적인 토큰 값은, 커넥션에 대한 비표준 옵션을 의미한다.
    • close값은, 커넥션 작업이 완료되면 종료되어야 함을 의미한다.
  • Connection 헤더에 있는 모든 헤더 필드는 메시지를 다른 곳으로 전달하는 시점에 삭제되어야 한다.
    • 헤더에는 홉별(hop by hop) 헤더 명을 기술하는데 이것을 헤더 보호하기라 한다.
      • hop은 각 서버를 의미한다.
뭘 잘못 알고 있는다는 건지
그럼 삭제되면 새로 중개서버에서 헤더를 새로 생성하는지?

 

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

  • 커넥션 관리가 제대로 이루어지지 않으면 TCP 성능이 매우 안좋아질 수 있다.
  • 이미지 3개가 있는 웹페이지가 있다고 해보자
    • 그렇다면 브라우저가 이 페이지를 보여주기 위해서는 HTTP 트랙잭션이 4번 필요하다.
    • HTML을 불러오는데 한번, 이미지를 순차적으로 불러오는 데 총 3번이다.
    • 물리적 지연 뿐만 아니라 그것을 지켜보는 클라이언트 입장에서 심리적 지연감도 느낄 것이다.
  • 순차적으로 로드하는 방식에 또다른 단점이 있다.
    • 특정 브라우저의 경우 객체를 화면에 배치하려면 객체의 크기를 알아야 하기 때문에 모든 객체를 내려받기 전까지 텅 빈화면만 보여줘야 한다.
  • HTTP 커넥션 성능을 향상 시킬수 있는 여러 최신기술이 있다.
    • 병렬(parallel) 커넥션
      • 여러개의 TCP 커넥션을 통한 동시 HTTP 요청
    • 지속(persistent) 커넥션
      • 커넥션을 맺고 끊는 데서 발생하는 지연을 제거하기 위한 TCP 커넥션의 재활용
    • 파이프라인(pipelined) 커넥션
      • 공유 TCP 커넥션을 통한 병렬 HTTP 요청
    • 다중(multiplexed) 커넥션
    • 요청과 응답들에 대한 중재 (실험적 기술이다.)

 

 

4.4 병렬 커넥션

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

 

 

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

  • 하나의 커넥션으로 객체들을 로드할 때의 대역폭(bandwidth) 제한과 대기 시간을 줄일 수 잇다면 더 빠르게 로드할 수 있다.
  • 각 커넥션의 지연 시간을 겹치게 하면 총 지연 시간을 줄일 수 있고 클라이언트의 인터넷 대역폭을 한 개의 커넥션이 다 서버리는 것이 아니라면 나머지 객체를 내려받는 데 남은 대역폭을 사용할 수 있다.

 

 

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

  • 일반적으로 병렬 커넥션이 더 빠르지만 항상 그렇지는 않다.
  • 대역폭(bandwidth)가 좁다면 충분한 지연 시간을 가질 수 없을 것이다.
  • 또한 다수의 커넥션은 메모리를 많이 소모하고 자체적인 성능 문제를 발생시킨다.
  • 브라우저는 실제로 병렬 커넥션을 사용하긴 하지만 적은 수의 병렬 커넥션만을 허용한다.
  • 서버는 특정 클라이언트로부터 과도한 수의 커넥션이 맺어지면 강제로 끊을 수 있다.

 

 

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

  • 병렬 커넥션으로 내려받으면 객체가 병렬적으로 받아지기 때문에 순차적인 방식보다 빠르게 보일 수 도있다.

 

 

4.5 지속 커넥션

  • HTTP 요청에 대한 응답을 받은 이후에도 TCP 커넥션이 계속 연결되어 있는 상태가 지속 커넥션이다.
  • 비지속 커넥션은 각 처리가 끝날 때마다 커넥션을 끊지만, 지속 커넥션은 클라이언트나 서버가 커넥션을 끊기 전까지는 트랜잭션 간에도 커넥션을 유지한다.
    • 커넥션을 다시 맺기 위한 준비시간이나 느린시작으로 인한 지연을 피할 수 있다.

 

 

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

  • 병렬 커넥션은 여러 객체가 있는 페이지를 빠르게 전송하지만 단점이 있다.
    • 각 트랜잭션마다 새로운 커넥션은 맺고 끊기 때문에 시간과 대역폭(bandwidth)가 소요된다.
    • 각각의 새로운 커넥션은 TCP 느린 시작 떄문에 성능이 떨어진다.
    • 실제로 연결할 수 있는 병렬 커넥션의 수에는 제한이 있다.
  • 지속커넥션의 장점
    • 커넥션을 맺기 위한 사전 작업과 지연을 줄여줌
    • 튜닝된 커넥션을 유지( 느린 시작 하지 않아도 되는 상태 )
    • 커넥션의 수를 줄여준다.
  • 지속커넥션의 단점
    • 관리를 잘못하면 커넥션이 계속 쌓이게 된다.
    • 이는 로컬의 리소스 그리고 원격의 클라이언트와 서버의 리소스에 불필요한 소모를 발생시킨다.

 

지속 커넥션은 병렬 커넥션과 함께 사용될 때에 가장 효과적이다.

  • 오늘날 많은 어플리케이션은 적은 수의 병렬 커넥션만을 맺고 그것을 유지한다.
  • 두가지 커넥션 타입이 있다.
    • HTTP/1.0+ : 'keep-alive'
    • HTTP/1.1 : 지속 커넥션

 

 

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

  • 커넥션을 연속으로 생성해 지연 시간을 없애준다.

 

 

4.5.3 Keep-Alive 동작

  • keep-alive는 사용하지 않기로 결정되어 HTTP/1.1 명세에 빠졌지만 아직도 사용되고 있어서 그것을 처리할 수 있게 개발해야 한다.
  • keep-alive의 동작 방식
    • 클라이언트는 커넥션을 유지하기 위해서 요청에 Connection:Keep-Alive 헤더를 포함
    • 이 요청을 받은 서버는 그 다음 요청도 이 커넥션을 통해 받고자 한다면 응답 메시지에 같은 헤더를 포함시켜 응답함

 

 

4.5.4 Keep-Alive 옵션

  • Keep-Alive 헤더는 커넥션을 유지하기를 바라는 요청일 뿐이다.
  • 클라이언트나 서버는 Keep-Alive를 받더라도 커넥션을 끊을 수 있다.
  • keep-alive의 동작은 Keep-Alive 헤더의 쉼표로 구분된 옵션들로 제어할 수 있다.
    • timeout 파라미터는 Keep-Alive 응답 헤더를 통해 보낸다. / 유지시간
    • max 파라미터는 몇개의 HTTP 트랜잭션을 처리할 때 까지 유지할 것인지 설정한다.

 

 

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

  • HTTP/1.0 에서 기본으로 사용되지 않음
  • 커넥션을 유지하려면 계속해서 헤더에 Connection:Keep-Alive 요청을 담아야 한다.
  • 커넥션이 끊기기 전에 엔티티 본문의 길이를 알 수 있어야 커넥션을 유지할 수 있다.
    • 종단점을 알아야 한다는 의미이다.
  • 프록시나 게이트웨이는 Connectino 헤더의 규칙을 철저히 지켜야 한다.
    • 프록시와 게이트웨이는 메시지를 전달하거나 캐시에 넣기 전에 Connection 헤더에 명시된 모든 헤더 필드와 Connection 헤더를 제거해야 함
    • 서버와 클라이언트가 유지되어야 하는 것이고 프록시와 게이트와는 관련 없기 때문 -> 이게 쉽지 않음

 

 

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

  • 프록시는 Connection 헤더를 이해하지 못해서 해당 헤더들을 삭제하지 않고 요청 그대로를 다음 프록시에 전달함
  • 프록시가 서버에 Connection:Keep-Alive를 그대로 보내고, 서버는 프록시가 커넥션을 유지하고자 하는 것으로 이해함
  • 서버는 다시 Connection을 클라이언트로 보내고, 클라이언트는 Keep-Alive가 되었다고 착각한다.
  • 프록시는 데이터를 전달한 후 서버와의 커넥션이 끊기기를 기다리지만 서버는 끊지 않는다.
  • 클라이언트가 한번 더 요청을 보내면 프록시는 응답을 기다리는 상태와 요청을 받는 상황이 충돌하여 로드 중이라는 표시만 나오게 된다.
  • 이는 타임아웃을 나게 한다.

 

 

4.5.7 Proxy-Connection 살펴보기

  • 넷스케이프는 멍청한 프록시 문제를 해결하기 위해 브라우저에서 일반적으로 전달하는 Connection 헤더 대신에 비표준인 Proxy-Connection 확장 헤더를 프록시에게 전달한다.
    • 넷스케이프는 미국의 인터넷, 소프트웨어, 통신 산업을 하는 회사다.
  • 영리한 프록시는 Proxy-Connection 헤더 요청을 받으면 keep-alive 요청을 맺기 위한 요청을 보낸다.
    • 그런데 이 경우도 프록시가 하나일 때 제대로 작동한다.

 

 

4.5.8 HTTP/1.1의 지속 커넥션

  • HTTP/1.1에서는 keep-alive 커넥션을 지원하지 않는 대신 설계가 더 개선된 지속커넥션을 지원함
  • HTTP/1.1은 기본적으로 커넥션을 지속 커넥션으로 활성화 한다.
    • 커넥션을 끊기 위해 별도로 Connection:close 헤더를 명시해야 한다.

 

 

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

다음은 지속 커넥션에 대한 몇가지 제한과 사용방법에 대한 상세 내용이다.

  • 클라이언트는 Connection:close 헤더와 함께 다른 요청도 보낼 수 없음
  • 커넥션에 있는 모든 메시지가 자신의 길이 정보를 정확히 가지고 있을 때만 지속 가능
  • HTTP/1.1 프록시는 클라이언트와 서버 각각에 대해 별도의 지속 커넥션을 맺고 관리해야함
  • 하나의 사용자 클라이언트는 서버의 과부하를 방지하기 위해서 넉넉잡아 두개의 지속 커넥션만을 유지해야 한다.

 

 

4.6 파이프라인 커넥션

  • HTTP/1.1은 지속 커넥션을 통해서 파이프라이닝 할 수 있다.
    • 이는 keep-alive 커넥션의 성능을 더 높여준다.
    • 응답이 도착하기 전에 큐에 요청이 쌓인다.
    • 첫 번째 요청이 네트워크를 통해 지구 반대편에 있는 서버로 전달되면, 거기에 이어 두 번째와 세 번째 요청이 전달될 수 있다.
    • 이는 대기시간이 긴 네트워크 상황에서 네트워크상의 왕복으로 인한 시간을 줄여서 성능을 높여준다.
  • 제약 사항
    • HTTP 클라이언트는 커넥션이 지속 커넥션인지 확인하기 전까지 파이프라인을 이어선 안된다.
    • 응답은 요청 순서와 같게 와야한다.
    • POST 요청같이 반복해서 보낼 경우 문제가 생기는 요청은 파이프라인을 통해 보내면 안된다.

 

 

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

  • 커넥션 관리에는 명확한 기준이 없다.

'CS > 네트워크' 카테고리의 다른 글

[HTTP] 프록시  (0) 2021.09.28
[HTTP] 웹 서버  (0) 2021.09.25
[HTTP] HTTP 메시지 (메소드, 상태코드, 헤더)  (0) 2021.09.12
[HTTP] URL과 리소스 정리  (0) 2021.09.05
[HTTP] 웹 기초  (0) 2021.09.03

+ Recent posts