HTTP 완벽 가이드 4주차 4장

박정훈·2022년 8월 19일
1

HTTP 완벽 가이드

목록 보기
4/8
post-thumbnail

📚독서 스터디

독서 스터디도 4주차가 지나가네요.
이번 4주차도 어김없이 읽은 내용을 정리하고 문제를 만들어 가야합니다!

전세계 모든 HTTP 통신은, 지구상의 컴퓨터와 네트워크 장비에서 널리 쓰이고 있는 패킷 교환 네트워크 프로토콜들의 계층화된 집합인 TCP/IP를 통해 이루어진다. 세계 어디서든 클라이언트 애플리케이션은 서버 애플리케이션으로 TCP/IP 커넥션을 맺을 수 있다.
-HTTP 완벽 가이드 p.85-

📌TCP

TCP 커넥션은 인터넷을 안정적으로 연결해준다. HTTP에게 신뢰할 만한 통신 방식을 제공한다.

⚡IP패킷

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

⚡TCP 커넥션

TCP는 포트 번호를 통해서 여러개의 커넥션을 유지한다. 포트 번호는 회사 직원의 내선전화와 같다. 대표 전화번호는 안내 데스크로 연결되고 내선전화는 직원으로 연결되듯이 IP주소는 해당 컴퓨터에 연결되고 포트번호는 애플리케이션으로 연결된다. TCP 커넥션은 네가지 값으로 식별한다.
발신지 IP 주소, 발신지 포트, 수신지 IP 주소, 수신지 포트

이 네 가지 값으로 유일한 커넥션을 생성한다. 서로 다른 두 개의 TCP 커넥션은 네가지 주소 구성요소의 값이 모두 같을 수 없다.

📌TCP 성능 고려

트랜잭션 지연

  1. 클라이언트는 URI에서 웹 서버의 IP 주소와 포트 번호를 알아내야 한다. 현재는 DNS 이름 분석이 밀리초 단위로 끝난다. 또한 대부분의 HTTP 클라이언트는 최근 접속한 사이트에 대한 IP 주소의 DNS 캐시를 가지고 있다.
  2. 클라이언트는 TCP 커넥션 요청을 서버에게 보내고 서버가 커넥션 허가 응답을 회신하기를 기다린다. 이 역시 인프라의 발전으로 1초 미만으로 끝난다.
  3. 커넥션이 맺어지면 클라이언트는 HTTP 요청을 새로 생성된 TCP 파이프를 통해 전송한다. 웹 서버는 데이터가 도착하는 대로 TCP 커넥션에서 요청 메시지를 읽고 처리한다. 이 일련의 과정에서 시간이 소요된다.
  4. 웹 서버가 HTTP 응답을 보내는 것 역시 시간이 소요된다.

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

  1. 클라이언트는 새로운 TCP 커넥션을 생성하기 위해 작은 TCP 패킷(40~60바이트)을 서버에게 보낸다. 그 패킷은 SYN라는 특별한 플래그를 가지는데, 이게 커넥션 생성 요청이다.
  2. 서버가 커넥션 요청을 받으면 몇가지 커넥션 매개변수를 산출, 커넥션 요청이 받아들여졌음을 의미하는 SYNACK 플래그를 포함한 TCP 패킷을 클라이언트에게 보낸다.
  3. 클라이언트는 다시 서버에게 커넥션이 잘 맺어졌다고 확인응답 신호를 보낸다. (이제는 확인응답과 데이터를 함께 보낼 수 있다.)

HTTP 프로그래머는 패킷들을 보지 못한다. 오직 TCP 커넥션이 생성될 때 발생하는 지연이 전부다.
크기가 작은 HTTP 트랜잭션은 50% 이상의 시간을 TCP를 구성하는 데 쓴다. 이러한 지연을 제거하기 위해 HTTP 이미 존재하는 커넥션을 어떻게 재활용하는지 후에 나온다.

⚡확인응답 지연

