제조 PLM 시스템을 Next.js로 마이그레이션하면서 겪은 문제 해결 과정을 기록하는 시리즈입니다. 첫 편은 대용량 파일 업로드 장애 추적기입니다.
배경
산업설비 제조사의 PLM 시스템에는 설계자가 CAD 도면을 업로드하는 기능이 있습니다. 3D 모델 파일은 수십~수백 MB가 기본입니다. 어느 날 120MB짜리 CAD 파일 업로드가 500 에러로 실패한다는 것을 확인했습니다.
1차 용의자: 리버스 프록시
서버 앞단에 Traefik 리버스 프록시가 있었기 때문에 자연스럽게 프록시의 바디 크기 제한을 의심했습니다. 흔한 시나리오니까요(nginx의 client_max_body_size 같은). 그런데 Traefik 설정을 아무리 봐도 바디 제한을 걸어둔 곳이 없었습니다.
여기서 추측을 이어가는 대신, 측정으로 전환했습니다.
이분 탐색으로 임계값 찾기
“120MB는 실패한다”는 사실 하나로는 원인 계층을 특정할 수 없습니다. 그래서 파일 크기를 바꿔가며 이분 탐색을 했습니다.
- 5MB → 성공
- 50MB → 실패
- 10MB → 실패
- 8MB → 성공
임계값이 정확히 10MB. 이 숫자가 결정적 단서였습니다. 프록시 기본값이라기엔 너무 “프레임워크스러운” 라운드 넘버였고, 실제로 서버 로그를 뒤지니 이 한 줄이 나왔습니다.
Error: Request body exceeded 10MB
Traefik이 아니라 Next.js가 미들웨어를 경유하는 요청에 적용하는 바디 한도가 원인이었습니다. 이 시스템은 전역 인증 미들웨어가 모든 요청을 가로채는 구조라, 모든 업로드가 이 한도에 걸리고 있었던 겁니다.
함정: 수정이 적용된 “것처럼 보이는” 상태
원인을 찾았으니 설정만 바꾸면 끝… 이 아니었습니다. 여기서 두 번 미끄러졌습니다.
첫 번째: 1차로 넣은 설정 키가 잘못된 위치였습니다. 문서만 보고 최상위에 넣었는데, 실제 스키마상 experimental 하위에 있어야 하는 키였습니다. 설정이 조용히 무시됐습니다.
두 번째가 진짜 함정: 서버를 재기동했는데 이전 프로세스가 포트를 물고 죽지 않은 상태(EADDRINUSE)였고, 새 프로세스는 뜨지 못했는데 구 프로세스가 계속 응답하고 있었습니다. 설정을 바꿨는데도 동작이 그대로라서 “이 키도 아닌가?” 하고 엉뚱한 방향으로 갈 뻔했습니다. 재기동 검증의 대전제 — 지금 응답하는 프로세스가 정말 새 프로세스인가 — 를 확인하지 않은 대가였습니다.
포트를 정리하고 올바른 설정 키로 재기동한 뒤, 120MB와 300MB 업로드까지 검증을 마쳤습니다.
교훈
- “몇 MB부터 실패하는가”를 먼저 측정하라. 임계값은 원인 계층(프록시/프레임워크/앱)을 좁혀주는 가장 싼 단서다. 10MB 같은 라운드 넘버는 대부분 어딘가의 기본값이다.
- 설정 변경 검증은 “적용됐는가”부터. 조용히 무시되는 설정 키는 생각보다 흔하다.
- 재기동 후에는 응답하는 프로세스의 신원을 확인하라. EADDRINUSE + 좀비 프로세스 조합은 “수정이 효과 없다”는 착시를 만든다.
덧붙여
이 프로젝트는 AI 코딩 에이전트(Claude Code)와 페어로 진행하고 있습니다. 이 장애 추적에서도 이분 탐색 실험과 로그 수집은 에이전트가 수행하고, 가설 채택과 “응답 프로세스가 구 프로세스였다”는 함정의 판별은 사람이 했습니다. AI가 실험을 빨리 돌려주는 만큼, 실험 설계와 결과 해석의 엄밀함이 더 중요해진다는 걸 체감한 사례였습니다.
프로젝트 전체 개요와 다른 문제 해결기는 GitHub 저장소에 정리되어 있습니다.