요즘IT
위시켓
새로 나온
인기요즘 작가들컬렉션
물어봐
새로 나온
인기
요즘 작가들
컬렉션
물어봐
개발
AI
IT서비스
기획
디자인
비즈니스
프로덕트
커리어
트렌드
스타트업
서비스 전체보기
위시켓요즘IT
고객 문의
02-6925-4867
10:00-18:00주말·공휴일 제외
yozm_help@wishket.com
요즘IT
요즘IT 소개작가 지원
기타 문의
콘텐츠 제안하기광고 상품 보기
요즘IT 슬랙봇크롬 확장 프로그램
이용약관
개인정보 처리방침
청소년보호정책
㈜위시켓
대표이사 : 박우범
서울특별시 강남구 테헤란로 211 3층 ㈜위시켓
사업자등록번호 : 209-81-57303
통신판매업신고 : 제2018-서울강남-02337 호
직업정보제공사업 신고번호 : J1200020180019
제호 : 요즘IT
발행인 : 박우범
편집인 : 노희선
청소년보호책임자 : 박우범
인터넷신문등록번호 : 서울,아54129
등록일 : 2022년 01월 23일
발행일 : 2021년 01월 10일
© 2013 Wishket Corp.
로그인
요즘IT 소개
콘텐츠 제안하기
광고 상품 보기
개발

HTTP 요청 사이즈는 정말 무제한일까?

zwoo
7분
2시간 전
229
에디터가 직접 고른 실무 인사이트 매주 목요일에 만나요.
newsletter_profile0명 뉴스레터 구독 중

사이드 프로젝트랑 실무의 가장 큰 차이 중 하나는 데이터의 규모다. 포트폴리오를 위해 일부러 대용량 데이터를 만들어보는 사람도 있지만, 내가 함께한 팀은 그렇게까지 치밀하지는 못했다. 우리가 다루던 데이터는 늘 ‘적당한 크기’였고, 페이지네이션도 그냥 습관처럼 넣었을 뿐이다. 그래서 큰 응답이 지연된다거나, 대용량 요청이 시스템에 어떤 영향을 주는지까지는 깊게 생각하지 못했다.

 

하지만 회사에 들어와 실제 프로젝트를 보니, HTTP 요청 횟수 제한과 사이즈 제한이 명확하게 설정되어 있었다. 그리고 우리 팀은 점점 커지는 데이터 전송량을 감당하기 위해 데이터 전송 방식을 근본적으로 개선해야 하는 과제를 안고 있었다. 그때 처음으로 이런 생각이 들었다. “HTTP 요청 사이즈는 진짜로 무제한일까?” 아마 나처럼 이 고민을 제대로 해본 적이 없는 사람도 있을 것이다. 이번 글에서는 HTTP의 POST 사이즈에 대한 이야기를 해보려 한다.

 

<출처: ChatGPT 생성>
 

프로토콜은 무제한을 허용하지만, 현실은 다르다

사실 HTTP 스펙 자체에는 POST, PATCH, PUT 요청의 크기에 대한 제한이 없다. 즉, 이론적으로는 무제한이다. 그러나 실제 서비스 환경에서는 다르다. 현실의 시스템은 안전을 위해 요청 크기(limit) 와 요청 횟수(rate limit)에 제한을 둔다.

 

일반적으로 클라이언트의 요청은 다양한 레이어를 거쳐서 메인 서버에 전달된다. 예를 들어, 프록시 서버 → 로드밸런서 → 웹 서버 → 메인 서버 순으로 전송된다. 그리고 메인 서버에 지나치게 부하가 가는 것을 막기 위해 앞 단계에서 요청량을 조절한다.

 