인터넷 자체가 패킷 전송을 완벽히 보장하지 않기에, TCP는 자체적인 확인 체계를 가진다. 각 세그먼트의 수신자는 세그먼트를 온전히 받으면 확인응답 패킷을 송신자에게 반환한다. 송신자가 이를 받지 못하면 데이터가 유실되었거나 오류가 생겼다고 판단, 다시 보낸다.
확인응답은 그 크기가 작기에, 데이터 패킷에 편승 시킨다. 안타깝게도 요청과 응답 두 가지 형식으로만 이루어지는 HTTP 동작 방식은, 편승할 기회를 감소시킨다.
막상 편승하려고 해도, 패킷이 많지 않기에 확인응답 지연 알고리즘으로 인해 지연이 자주 발생한다.

확인응답 지연은 송출할 확인응답을 특정 시간 동안 버퍼에 저장해 두고, 확인응답을 편승시키기 위한 송출 데이터 패킷을 찾는다. 만약 일정 시간 안에 송출 데이터 패킷을 찾지 못하면 확인응답은 별도 패킷을 만들어 전송된다.

⚡TCP 느린 시작

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

⚡네이글 알고리즘과 TCP_NODELAY

네이글 알고리즘은 세그먼트가 최대 크기(LAN상에서 1500바이트, 인터넷상에서 수백바이트)가 되지 않으면 전송하지 않는다. 다만 다른 모든 패킷이 확인응답을 받았을 경우에는 최대 크기보다 작은 패킷의 전송을 허락한다. 이 알고리즘은 HTTP 성능 관련해서 여러 문제가 있다.

  1. 크기가 작은 HTTP 메시지는 패킷을 채우지 못하기 때문에, 앞으로 생길지 생기지 않을지 모르는 추가적인 데이터를 기다리며 지연된다.
  2. 확인응답 지연과 함께 쓰일 경우 형편없이 동작한다. 네이글 알고리즘은 확인응답이 도착할 때까지 데이터 전송을 멈추고, 확인응답 지연 알고리즘은 확인응답을 100~200밀리초 지연시킨다.

따라서 TCP_NODELAY 값을 설정해 알고리즘을 비활성화하기도 한다.

⚡TIME_WAIT의 누적과 포트 고갈

일반적으로 2MSL의 커넥션 종료 지연이 문제가 되지는 않지만, 성능시험을 하는 상황에서는 문제가 될 수 있다. ...(중략)... 클라이언트가 서버에 접속할 때마다, 유일한 커넥션을 생성하기 위해서 새로운 발신지 포트를 쓴다. 하지만 사용할 수 있는 발신지 포트의 수는 제한되어 있고(60,000개로 가정) 2MLS(120초로 가정)동안 커넥션이 재사용될 수 없으므로, 초당 500개로 커넥션이 제한된다. 서버가 초당 500개 이상의 트랜잭션을 처리할 만큼 빠르지 않다면 TIME_WAIT 포트 고갈은 일어나지 않는다. 포트 고갈 문제가 아니더라고, 커넥션을 너무 많이 맺거나, 대기 상태로 있는 제어 블록이 너무 많아지는 상황은 주의해야 한다.

📌HTTP 커넥션 관리

⚡Connection 헤더

커넥션 토큰이 HTTP 헤더 필드 명을 가지고 있으면, 해당 필드들은 현재 커넥션만을 위한 정보이므로 다음 커넥션에 전달하면 안 되고, 다른 곳으로 전달하는 시점에 삭제되어야 한다.

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

3개의 이미지가 있는 웹페이지가 있다. 하나는 해당 HTML을, 나머지 세 개는 첨부된 이미지를 받기 위한 것이다. 각 트랜잭션이 새로운 커넥션을 필요로 한다면, 커넥션을 맺는데 발생하는 지연과 함께 느린 시작 지연이 발생한다. 순차적인 처리로 인한 지연으로 물리적인 지연뿐 아니라, 심리적인 지연도 동반된다.

사용자는 여러개의 이미지가 동시에 로드되는 것을 더 좋아한다.

또한 브라우저의 경우 객체를 화면에 배치하려면 객체의 크기를 알아야 하기 때문에, 모든 객체를 내려받기 전까지는 텅 빈 화면을 보여준다.

📌HTTP 커넥션 성능 향상

⚡병렬 커넥션

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

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

각 커넥션의 지연시간을 겹치게 하면 총 지연 시간을 줄일 수 있고, 클라이언트의 인터넷 대역폭을 한 개의 커넥션이 다 써버리는 것이 아니라면 나머지 객체를 내려받는 데에 남은 대역폭을 사용할 수 있다.

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

