NOTEHTTP가 느린 이유에는 두 가지 요소가 있다 ; 1. TCP 연결 지연 (TCP Slow Start) 2. 커넥션 지연
키워드
TCP 커넥션
, 소켓
, 커넥션 지연
, 병렬 커넥션
, 지속 커넥션
, 홉별 헤더
, 커넥션 헤더
, 멱등성
메모 및 핵심 요점
- HTTP 가 메시지를 보내는 과정을 생각해보자. 먼저 HTTP 가 현재 연결되어 있는 TCP 커넥션을 통해 메시지 데이터의 내용을 순서대로 보낸다. (애플리케이션 계층 → 전송 계층) 이 후, TCP에서는 이 데이터를 세그먼트라는 단위로 분할 한 후, IP 패킷에 담아 (네트워크 계층(인터넷)을 통하여, 데이터를 전송해야 하므로. 네트워크 계층으로 보내기 위해 해당 프로토콜이 필요하다) 인터넷을 통해 데이터를 전송한다.
- 🌧️ 결국 네트워크의 계층 단위로 타고 내려가는 형태를 생각하면 된다. 이 후에는 데이터 링크 계층을 통하여 IP가 MAC 주소로 변환될 것(물리적인 주소를 찾아가는 과정이라고 생각)을 예상할 수 있다.
- 운영체제와 프로그램 언어에서 TCP 커넥션의 생성과 관련된 여러 기능들을 소켓 API를 통하여 지원한다. 이 소켓 API를 통하여, 클라이언트와 서버의 TCP endpoint를 연결하여 데이터 스트림을 읽고 쓸 수 있다. (즉, 소켓 API를 통하여 HTTP 트랜잭션을 수행할 수 있다)
socket
(새로운 소켓을 만든다),bind
(소켓을 포트로 묶는다),listen
(소켓 커넥션을 허가한다),accpet
(소켓 커넥션을 기다린다),write
(요청을 보낸다),read
(요청을 읽는다),close
(커넥션을 닫는다)
- HTTP 성능에 영향을 주는 요소들 : TCP 성능, HTTP 커넥션의 성능 (커넥션이 순차적으로 처리될 경우, 물리적인 지연과 UX 적인 지연이 생길 수 있음)
- HTTP Connection Header 는 애플리케이션이 현재 맺고 있는 커넥션에만 적용될 옵션 을 지정한다(커넥션 헤더는 hop-by-hop 헤더다, 오직 하나의 전송 링크에만 적용되며 다음 서버로 전달되어서는 안된다). 따라서 이 커넥션이 종료되고 다음 커넥션에 메시지를 전달하기 전에, 이 헤더의 내용은 모두 삭제하여야 한다.
- 또한, Connection 헤더에 기술되어 있지는 않더라도 홉별 헤더인 것들도 있다.
- 특정 브라우저의 경우, 객체를 화면에 그릴 때 객체의 크기를 알아야 한다. 따라서 이러한 객체를 내려받기 전에 레이아웃을 그리기 위해 HTML에서 폭과 높이를 기술하여 레이아웃 지연을 없앨 수 있다.
- 🌧️ LightHouse 를 통하여 성능 분석을 할 때, 이미지 객체의
width
와height
를 명시하라는 이야기를 하는 이유인 것 같다. 또한, 많은 이미지 최적화 도구 (예:next/image
) 에서 이미지 렌더링 성능을 향상시키기 위하여size
를 명시하도록 하는 내용과 일맥상통 하다.
- 🌧️ LightHouse 를 통하여 성능 분석을 할 때, 이미지 객체의
- 커넥션 지연을 막기 위한 방안
- 병렬 커넥션 : 리소스 별로 connection을 사용한다. 즉 리소스 별로 connection 을 사용함으로써, 각 리소스를 동시에 내려받는다.
- connection 을 한 번에 여러 개 사용하므로, 다수의 커넥션에 대한 성능 부하가 생길 수 있다. (서버에 부담이 커진다.)
- 브라우저에서는 병렬 커넥션을 허용하지만, 적은 수의 병렬 커넥션 만을 허용한다.
- 지속 커넥션 : site locality 를 이용한다. 이는 대부분 client 는 보통 같은 site 에 여러 개의 connection 을 맺는다는 것에 기인한다.
- connection의 처리가 완료된 이후에도 이 connection 을 계속해서 유지한다. 따라서 connection 을 맺고 끊음에 대한 overhead 가 줄어들며, 추가적으로 TCP slow start 에 대한 overhead 도 줄어들게 된다.
- 지속 커넥션을 사용하는 경우, 커넥션을 잘못 관리하면 서버에 계속 연결되어있는 지속 connection 이 생성되게 된다.
- 🌧️ 지속 커넥션을 사용할 때는 커넥션에 있는 모든 메시지가 자신의 길이 정보를 정확히 가지고 있을 때만 커넥션을 지속할 수 있다. 트랜잭션이 끝나는 시점에 기존 메시지의 끝과 새로운 매시지의 시작점을 구분하기 위하여 길이정보를 사용한다
- 지속 커넥션을 사용하는 방법 :
Connection: keep-alive
헤더, HTTP/1.1 의 지속 커넥션
- 병렬 커넥션 : 리소스 별로 connection을 사용한다. 즉 리소스 별로 connection 을 사용함으로써, 각 리소스를 동시에 내려받는다.
- 프록시에서는 Connection 헤더나, 홉별 헤더를 사용하면 문제가 생길 수 있으므로 (멍청한 프록시 문제) 프프록시는 Connection 헤더와 Connection 헤더에 명시된 헤더들은 절대로 전달해서는 안된다. 또한 홉별 헤더들 역시 전달하거나 캐시해서는 안된다.
- 멱등(idempotent) - 한 번 또는 여러 번 실행됐는지에 상관없이 같은 결과를 반환 (반대 : 비멱등 (unidempotent)
- HTTP 메서드에서 멱등성을 판단할 때는 백엔드의 상태 만을 보고 판단한다. 즉, 요청을 몇 번 보냈던간에 서버의 상태가 항상 같으면 멱등성을 가지는 메서드라고 한다.
- 안전한 메서드와 멱등성은 다르다. 모든 안전한 메서드들은 멱등성을 갖지만, 역은 성립하지 않는다. 안전한 메서드란 요청을 보냈을 때, 서버의 상태를 변경시키지 않는 메서드이다.
인용
HTTP가 메시지를 전송하고자 할 경우, 현재 연결되어 있는 TCP 커넥션을 통해서 메시지 데이터의 내용을 순서대로 보낸다. TCP는 세그먼트라는 단위로 데이터 스트림을 잘게 나누고, 세그먼트를 IP 패킷이라고 불리는 봉투에 담아서 인터넷을 통해 데이터를 전달한다. (87p)
소켓 API를 사용하면, TCP 종단 (endpoint) 데이터 구조를 생성하고, 원격 서버의 TCP 종단에 그 종단 데이터 구조를 연결하여 데이터 스트림을 읽고 쓸 수 있다. (90p)
HTML 퍼블리셔는 이미지와 같은 내부 객체에 대한 HTML 태그에 폭(width)와 높이(height)를 기술하여 ‘레이아웃 지연’을 없앨 수 있다. 내부 객체들에 대한 폭과 높이를 명시함으로써 브라우저는 서버에서 객첻ㄹ을 내려받기 전에 레이아웃을 그릴 수 있다. (101p)
멍청한 프락시는 Connection: Keep-Allive 와 같은 홉별 헤더를 무조건 전달하기 때문에 문제를 일으킨다. 홉별 헤더들은 한 개의 특정 커넥션에서 쓰이고 그 이후에는 전달하면 안 된다. 홉별 헤더를 전달받은 서버가 그 헤더를 자신과 프락시 간의 커넥션에 대한 것으로 오해하면서 문제가 생기는 것이다. (111p)
HTTP/1.1 에서는 keep-alive 커넥션을 지원하지 않는 대신, 설계가 더 개선된 지속 커넥션을 지원한다. 지속 커넥션의 목적은 keep-alive 커넥션과 같지만 그에 비해 더 잘 동작한다. (112p)