Article/Network - Deep Dive

HTTP/2.0 Stream Blocking & 흐름제어

조금씩 차근차근 2025. 4. 24. 19:32

HTTP/2.0의 흐름제어는 뭘까?

  • 흐름 제어는 전송계층(TCP)에서 수행하는 것 아닌가?
    • 한 스트림이 커넥션을 독차지하지 않도록, 연결에 대한 전체 스트림의 리소스 배분을 수행해야 한다.
    • 이것이 HTTP/2.0의 흐름제어이다.

 

HTTP/2.0 흐름제어와 TCP 흐름제어의 차이점

항목 TCP 흐름 제어 HTTP/2.0 흐름 제어
보호 대상 - 연결 전체 (TCP 수신 버퍼) - 스트림 별
- 연결 전체
제어 단위 - 바이트 (시퀀스 번호) - 프레임 페이로드 (바이트)
주요 목적 - 수신 측의 소켓 버퍼가 넘치지 않도록 전송 속도 억제
- 연결 안정성 유지
- 멀티플렉싱되는 여러 스트림 간 자원 경쟁 완화
- 특정 스트림 일시적 쓰로틀링/우선순위 조절
윈도우 크기 표기 - 16-bit Window Size 필드 - WINDOW_UPDATE 프레임의 credit 증분 값 (credit: 현재 송신자에게 남은 전송 가능 바이트 수)
윈도우 증분 방법 - ACK 세그먼트마다 절대 크기 전달
- 증가/감소 모두 허용
- 데이터 소비 후 수신 애플리케이션이 임의 시점에 credit 추가
- 프로토콜상 수신 측의 감소 금지 (최대 2³¹-1까지 허용)
적용 범위 - 하나의 연결에 단일 윈도우 - 각 스트림마다 독립 윈도우 (streamWindow[i])
- 전체 스트림 관리용 연결 윈도우 (connectionWindow)
전송 중단 방법 - 수신 측이 윈도우 크기를 0으로 설정 후 전송
- 송신 측은 0-window probing으로 주기적 윈도우 개방 검사
- 수신 측이 특정 스트림의 윈도우만 0으로 설정하여 선택적 블로킹 수행
- 수신 측이 WINDOW_UPDATE 전송 시까지 송신 대기 (수신 측이 능동적으로 WINDOW_SIZE 전송함)
구현 위치 - 커널 내부 TCP 스택 - 사용자 공간 HTTP/2 라이브러리
튜닝 지점 차이 - OS 버퍼 및 Socket 옵션으로 초기 윈도우 크기 설정 - SETTINGS_INITIAL_WINDOW_SIZE 설정으로 조정

한번 아래 내용을 쭉 읽고, 다시 위로 올라와서 읽어보길 바란다.

HTTP/2.0 흐름 제어의 동작 원리

HTTTP/2.0의 "스트림 블로킹(Stream Blocking)"이란?

흐름 제어 윈도우 고갈

  • HTTP/2.0은 스트림(Stream) 단위로 요청을 처리한다.
  • 여러개의 스트림이 하나의 커넥션을 공유한다.
  • 개별 스트림 윈도우가 0이 되면, 그 스트림만 송신을 일시 정지시킨다.
  • 연결 전체 윈도우가 0이 되면, 모든 스트림의 데이터 전송을 멈춘다.

동시 스트림 제한

  • 한 커넥션에 열 수 있는 동시 스트림 수는 MAX_CONCURRENT_STREAMS 설정으로 제한되어 있다.
  • 예를 들어, MAX_CONCURRENT_STREAMS=100 이면, 100개의 스트림이 이미 열려 있는 동안, 새로운 요청은 블로킹된다.
  • RFC에서는 CONCURRENCY를 지나치게 제한하지 않기 위해, 이 값을 최소 100 이상으로 설정할 것을 권장한다.

HTTP/2.0의 흐름 제어 구조와 동작 방식

흐름 제어 윈도우

  • 흐름 제어 윈도우는 두 종류가 있다.
    • 스트림 윈도우(Stream Window)
    • 연결 전역 윈도우(Connection Flow-Control Window)
  • 각 엔드포인트는 각자 수신자로서 윈도우 크기를 광고하고, 송신자로서 상대가 광고한 제한을 준수해야 한다.

