포팅 중인 서비스의 영상 업로드가 작은 파일로는 잘 됐는데 큰 파일을 올리자 여러 곳에서 걸렸다. 걸리는 자리가 한 곳이 아니었다.
Table of contents
Open Table of contents
배경 — 계층마다 있던 제한
nginx 의 client_max_body_size 와 PHP 의 upload_max_filesize 와 post_max_size 가 각각 따로 있었다. nginx 공식 문서대로 본문이 그 값을 넘으면 413 이 돌아간다. PHP 매뉴얼대로 post_max_size 는 upload_max_filesize 보다 커야 한다.
memory_limit 은 파일을 통째로 읽으면 그만큼 쓴다. 연결은 업로드 도중에 max_execution_time 에 걸렸다. 오브젝트 스토리지에도 단일 PUT 으로 올릴 수 있는 크기 제한이 있었다. 다섯 곳을 다 손대야 통과하는 구조였다.
막힌 지점 — 하나씩 순서대로 나타난다
이 문제가 답답한 것은 제한이 한 번에 다 보이지 않는다는 점이다. 앞의 것을 고쳐야 다음 것이 드러난다.
그래서 브라우저에서 저장소까지 경로 전체를 그려 놓고 각 계층의 제한을 한 번에 확인했다. 하나씩 걸릴 때마다 찾으면 그 횟수만큼 배포와 재현이 반복된다.
조치 — 통째로 읽지 않는 방식
메모리 문제는 설정으로 풀리지 않아서 코드를 바꿔야 했다. file_get_contents 로 통째로 읽어 넘기던 것을 fopen 과 stream_copy_to_stream 으로 흘려보내게 했다.
memory_limit 을 늘리는 것은 임시방편이고 파일이 더 커지면 다시 걸린다. 읽는 방식을 바꾸는 것이 근본 해결이었다.
해 본 것 — 나눠 올리기와 기준
저장소의 단일 요청 제한은 스트림으로도 안 풀렸다. CreateMultipartUpload 와 UploadPart 로 여러 조각으로 나눠 올리는 방식을 썼다. 중간에 실패해도 그 조각만 다시 올리면 되므로 실패에 강해진다.
작은 파일은 단순 PUT 으로 두고 100MB 를 넘는 파일만 나눠 올리는 쪽을 골랐다. 이유는 조각 관리와 실패 처리가 붙어 복잡해지는 경로를 큰 파일에만 태우고 싶어서다. 대신 업로드 경로가 둘이 되어 시험할 조합이 늘어나는 것을 감수했다.
주의 — 미완료 조각의 정리
한동안 돌리고 저장소를 보니 미완료 업로드가 쌓여 있었다. 조각은 올라갔는데 CompleteMultipartUpload 가 안 된 것들이고 그대로 공간과 요금을 쓴다.
일정 기간이 지난 미완료 업로드를 버킷 수명 주기 규칙으로 자동으로 지우게 했다. 나눠 올리기를 도입하면 이 정리 규칙이 반드시 따라와야 하고 안 넣으면 조용히 쌓인다.
진행률은 조각 단위로 계산해 보여 줬다. 조각마다 걸리는 시간이 달라 정확하지는 않지만 멈춘 것처럼 보이지 않는 것이 목적이었다.
정리
- 파일이 커지면 여러 계층의 제한에 순서대로 걸린다
- 앞의 것을 고쳐야 다음 것이 드러난다
- 경로 전체를 그려 놓고 각 계층의 제한을 한 번에 확인한다
- 메모리는 설정이 아니라 스트림으로 푼다
- 한도를 늘리는 것은 파일이 더 커지면 다시 걸린다
- 단일 요청 제한은 나눠 올리기로 풀고 기준을 정해 둔다
- 나눠 올리기는 미완료 조각 정리 규칙이 반드시 필요하다
- 진행률은 정확하지 않아도 움직이면 목적을 달성한다