웹 개발에서의 Chunked Transfer — 개념, 형식 및 청크 전송 원리

저자: IT Sectr 게시일: 2026-03-10 읽는 시간: 9 분

Chunked Transfer는 HTTP 프로토콜의 메커니즘으로, 서버가 응답 본문을 개별 조각(청크)으로 나누어 전체 데이터 크기를 미리 지정하지 않고 전송합니다. 각 청크에는 16진수 형식의 크기와 지정된 길이의 데이터가 포함되며, 크기가 0인 최종 청크로 종료됩니다. MDN Web Docs, 2025에 따르면, Transfer-Encoding: chunked는 응답 크기를 미리 알 수 없을 때 서버에 의해 자동으로 활성화됩니다. 예를 들어, 즉석 콘텐츠 생성 또는 데이터 스트리밍 전송 시에 사용됩니다.

핵심 사항

  • Chunked Transfer — Content-Length를 미리 지정하지 않고 HTTP 응답을 부분으로 나누어 전송.
  • Transfer-Encoding: chunked — 청크 데이터 전송 모드를 활성화하는 헤더.
  • 각 청크는 16진수 크기, 데이터 및 끝에 CRLF를 포함하며, 크기가 0인 청크로 종료를 표시.
  • Streaming — 오디오, 비디오 및 SSE 이벤트 전송을 위한 chunked transfer의 주요 용도.
  • Chunked Transfer는 Content-Length 헤더와 호환되지 않으며 동시에 사용되지 않음.

Chunked Transfer란?

Chunked Transfer는 HTTP/1.1 사양(RFC 7230, 섹션 4.1)에 정의된 HTTP 메커니즘으로, 서버가 전체 Content-Length를 지정하지 않고 응답 본문을 부분적으로 전송할 수 있게 합니다. 전송 전에 응답 크기를 계산하는 대신, 서버는 데이터 조각이 준비되는 대로 즉시 전송을 시작합니다. 각 조각에는 자체 크기 헤더가 포함되어 클라이언트가 조각에서 응답을 조립할 수 있습니다.

이 메커니즘은 Transfer-Encoding: chunked 헤더에 의해 활성화됩니다. 클라이언트가 응답에서 이 헤더를 확인하면 본문이 청크로 전송됨을 인지하고 루프로 응답을 읽어야 합니다. 청크 크기를 읽고, 지정된 크기의 데이터를 읽고, 반복합니다. 크기가 0인 청크가 발견되면 프로세스가 종료됩니다. Chunked Transfer는 HTTP/1.1의 필수 부분이며, 모든 최신 웹 서버와 HTTP 클라이언트에서 지원됩니다.

청크 전송을 사용하는 주된 이유는 동적 콘텐츠 생성입니다. 서버가 데이터베이스 쿼리, 외부 API 또는 장기 계산을 기반으로 응답을 생성할 때 결과 크기를 미리 알 수 없습니다. 전체 응답을 메모리에 버퍼링하는 대신(대용량에 위험함), 서버는 Transfer-Encoding: chunked를 활성화하고 데이터를 사용 가능해지면 전송합니다. 이는 메모리가 제한된 서버와 크기가 매우 클 수 있는 응답(100 MB 이상)에 특히 중요합니다.

HTTP/1.1 청크와 HTTP/2의 차이

HTTP/2에서는 프로토콜이 프레임 수준에서 스트림 다중화를 사용하므로 청크 전송 메커니즘 자체가 존재하지 않습니다. HTTP/2에서는 모든 크기의 데이터가 DATA 프레임으로 전송되며, 응답 본문의 크기를 미리 선언할 필요가 없습니다. 스트림은 언제든지 닫을 수 있습니다. 최신 서버는 HTTP/2 업스트림으로 프록시할 때 HTTP/1.1 청크 응답을 동등한 스트리밍 전송으로 자동 변환합니다. Chunked Transfer는 HTTP/1.1 연결에서 계속 중요합니다.

청크 전송 작동 방식