WINDOW_UPDATE FRAME

  • 각 송신자는 상대방 윈도우에 대한 credit(보낼 수 있는 바이트 한도)를 갖는다.
  • 수신자 측에서 데이터를 처리하거나 버퍼에 여유가 생길 때, WINDOW_UPDATE 프레임을 보내어 상대방의 credit을 증가시킨다.
  • HTTP/2.0 에서 흐름 제어는 오직 DATA 프레임에만 적용되며, WINDOW_UPDATE, HEADERS, RST_STREAM 등의 제어 프레임은 윈도우의 제약을 받지 않는다.

스트림 블로킹/흐름 제어 예시

1. 초기 상태

  • 클라이언트와 서버는 각각 스트림별(Stream-Level) 윈도우와 연결 전체(Connection-Level) 윈도우를 만든다.
  • 이 윈도우 크기는 데이터 수신 측이 한 번에 받을 수 있는 데이터 양(크레딧)을 의미한다.
  • 모든 스트림 윈도우와 커넥션 레벨 윈도우가 65KB의 윈도우를 가지고 있다고 가정하자.

2. 데이터 전송과 윈도우 소진

  • 서버가 스트림 1로 65KB짜리 데이터를 보내면, 해당 데이터 크기만큼 스트림 1의 윈도우와 연결 전체 윈도우가 모두 감소한다.
  • 65KB를 모두 보내면 두 윈도우의 크레딧이 0이 되어 서버는 더 이상 스트림 1 (그리고 잠재적으로 다른 스트림에도) 데이터를 보낼 수 없게 된다.
  • 흐름 제어로 인한 일시 중지 상태가 되었다.

3. 클라이언트의 데이터 처리 및 WINDOW_UPDATE

  • 클라이언트는 수신한 데이터를 처리하여 내부 버퍼 공간을 확보했다고 가정하자.
  • 공간이 생기면, 서버에게 데이터를 더 보내도 좋다는 의미로 WINDOW_UPDATE 프레임을 전송하여, 해당 스트림(여기서는 스트림 1) 및 연결 전체의 윈도우 크레딧을 다시 늘려준다.
  • 수신자가 65KB의 크레딧을 다시 부여한다고 가정하자.

4. 전송 재개 및 스트림 간 독립성 - HoL Blocking 문제 완화

  • 서버가 WINDOW_UPDATE을 받아 스트림 1에 새 윈도우가 생기면, 남은 응답 데이터를 이어서 전송한다.
  • 만약 연결 전체 윈도우만 WINDOW_UPDATE를 보내게 된다면(Stream ID = 0), 스트림 1이 윈도우 고갈 또는 클라이언트 처리 지연으로 잠시 멈추더라도, 이는 스트림 1에 국한된 문제가 된다.
  • 만약 연결 전체의 윈도우에 여유가 있고 다른 스트림(예: 스트림 3)의 윈도우에도 여유가 있다면, 서버는 스트림 3의 데이터를 계속 전송할 수 있게 된다.

최적화 전략

윈도우 크기 튜닝 및 흐름제어 해제

  • 대용량 파일 전송이나 gRPC 같이, 양단이 고속으로 데이터를 주고받아야 할 때는,
  • 초기 윈도우 크기를 기본 65KB에서 수백KB~수MB 수준으로 올려 프로토콜 레벨 제약을 줄이는 경우도 많다.
  • SETTINGS_INITIAL_WINDOW_SIZE

동시 스트림과 연결 관리

  • 대용량 프레임이 전송되는 도중에는, 제어 프레임의 전송이 늦어질 수 있다.
  • 대용량 파일과 짧은 요청이 섞여있다면, 브라우저나 클라이언트가 해당 대용량 전송 요청을 별도로 처리하도록 유도하자.
    • 한 스트림이 연결을 전부 사용하지 않도록!
    • HTTP/1.1 서버에게 요청하도록 만들기.

프레임 크기 압축 및 최적화

  • MAX_FRAME_SIZE 값을 너무 크게 키우면 제어 프레임의 전송이 늦어질 수 있고
  • MAX_FRAME_SIZE 값을 너무 작게 줄이면 패킷 오버헤드가 증가한다.
  • 네트워크의 혼잡과 지연 간 균형을 맞출 수 있는 값을 설정하자.

스트림의 연결 과정 - open/close 상태 전이(작성예정)

 

참고 자료

- RFC 9113

 

RFC 9113: HTTP/2

This specification describes an optimized expression of the semantics of the Hypertext Transfer Protocol (HTTP), referred to as HTTP version 2 (HTTP/2). HTTP/2 enables a more efficient use of network resources and a reduced latency by introducing field com

datatracker.ietf.org