클라이언트의 네트워크 대역폭이 좁을 때는 대부분 시간을 데이터를 전송하는 데만 쓴다. 다수의 커넥션은 메모리를 많이 소모하며, 성능 문제를 발생시킨다. 실제로 병렬 커넥션을 사용하긴 하지만, 최신 브라우저 기준으로 대부분 6~8개의 적은 병렬 커넥션을 지원한다.

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

실제로는 더 빠르게 로드하지 않는데, 화면에 여러개의 객체가 동시에 보이면서 내려받는 상황을 유저가 볼 수 있기 때문에 더 빠르다고 여긴다.

📌지속 커넥션

HTTP/1.1을 지원하는 기기는 처리가 완료된 후에도 TCP 커넥션을 유지하여 앞으로 있을 HTTP 요청에 재사용할 수 있다. 처리가 완료된 후에도 계속 연결된 상태로 있는 TCP 커넥션을 지속 커넥션이라고 부른다.

  • 이미 맺어져 있는 지속 커넥션을 사용함으로써, 준비작업에 따를 시간을 절약 가능
  • TCP의 느린 시작으로 인한 지연을 피함

⚡지속 vs 병렬

병렬 커넥션의 단점

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

지속 커넥션의 장점

  • 커넥션을 맺기 위한 사전 작업과 지연을 줄여줌
  • 튜닝된 커넥션 유지 (느린 시작에서, 패킷을 수차례 성공적으로 전송한 결과, 다수의 패킷을 전송할 수 있는 권한을 얻은 커넥션)하며 커넥션의 수를 줄여줌

잘못 관리할 경우, 계속 연결된 상태로 있는 수많은 커넥션이 쌓이게 될 것이다. 이는 로컬의 리소스 그리고 원격의 클라이언트와 서버의 리소스에 불필요한 소모를 발생시킨다. 오늘날 많은 웹앱플리케이션은 적은 수의 병렬 커넥션만을 맺고 그것을 유지한다.

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

Keep-alive는 HTTP/1.1 명세에서 빠졌다. 하지만 아직도 keep-alive 핸드셰이크가 널리 사용되고 있기에, HTTP 애플리케이션을 이를 처리할 수 있게 개발해야 한다. 동작하는 방식을 간단히 살펴보면

  • 클라이언트는 커넥션을 유지하기 위해서 요청에 Connection:Keep-Alive 헤더를 포함시킨다.
  • 요청을 받은 서버는 그다음 요청도 이 커넥션을 통해 받고자 한다면, 응답 메시지에 같은 헤더를 포함시켜 응답한다.
  • 응답에 Connection:Keep-Alive 헤더가 없다면, 서버가 keep-alive를 지원하지 않으며, 커넥션을 끊을 것이라 추정한다.

⚡Keep-Alive 옵션

Keep-Alive 헤더는 커넥션을 유지하기를 바라는 요청일 뿐이다. 이를 무조건 따를 필요는 없다. 언제든지 커넥션을 끊을 수 있고, Keep-Alive 커넥션에서 처리되는 트랜잭션의 수를 제한할 수도 있다.

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

  • 커넥션이 끊어지기 전에 엔터티 본문의 길이를 알 수 있어야 커넥션을 유지할 수 있다.
  • 프락시와 게이트웨이는 Connection 헤더의 규칙을 철저히 지켜야 한다. 프락시와 게이트웨이는 메시지를 전달하거나 캐시에 넣기 전에 Connection 헤더에 명시된 모든 헤더 필드와 Connection 헤더를 제거해야 한다.
  • 정석대로라면 Keep-Alive 커넥션은 Connection 헤더를 인식하지 못하는 프락시 서버와는 맺어지면 안 된다.
  • 클라이언트는 응답 전체를 모두 받기 전에 커넥션이 끊어졌을 경우, 별다른 문제가 없으면 요청을 다시 보낼 수 있게 준비되어 있어야 한다.