서버가 Chunked Transfer를 사용하기로 결정하면 Content-Length를 계산하지 않고 Transfer-Encoding: chunked 헤더를 전송합니다. 그런 다음 응답 본문이 청크 시퀀스로 형성됩니다. 각 청크는 16진수 형식(0x 접두사 없음)의 청크 크기를 포함하는 줄로 시작하며, 그 뒤에 CRLF( )가 옵니다. 그런 다음 지정된 크기의 청크 데이터가 오고 CRLF로 끝납니다. 마지막 청크의 크기는 0이며, 그 뒤에 trailer 헤더가 올 수 있습니다.

16진수 크기를 사용하면 1바이트부터 이론적으로 무제한까지 모든 크기의 청크를 전송할 수 있습니다. 실제로 청크 크기는 서버에 의해 선택됩니다. 일반적인 값은 4KB, 8KB 또는 16KB입니다. 최적의 청크 크기는 전송 계층에서 단편화를 최소화하기 위해 TCP 세그먼트 크기(이더넷의 경우 일반적으로 1460바이트)의 배수여야 합니다. Nginx는 기본적으로 4KB 청크를, Apache는 8KB 청크를 사용합니다.

Transfer-Encoding: chunked를 수신하는 클라이언트는 종료 영점 청크까지 응답을 청크별로 읽어야 합니다. 클라이언트가 청크 전송을 지원하지 않으면 서버는 이 모드를 사용할 수 없습니다. 실제로 모든 최신 HTTP 클라이언트(브라우저, OkHttp, URLSession, curl)는 청크 응답을 완전히 지원합니다. 스트리밍 읽기를 통해 클라이언트는 전체 응답을 받기 전에 데이터 처리를 시작할 수 있으며, 이는 성능에 매우 중요합니다.

청크 요소형식예시
청크 크기HEX + CRLF1000
청크 데이터[크기 바이트] + CRLF[4096바이트 데이터]
종료 청크0 0
Trailer(선택 사항)헤더 + CRLFExpires: Wed, 21 Oct 2025

Chunked Transfer의 Trailer 헤더

Chunked Transfer는 trailer 헤더(마지막 청크 후에 전송되는 추가 HTTP 헤더)를 지원합니다. 이는 응답 생성이 완료된 후에만 알 수 있는 메타데이터에 유용합니다. 예: Content-MD5 또는 X-Compression-Ratio. Trailer 헤더는 Trailer 헤더에 선언되어야 합니다: Trailer: Content-MD5, X-Compression-Ratio. 실제로 trailer는 거의 사용되지 않으며, 대부분의 서버는 응답에 이를 포함하지 않습니다.

Chunked 응답 형식

청크 응답은 클라이언트가 올바르게 파싱해야 하는 엄격하게 정의된 구조를 가지고 있습니다. Transfer-Encoding: chunked를 사용한 HTTP 응답의 전체 예를 살펴보겠습니다. 헤더와 빈 줄 다음에 응답 본문이 시작됩니다. 본문 구조는 다음과 같은 시퀀스입니다: 청크_크기 데이터 청크_크기 데이터 ... 0 까지. 각 크기는 ASCII 문자를 사용한 16진수 표기법으로 전송됩니다.

Chunked Transfer를 사용한 서버 응답 예시:
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

7
Hello
6
World!
0

이 예시에서 서버는 문자열 “Hello World!”를 두 개의 청크로 전송합니다. 첫 번째 청크는 7바이트로 “Hello ”를 포함하고, 두 번째는 6바이트로 “World!”를 포함합니다. 클라이언트는 두 청크에서 데이터를 수집하여 전체 문자열을 얻습니다. 중요: 청크 크기에는 데이터만 포함되며, 청크 자체의 CRLF 구분 기호는 포함되지 않습니다. 종료 빈 청크(0 )는 클라이언트에게 전송이 완료되었음을 알립니다.

kotlin
import java.net.HttpURLConnection
import java.io.BufferedReader
import java.io.InputStreamReader