각 단계의 제한값은 수동으로 설정이 가능하지만, 별도의 설정을 하지 않는 경우 기본값은 다음과 같다.

 

  • CloudFront(CDN/프록시 계층): 헤더를 포함한 요청 크기는 20MB로 제한되고, 초당 요청 횟수는 250,000건으로 제한된다.
  • AWS API Gateway(로드밸런서/게이트웨이 계층): 요청 본문은 최대 10MB로 제한되며, 초당 요청 수는 리전 별로 초당 10,000개로 제한된다. 정상 상태의 요청 속도 및 버스트 제한을 초과할 경우 API Gateway에서 요청을 제한하기 시작한다. 이 시점에서 클라이언트는 429 Too Many Requests 오류 응답을 받게 되고, 클라이언트는 속도 제한을 걸어야 요청에 성공할 수 있다.
  • Nginx(웹 서버 계층): 기본 요청 본문 크기(client_max_body_size)는 기본적으로 1MB이다. 초과 시 413 Request Entity Too Large 오류를 반환한다.
  • Express (메인 서버 / 애플리케이션 계층): body-parser 미들웨어를 사용하는 경우 요청 본문 크기 기본 제한은 100kb, 이며, 이보다 큰 Body를 가진 요청은 “PayloadTooLargeError”로 거절된다.

 

<출처: Sitechecker>

 

요청 크기 제한이 필요한 이유

티켓팅이나 수강 신청을 해본 사람들은 트래픽이 과도하게 몰리는 것이 위험하다는 것을 직관적으로 이해할 수 있다. 그렇다면 요청 크기는 왜 제한을 걸어야 할까? 그건 요청 본문이 커질수록 서버는 더 많은 리소스를 소모하고, 결과적으로 서비스 안정성과 보안이 취약해지기 때문이다.

 

1) 메모리 사용량 증가

대부분의 서버나 프레임워크는 요청 본문을 메모리에 올려서 처리한다. 메모리는 데이터에 즉시 접근해 처리하기 위한 공간으로, 처리 속도가 빠른 만큼 디스크(SSD, HDD)보다 훨씬 비싸다. 그래서 무한정 크게 사용할 수가 없기 때문에, 효율적으로 관리해야 하는 공간이다.

 

만약 100MB짜리 JSON을 동시에 여러 클라이언트가 업로드한다면, 서버 메모리는 순식간에 포화되고 Out of Memory(OOM) 에러가 발생할 수도 있다. 앞 단계에서 크기 제한을 두지 않아서 큰 요청이 메인 서버까지 전달되면, body-parser나 express.json()은 요청 본문을 전부 메모리에 읽은 뒤 파싱하게 되고, 큰 Body로 인해 하나의 프로세스가 메모리를 많이, 오래 점유하게 된다. 이처럼 “메모리 폭탄”을 막기 위해 앞단에서 크기를 제한하는 구조가 필요하다.

 

2) 응답 지연 및 타임아웃

큰 요청은 전송과 파싱에 시간이 오래 걸린다. 그 과정에서 타임아웃이 발생하거나, 다른 요청들이 대기 상태에 빠질 수 있다. 요청 크기가 크면 전송 시간과 파싱 시간이 길어지고, 전체 처리 시간이 길어진다. 처리 시간이 길어질수록 응답이 지연되고, 타임아웃으로 인한 실패 확률이 높아진다. 클라이언트의 요청을 처리하는 데 관여하는 서비스들은 클라이언트가 지나치게 오래 기다리는 상황을 방지하고자 타임아웃이 걸려있는 것이 일반적이다.

 

만약 타임아웃이 걸리지 않아서 취소되지 않더라도, 응답이 지나치게 지연되면 다음과 같은 문제들이 발생할 수 있다.

 

  • Nginx: 각 워커 프로세스가 수천 개의 연결을 비동기적으로 관리하는데, CPU 자원은 한정적이기 때문에 파일 전송이 오래 걸리면 그 워커는 계속 해당 연결에 붙잡혀 있게 된다. 그러면 새로운 요청들은 즉시 처리되지 못하고 큐에 쌓인다. 결과적으로 서비스 전반이 느려진다.
  • Express: Node.js는 단일 스레드 이벤트 루프 기반이기 때문에, 하나의 큰 요청이 들어오면 그 처리가 끝날 때까지 이벤트 루프가 막힐 수 있다.

 