⚡멍청한 프락시

  • 프락시는 Connection 헤더를 이해하지 못한다. 따라서 다음 서버에 메시지를 그대로 전달한다.
  • 서버는 keep-alive 요청을 받아들인다. 서버와 프락시가 keep-alive 규칙에 맞게 통신을 하게 되었다.
  • 이제 멍청한 프락시는 서버로부터 받은 keep-alive 헤더를 포함하고 있는 응답 메시지를 클라이언트에게 전달한다. 이제 클라이언트와 프락시 keep-alive 규칙에 맞게 통신을 하게 되었다.
  • 프락시는 keep-alive를 전혀 모르지만, 받았던 모든 데이터를 그대로 클라이언트에게 전달, 서버가 커넥션을 끊기를 기다린다. 그렇지만 둘은 keep-alive가 맺어졌기에 커넥션이 끊어지지 않는다.
  • 클라이언트가 응답 메시지를 받으면, 다음 요청을 보내기 시작한다.프락시는 같은 커넥션 상에서 다른 요청이 오는 경우는 예상하지 못하기에, 해당 요청은 프락시로부터 무시되고 브라우저는 아무런 응답 없이 로딩이라는 표시만 나온다.

⚡Proxy-Connection

넷스케이프는 멍청한 프락시 문제를 해결하기 위해 브라우저에서 전달하는 Connection 헤더 대신에 비표준인 Proxy-Connection 확장 헤더를 프락시에게 전달한다. 영리한 프락시라면, 이 의미 없는 Proxy-Connection 헤더를 Connection 헤더로 바꾼다.

⚡지속 커넥션

HTTP/1.1에서 keep-alive를 지원하지 않는 대신, 지속 커넥션을 기본적으로 지원한다. 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection:close헤더를 명시해야 한다. 그치만 Connection:close를 보내지 않는 것이 서버가 커넥션을 영원히 유지하겠다는 뜻은 아니다.

⚡지속 커넥션 제한과 규칙

  • 클라이언트가 커넥션으로 추가적인 요청을 보내지 않을 것이라면, 마지막 요청에 Connection:close 헤더를 보내야 한다.
  • 커넥션에 있는 모든 메시지가 자신의 길이 정보를 정확히 가지고 있어야 유지된다.
  • HTTP/1.1은 중간에 끊어지는 커넥션을 복구 할 수 있다.

🎉우아한 커넥션 끊기

HTTP 명세에서는 클라이언트나 서버가 예기치 않게 커넥션을 끊어야 한다면, 우아하게 커넥션을 끊어야 한다라고 하지만, 정작 그 방법은 설명하고 있지 않다.

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

🤸‍♂️ 문제

1. HTTP와 HTTPS 네트워크 프로토콜 스택

IP는 (   )계층, TCP는 (   )계층, HTTP는 (   )계층이다. HTTP에 보안 기능을 더한 HTTPS는 (   ) 혹은 (   )이라 불리기도 하며 HTTP와 TCP 사이에 있는 (   )계층이다.

네트워크, 전송, 애플리케이션, TLS, SSL, 암호화(보안)

2. 옳은 것을 모두 고르세요.

a) TCP 커넥션은 네 가지 값으로 식별한다. 서로 다른 두개의 TCP 커넥션은 네가지 주소 구성요소의 값이 모두 같을 수 있다.
b) HTTP 프로그래머는 새로운 TCP 커넥션이 생성될 때 발생하는 지연 및 패킷까지 보고 제어한다.
c) HTTP 커넥션의 성능을 향상시킬 수 있는 여러 최신 기술들로는 병렬, 지속, 파이프라인, 다중커넥션이 있다.
d) 병렬 커넥션은 언제나 더 빠른 방식임을 보장한다.
e) 클라이언트나 서버가 keep-alive 요청을 받았다면 반드시 이를 따라야 함을 보장해야 한다.
f) 애플리케이션은 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection: close 헤더를 명시해야 한다. 해당 헤더가 없으면 서버가 커넥션을 영원히 유지하겠다는 뜻이다.
g) 클라이언트나 서버가 예기치 않게 커넥션을 끊어야 한다면 우아하게 커넥션을 끊어야 한다라고 하지만, 정작 그 방법은 설명하고 있지 않다.

c, g

🙆‍♂️마무리

내용이 꽤 많았다. TCP에 대해서 꽤 깊숙하게 들여다 본 느낌... 왜 지연이 일어나는지에 대해 책을 읽다보면 내용이 꽤 재밌다. 이걸 기억하는건 별개의 문제겠지만! 특히 우아한 커넥션 끊기는 정말이지... 기억에 남는다.

profile
그냥 개인적으로 공부한 글들에 불과

0개의 댓글