fun readChunkedResponse() {
    val url = java.net.URL("https://stream.example.com/data")
    val connection = url.openConnection() as HttpURLConnection
    val reader = BufferedReader(
        InputStreamReader(connection.inputStream)
    )

    var line: String?
    while (reader.readLine().also { line = it } != null) {
        println("청크: $line")
    }
    reader.close()
}

OkHttp에서 청크 응답 파싱

OkHttp는 Chunked Transfer의 세부 사항으로부터 개발자를 완전히 추상화합니다. Transfer-Encoding: chunked가 포함된 응답을 수신하면 OkHttp가 자동으로 청크를 수집하고 response.body?.string()을 통해 개발자에게 전체 응답 본문을 제공합니다. 스트리밍 처리를 위해 response.body?.source()를 사용하며, BufferedSource를 반환하여 데이터가 도착할 때 읽을 수 있습니다. 개발자는 16진수 크기와 CRLF를 수동으로 파싱할 필요가 없으며 라이브러리가 자동으로 처리합니다.

Chunked Transfer vs Content-Length

Content-Length와 Transfer-Encoding: chunked는 HTTP 메시지 본문의 크기를 지정하는 상호 배타적인 두 가지 방법입니다. Content-Length는 본문의 정확한 크기를 바이트 단위로 포함하는 헤더입니다. 크기를 미리 알 수 있는 응답과 본문이 있는 요청(POST, PUT)에 필수입니다. Content-Length를 통해 클라이언트는 필요한 크기의 버퍼를 미리 할당하고 모든 데이터가 수신되었는지 확인할 수 있습니다.

Chunked Transfer는 본문 크기를 미리 알 수 없을 때 사용됩니다. 이는 세 가지 주요 시나리오에서 발생합니다: 동적 콘텐츠 생성(예: 결과를 아직 사용할 수 없는 데이터베이스 쿼리), 대용량 파일의 스트리밍 전송(파일 전체를 메모리에 버퍼링하지 않기 위해), 실시간 이벤트 전송을 위한 Server-Sent Events(SSE)입니다. 선택은 서버의 책임입니다. 서버가 전송 시작 전에 크기를 알고 있다면 더 간단하고 예측 가능한 메커니즘인 Content-Length를 사용해야 합니다.

HTTP/1.1 사양은 Content-Length와 Transfer-Encoding: chunked의 동시 사용을 금지합니다. 서버가 두 헤더를 모두 전송하면 클라이언트는 Content-Length를 무시하고 응답을 청크로 처리해야 합니다. 우선 순위는 RFC 7230에서 프록시 서버가 응답 본문을 수정하고 원래 Content-Length를 유지할 수 없는 경우를 위해 설정되었습니다. 일부 오래된 HTTP 클라이언트는 이 상황을 잘못 처리하지만 최신 구현은 사양을 따릅니다.

Content-Length가 불가능한 경우

Content-Length를 근본적으로 미리 계산할 수 없는 시나리오가 있습니다. 동적 보고서는 필터링 및 집계와 함께 요청 시 생성되며, 서버는 데이터베이스 쿼리가 완료될 때까지 데이터 양을 알 수 없습니다. 실시간 카메라에서 전송되는 스트리밍 비디오는 크기가 무한합니다. 알림을 위한 SSE 및 long polling은 응답이 무기한 지속될 수 있습니다. 이러한 모든 경우에 Chunked Transfer가 유일하게 올바른 메커니즘입니다.

Chunked transfer 기반 스트리밍

Chunked Transfer는 웹의 많은 스트리밍 기술의 기반이 됩니다. 가장 잘 알려진 것은 Server-Sent Events(SSE)로, 서버가 Transfer-Encoding: chunked를 사용하여 단일 HTTP 연결을 통해 클라이언트에 이벤트를 전송합니다. SSE는 특수 텍스트 형식(data: 메시지 )을 사용하지만 전송 계층은 일반 청크 전송입니다. 브라우저는 서버가 이벤트를 보낼 때 응답 완료를 기다리지 않고 수신합니다.