즉, 큰 요청 하나 때문에 전체 서비스의 응답성이 저하될 수 있다.

 

3) 전송 실패 리스크 증가

대용량 요청은 네트워크 장애에 훨씬 더 취약하다. POST나 PUT 요청의 경우, 서버는 요청 본문(Body)을 끝까지 완전히 받아야만 유효한 요청으로 인식한다. 만약 전송 중 일부가 끊기면 HTTP 프로토콜 레벨에서 불완전한 요청(incomplete body)으로 간주되어 폐기된다. 따라서 전송 도중 네트워크가 끊기면 이미 업로드된 데이터는 버리고, 처음부터 다시 전송해야 한다.

 

이때 클라이언트가 자동 재시도를 여러 번 수행하면, 짧은 시간 안에 과도한 재전송 요청(rate spike)이 발생하여 시스템 부하를 가중시킬 수 있다. 참고로 이런 상황을 방지하기 위해 청크 업로드(Chunked Upload) 방식을 사용하기도 한다. 파일을 여러 조각으로 나누어 전송하고, 실패한 조각만 재전송함으로써 네트워크 오류에 대한 복원력을 확보하는 방식이다.

 

4) 보안 리스크

대용량 요청은 서비스 거부 공격(DoS, Denial of Service)의 수단이 될 수 있다. 공격자가 의도적으로 거대한 요청을 보내면, 서버는 파싱에 모든 리소스를 소비하게 된다.

 

이런 공격은 보통 두 가지 형태로 나타난다.

 

  • Large Payload DoS: 매우 큰 POST 요청을 반복적으로 보내 서버의 메모리와 CPU를 고갈시키는 방식.
  • Slow HTTP Attack: Content-Length를 크게 설정해 놓고, 데이터를 아주 천천히 전송해 커넥션을 장시간 점유하는 방식.

 

두 가지 경우 모두 요청 크기 제한과 타임아웃 설정이 제대로 되어 있지 않다면, 서버가 정상적인 요청을 처리하지 못하고 결국 마비될 수 있다.

 

 

데이터 전송 방식 개선을 위한 고민

우리 팀이 관리하는 서비스 중에는 고객들에게 제공하는 컨텐츠를 업로드하는 백오피스 툴이 있다. 운영자 편의를 위해 CSV를 이용한 대용량 업로드 기능이 구현되어 있고, 각 업로드 요청은 리비전 번호를 부여받는다. 즉, 최신 데이터는 가장 최근 리비전 번호를 가진 데이터를 의미한다.

 

이 툴은 다음과 같은 방식으로 사용되고 있었다.

 

  1. 최신 리비전의 데이터를 CSV로 내려받는다. (이 안에는 모든 데이터가 포함되어 있다.)
  2. 하단에 새로운 행을 추가한다.
  3. 수정된 파일을 다시 업로드한다.

 

이렇듯 데이터 관리 방식 자체가 누적형 구조이기 때문에, 시간이 지날수록 요청 사이즈가 커지는 게 당연했다. PayloadTooLargeError 에러를 해결하는 과정에서, 우리는 일단은 요청을 파싱하는 body-parser의 limit을 약간 올려두고, 장기적으로는 대용량 데이터 전송 구조를 개선하기로 했다.

 

그 과정에서 검토했던 아이디어는 다음과 같다.

 

1) 전송 전 중복 제거 후 diff만 업로드

기존 데이터와 새 데이터를 비교하여 변경된 부분(diff) 만 서버로 전송한다. 사용자는 전체 CSV 대신 일부 데이터만 업로드할 수 있으며, 데이터가 기존에 존재하면 덮어쓰고, 없으면 새로 추가하여 최종적으로 새로운 리비전 데이터로 취급한다.

 

