multipart/form-data의 오버헤드를 정확하게 계산하기
파일 업로드 크기 제한 테스트에서 「파일은 9MB인데 413 오류가 나온다」는 경험이 없으신가요? 그 원인 중 하나가 <code>multipart/form-data</code>의 오버헤드입니다. HTML 폼에서 파일을 업로드할 때, HTTP 요청의 본체에는 파일 자체뿐만 아니라 추가 메타데이터가 포함됩니다. 이 글에서는 그 오버헤드의 정확한 계산 방법과 테스트 시 주의점을 설명합니다.
multipart/form-data의 구조
RFC 2046에서 정의한 <code>multipart/form-data</code>는 각 부분을 boundary 문자열로 구분한 구조를 갖습니다. 실제 HTTP 요청 본문은 다음과 같습니다.
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123
Content-Length: 10000xyz
------WebKitFormBoundaryABC123
Content-Disposition: form-data; name="file"; filename="test.png"
Content-Type: image/png
[ファイルのバイナリデータ]
------WebKitFormBoundaryABC123--
오버헤드 내역
일반적인 파일 업로드 요청의 오버헤드는 다음 요소로 구성됩니다.
| 요소 | 예시 | 바이트 수 (목안) |
|---|---|---|
| 시작 경계 | ------WebKitFormBoundaryABC123\r\n | 약 40~80 B |
| Content-Disposition 헤더 | Content-Disposition: form-data; name="file"; filename="test.png"\r\n | 약 60~120 B |
| Content-Type 헤더 | Content-Type: image/png\r\n | 약 25~50 B |
| 공백 줄 (헤더 끝) | \r\n | 2 B |
| 파트 말미의 줄바꿈 | \r\n | 2 B |
| 종료 boundary | ------WebKitFormBoundaryABC123--\r\n | 약 42~82 B |
| <strong>총 오버헤드</strong> | <strong>약 200~350 B</strong> |
파일 1개만 있는 간단한 폼이라면 오버헤드는 <strong>200~400바이트 정도</strong>입니다. 추가 폼 필드(텍스트 입력 등)가 있는 경우 그만큼 증가하지만, 일반적으로 수백 바이트~수 KB 정도에 수렴합니다.
413 오류가 발생하는 이유
Nginx의 <code>client_max_body_size</code>는 <strong>요청 본문 전체</strong>의 크기를 제한합니다. 즉, 설정값에 오버헤드를 포함해야 합니다.
# 10MiB のファイルをアップロードさせたい場合
# オーバーヘッド(約1KB)を考慮して少し大きめに設定
client_max_body_size 11m; # MiB単位: 11 MiB = 11,534,336 バイト
PHP의 경우 <code>upload_max_filesize</code> (개별 파일)와 <code>post_max_size</code> (전체 POST 본문) 2가지를 설정해야 합니다.
; php.ini
upload_max_filesize = 10M ; ファイル単体の上限: 10 MiB
post_max_size = 11M ; POSTボディ全体の上限: 11 MiB(オーバーヘッド分を加算)
정확한 오버헤드 측정 방법
실제 오버헤드를 정확히 알고 싶은 경우, <code>curl</code>로 요청을 보내 요청 크기를 측정할 수 있습니다.
# ファイルのバイト数を確認
wc -c test-10mb.png
# → 10485760 test-10mb.png
# curl でアップロードしてリクエストサイズを確認
curl -X POST https://example.com/upload \
-F "file=@test-10mb.png" \
-w "リクエストボディサイズ: %{size_upload} バイト\n" \
-o /dev/null -s
# → リクエストボディサイズ: 10486062 バイト(差: 302バイト)
Base64 인코딩과의 차이
<code>multipart/form-data</code>의 경우, 파일의 바이너리 데이터가 그대로 전송됩니다(Base64 인코딩 없음). Base64 인코딩이 필요한 것은 <code>application/x-www-form-urlencoded</code>로 바이너리를 보내는 경우나 JSON 본문으로 파일을 보내는 경우(<code>data:image/png;base64,...</code>)입니다.
| 전송 방식 | 오버헤드 | 용도 |
|---|---|---|
| multipart/form-data(HTML 폼) | 수백 B ~ 수 KB | 일반 파일 업로드 |
| JSON + Base64 | 약 33% 증가 | API를 통한 파일 전송 |
| application/octet-stream(PUT) | 거의 0에 가까움 | S3 서명된 URL 등 |
테스트 시 주의점
- 서버의 상한 체크가 "파일 단체"인지 "리퀘스트 바디 전체"인지를 파악
- Nginx가 PHP 앞단에서 요청을 제한하므로, Nginx의 <code>client_max_body_size</code>도 확인하세요
- 여러 파일의 동시 업로드는 오버헤드를 증가시킵니다
- 오버헤드에는 추가 양식 필드(이름, 댓글 등)도 포함됩니다
DevLab의 <a href="/ja/files/threshold/">임계값 테스트 파일</a>을 사용하면 경계 근처의 파일 크기로 실제 업로드 동작을 확인할 수 있습니다. Nginx/Apache/PHP 설정과 함께 검증하세요.
요약
- <code>multipart/form-data</code>의 오버헤드는 <strong>약 200~400바이트 정도</strong>(파일 1개의 경우)
- Nginx의 <code>client_max_body_size</code>는 요청 본문 전체를 제한하므로 파일 상한선보다 조금 크게 설정합니다
- PHP는 <code>upload_max_filesize</code> (개별 파일)와 <code>post_max_size</code> (POST 본문 전체) 두 가지를 설정해야 합니다.
- JSON + Base64로 전송할 경우 파일 크기가 약 33% 증가합니다
이 기사에서 사용할 수 있는 테스트 파일
- → <a href="/ja/files/threshold/" class="text-primary-600 dark:text-primary-400 hover:underline">임계값 테스트 파일 목록 (9.9MB / 10MB / 10.1MB)</a>
- → <a href="/ja/files/images/png/" class="text-primary-600 dark:text-primary-400 hover:underline">PNG 이미지 테스트 파일 목록</a>
관련 기사
- → <a href="/ja/blog/how-to-test-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">파일 업로드 제한을 올바르게 테스트하는 방법</a>
- → <a href="/ja/blog/mb-vs-mib-file-size/" class="text-primary-600 dark:text-primary-400 hover:underline">MB와 MiB는 다르다! 파일 크기 단위의 함정</a>