오디오 및 비디오 스트리밍도 Chunked Transfer에 의존합니다. Nginx RTMP 및 Wowza Streaming Engine과 같은 미디어 서버는 HTTP를 통해 미디어 데이터를 청크로 전송합니다. 클라이언트 측 플레이어는 첫 번째 청크를 수신하자마자 재생을 시작하며, 파일이 완전히 로드될 때까지 기다리지 않습니다. 이렇게 하면 첫 프레임까지의 시간이 수십 초에서 1-2초로 단축됩니다. YouTube와 Netflix는 HTTP 스트림에 정확히 이 접근 방식을 사용합니다.

모바일 개발에서 Chunked Transfer는 전체 응답을 메모리에 로드하지 않고 대량의 데이터를 전송하는 데 사용됩니다. Android에서 Coil 또는 Glide를 통해 이미지를 로드할 때 라이브러리는 청크별로 스트리밍 데이터를 읽고 점진적으로 이미지를 디코딩합니다. 이를 통해 OutOfMemoryError 없이 큰 이미지(10MB 이상)를 표시할 수 있습니다. OkHttp는 response.body?.byteStream()을 통한 스트리밍 읽기를 지원하며, 청크별로 데이터를 읽는 InputStream을 반환합니다.

gRPC 및 GraphQL의 Chunked transfer

gRPC는 HTTP/2를 사용하며, 스트리밍이 프로토콜 수준에 내장되어 있어 별도의 청크 메커니즘이 필요하지 않습니다. HTTP/1.1에서 실행되는 GraphQL 서버는 구독 결과를 스트리밍하기 위해 Chunked Transfer를 사용할 수 있습니다. Apollo Server와 Hasura는 GraphQL 구독에 대해 청크 응답을 전송하며, 이벤트가 발생할 때마다 전송합니다. 클라이언트는 폴링 없이 실시간 업데이트를 수신합니다.

장점과 제한 사항

Chunked Transfer는 웹 애플리케이션에 중요한 장점을 제공합니다. 즉시 데이터 전송 — 서버가 전송 전에 응답을 버퍼링하지 않아 첫 바이트까지의 지연 시간을 줄입니다. 스트리밍 처리 — 클라이언트가 완전한 다운로드를 기다리지 않고 데이터가 도착하는 대로 처리를 시작할 수 있습니다. 메모리 제한 없음 — 서버가 전체 응답을 메모리에 저장하지 않아 대용량 데이터에 중요합니다. 무한 스트림 전송 기능 — SSE, 라이브 비디오, 모니터링.

그러나 Chunked Transfer에는 제한 사항이 있습니다. 오버헤드는 각 청크의 크기 + CRLF에 대해 6-12바이트이며, 작은 청크가 많은 경우(예: 각 100바이트) 응답 크기가 10-15% 증가할 수 있습니다. 정확한 크기를 지정할 수 없음 — 클라이언트가 버퍼를 미리 할당하거나 진행 표시줄을 표시할 수 없습니다. 프록시 서버 문제 — 일부 오래된 프록시는 청크 전송을 지원하지 않으며 이러한 응답을 캐시할 수 없습니다. 다운로드 재개 지원 없음 — 부분적으로 수신된 청크 응답에 대해 Range 요청을 할 수 없습니다.

HTTP Archive, 2025에 따르면 모든 HTTP 응답의 약 35%가 Transfer-Encoding: chunked를 사용합니다. 그중 동적 페이지(60%), API 응답(25%), 미디어 스트림(15%)이 대부분을 차지합니다. 정적 파일은 크기를 미리 알 수 있으므로 거의 항상 Content-Length를 사용합니다. 추가 Transfer-Encoding 헤더 없이 프레임 수준에서 스트리밍이 구현되는 HTTP/2의 채택에 따라 청크 응답의 비율은 점차 감소하고 있습니다.

실용적인 권장 사항