2) 요청 크기 명시적 제한

서버에서 명확한 사이즈 제한을 두고, 클라이언트가 제한을 초과하면 경고를 반환하도록 한다. 앞의 아이디어와 마찬가지로, 전체 데이터가 아닌 부분 데이터만 업로드하는 방식이다. 

 

3) 파일 분할 전송(Chunked Upload)

큰 파일을 한 번에 업로드하지 않고, 여러 회차로 나누어 전송한다. 이 방식은 요청 크기를 줄일 뿐 아니라, 중간에 실패하더라도 해당 청크만 재전송할 수 있는 장점이 있다. 다만 요청 횟수가 많아져 네트워크 오버헤드가 늘고, 정합성 관리가 복잡해지는 단점이 있다.

 

4) 데이터 분리(Active / Inactive)

데이터 객체에 ‘기간(period)’ 속성을 추가해 current(활성) 데이터와 past(비활성) 데이터를 분리 저장하는 방식이다. 하지만 이 방식은 근본적인 해결이 되기는 어려웠다. 만약 비활성 데이터 수정 요청이 발생하면 여전히 용량이 커진다는 문제가 있고, 활성 데이터 또한 이론상 크기가 얼마든지 커질 수 있기 때문이다.

 

이 아이디어를 조금 보완해서, 데이터를 분리해서 저장할 것이 아니라, 필터를 통해 분리해서 요청하는 방식으로 변경했다. 또한 페이지네이션(pagination) 도 함께 적용해 응답 크기를 줄이고 로딩 속도 개선 효과도 기대해 보기로 했다.

 

 

마치며: 데이터의 확장을 미리 예상하고 설계할 것

이 툴을 개발할 때, 처음부터 큰 규모의 데이터를 고려해서 설계했다면 좋았을 것이다. 사용자에게 제공할 컨텐츠가 많아질 거라는 것은 충분히 예상 가능한 일이기 때문이다. 이처럼 데이터의 확장을 미리 고려하여 설계하는 것은 매우 중요하다.

 

만약 지금 개발자가 되려고 공부 중이고, CRUD 기능을 구현하고 있다면 대용량 업로드 기능도 한 번쯤 생각해 보면 좋다. 나의 경우, 공부할 때는 단일 데이터에 대한 생성 및 수정만 생각했고, 실무를 시작한 이후에 생각의 범위를 키우느라 시간이 꽤 걸렸다. 생각의 범위는 다루는 데이터의 크기만큼 커진다. 어릴 땐 ‘10’이 가장 큰 수였던 것처럼, 개발도 경험과 함께 스케일이 확장된다.

 

데이터는 얼마든지 커질 수 있다. 사용자에게 편리한 대용량 전송 환경을 제공하는 것도 중요하지만, 동시에 서비스 안정성과 효율성을 함께 고려해야 한다. 기술이 발전하면서 우리가 다루는 데이터 단위는 점점 커지고 있지만, 적어도 아직까지는 무한하지 않다. 그러므로 항상 효율적으로 다루는 법을 고민해야 한다.

 

나 또한 이번 개선 과정을 거치며, 요청 크기 문제는 단순히 업로드 로직의 문제가 아니라, 데이터 구조 설계와 시스템 이해의 문제라는 걸 깨달았다. 요청을 가볍게 만드는 가장 확실한 방법은 전송을 최적화하는 것보다, 데이터 자체를 효율적으로 다루는 것이다. 이는 반드시 하나의 기술로만 해결해야 하는 것도 아니다. 결국 좋은 시스템은 데이터를 잘 다루는 시스템이고, 좋은 개발자는 그걸 예상할 수 있어야 한다. 그렇다면 지금의 나는 “데이터를 얼마나 깊이 이해하고 있을까?” 그 질문에서 시작해 보면 좋을 것이다.

 

©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.