모바일 개발에서 대용량 파일(이미지, 비디오) 다운로드 및 대규모 데이터 배열을 반환하는 API 요청에 Chunked Transfer를 사용하세요. OkHttp는 추가 구성 없이 청크 전송을 완전히 지원합니다. 서버 업로드에는 Chunked Transfer가 사용되지 않습니다. HTTP/1.1에는 업로드를 위한 Transfer-Encoding이 없기 때문입니다. iOS에서 URLSession은 특별한 구성 없이 청크 데이터 송수신을 모두 지원합니다. 스트리밍 JSON 파싱(Jackson Streaming API 또는 Moshi를 통해)을 통해 청크 스트림으로 도착하는 대규모 JSON 배열을 처리할 수 있습니다.

자주 묻는 질문

서버가 Chunked Transfer를 어떻게 활성화하나요?

서버는 응답 크기를 알 수 없을 때 자동으로 Chunked Transfer를 활성화합니다. Nginx는 Content-Length가 설정되지 않은 경우 Transfer-Encoding: chunked를 추가합니다. Spring Boot에서 StreamingResponseBody와 SseEmitter는 자동으로 청크 전송을 사용합니다. Node.js Express에서 Content-Length 없이 res.write()와 res.end()를 호출하면 응답이 청크가 됩니다.

Content-Length와 chunked를 동시에 사용할 수 있나요?

아니요, HTTP/1.1 사양은 Content-Length와 Transfer-Encoding: chunked의 동시 사용을 금지합니다. 서버가 두 헤더를 모두 전송하면 클라이언트는 Content-Length를 무시하고 응답을 청크로 처리해야 합니다. 이 규칙은 응답 본문을 수정할 수 있는 프록시 서버와의 호환성을 위해 RFC 7230에 설정되었습니다.

최적의 청크 크기는 무엇인가요?

최적의 청크 크기는 시나리오에 따라 다릅니다. 일반 웹 페이지의 경우 4-8KB. 비디오 스트리밍의 경우 16-64KB. SSE의 경우 지연 시간을 줄이기 위해 1-2KB의 최소 청크를 사용합니다. 청크 크기는 전송 계층에서 단편화를 최소화하기 위해 TCP 세그먼트 크기(이더넷의 경우 1460바이트)의 배수여야 합니다.

Chunked Transfer가 프록시를 통해 작동하나요?

최신 프록시 서버(Nginx, HAProxy, Envoy)는 Chunked Transfer를 지원합니다. 프록시는 버퍼링 없이 청크를 전달(스트리밍)하거나 전체 응답을 버퍼링한 후 Content-Length와 함께 다시 전송할 수 있습니다. 오래된 프록시는 청크 응답이 완료될 때까지 버퍼링하여 지연 시간을 증가시킬 수 있습니다. HTTP/2는 이 문제를 프로토콜 수준에서 해결합니다.

Chunked Transfer와 HTTP chunked encoding의 차이점은 무엇인가요?

같은 것입니다. Chunked Transfer는 HTTP/1.1 사양의 메커니즘 전체 이름입니다. HTTP chunked encoding도 동일하며, 때로 라이브러리 문서에서 사용됩니다. Transfer-Encoding: chunked는 이 모드를 활성화하는 헤더입니다. 세 용어 모두 데이터를 부분으로 전송하는 동일한 메커니즘을 설명합니다.

요약

  • Chunked Transfer — Content-Length를 미리 지정하지 않고 응답 본문을 청크로 전송하는 HTTP/1.1 메커니즘.
  • Transfer-Encoding: chunked — 청크 전송을 활성화하는 헤더; 각 청크는 16진수 크기, 데이터 및 CRLF 포함.
  • 크기가 0인 종료 청크가 전송 종료를 알리며, 그 후에 trailer 헤더가 올 수 있음.
  • 동적 콘텐츠 스트리밍 — 주요 사용 사례: 동적 페이지, SSE, 스트리밍 오디오/비디오, 긴 보고서.
  • Content-Length와 chunked는 상호 배타적 — 사양은 이러한 헤더의 동시 사용을 금지.
  • 장점 — 지연 시간 감소, 메모리 절약, 무한 스트림 전송 기능, 클라이언트 측 스트리밍 처리.
  • 제한 사항 — 청크 헤더 오버헤드, 진행 상황 표시 불가, 오래된 프록시 서버와의 문제.

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기