갤러리 업로드 파이프라인
갤러리 업로드 파이프라인을 고쳐 월 S3 증가량을 30GB에서 3GB로, 사진 1장 업로드 시간을 3초에서 1초로 줄인 과정
갤러리 업로드 파이프라인
문제 상황
학원용 앱의 수업 사진 갤러리가 느리고 저장 비용도 큰 상태
- 선생님이 사진 여러 장을 올리면 저장 버튼을 누르고 한참을 기다려야 했다.
- S3 저장량은 매달 30GB씩 늘고 있었다.
- 느린 것과 비싼 것은 별개 문제처럼 보였지만, 파이프라인을 추적해보니 뿌리가 같았다.
시간 문제와 용량 문제가 한 파이프라인 안에 얽혀 있었다.
기존 구조
업로드 흐름과 병목 지점 정리
파일 선택 → 원본 그대로 전송 → 1장씩 순차 업로드 → S3 임시 경로(temp/) 보관 → 저장 버튼 → 임시에서 최종 경로로 1장씩 순차 복사 → 게시글 저장
- 원본 용량 그대로 : 폰 사진은 한 장에 평균 7MB인데, 그대로 전송되고 그대로 저장됐다.
- 순차 처리 : 업로드도 복사도 앞 장이 끝나야 다음 장이 시작됐다. 10장이면 대기 시간도 10배다.
- 2중 저장 : 임시 경로에 한 번, 최종 경로에 한 번, 같은 파일이 S3에 두 번 저장됐다.
- 중복 저장 : 같은 사진을 여러 게시글에 올리면 그때마다 새로 저장됐다. 학원 특성상 같은 단체 사진이 반별 게시판에 반복 게시되는 일이 흔했다.
시간 문제(2)와 용량 문제(1, 3, 4)를 한 파이프라인에서 함께 풀어야 했다.
1차 개선 : 순차 처리 병렬화
가장 눈에 띄는 병목인 순차 처리부터 병렬화
- 서버의 임시에서 최종으로의 복사를 스토리지의 비동기 API로 전환해 동시 5건씩 병렬 복사로 바꿨다.
- 전부를 하나의 성공/실패로 묶지 않고 건별로 판정해, 실패한 건만 제외하고 성공분은 반영되도록 했다.
- 앱 쪽도 동시 3장씩 병렬 업로드하는 워커 큐 방식으로 바꿨다.
- 동시성 수치는 처음부터 정답이 있었던 것이 아니라, 여러 장을 동시에 처리할 때 기기에 걸리는 부하를 보며 낮춰가면서 잡았다.
- 10장 저장 시간이 눈에 띄게 줄었다. 그런데 코드를 다듬다 보니 이 복사를 애초에 왜 하는지가 계속 걸렸다.
빨라지긴 했지만, 복사 단계가 필요한지는 아직 확인하지 않은 상태였다.
2차 개선 : 복사 단계 삭제
임시 경로를 거치지 않고 처음부터 최종 경로로 업로드
- 임시 경로의 목적은 글 작성을 취소하면 임시 파일만 버리면 된다는 것이었다.
- 그 목적은 임시 경로 없이도 달성된다. 업로드 시점에 파일 경로를 확정하고, 게시글 저장 시점에는 그 경로를 DB에 기록하기만 하면 된다. 글을 취소하면 참조되지 않는 파일이 남을 뿐이고, 그것은 별도로 정리하면 된다.
- 처음부터 최종 경로로 업로드하도록 바꾸자 임시에서 최종으로의 복사 시간이 통째로 사라졌고, 임시와 최종의 2중 저장도 함께 사라졌다.
- 1차에서 만든 병렬 복사 코드는 결국 걷어냈다. 필요한 단계인지 확인하기 전에 속도부터 높인 셈이었다.
2차 개선은 코드를 추가하는 것이 아니라 단계를 삭제하는 것이었다.
용량 축 : 압축과 중복 제거
시간 축과 병행해 클라이언트 압축과 지문 기반 중복 제거 적용
- WebP 압축 : 클라이언트에서 canvas에 그려 WebP로 변환하되, 최대 폭 1080px에서 시작해 결과가 2MB를 넘으면 폭을 한 단계씩 줄여가며 재시도한다.
- 클라이언트에서 먼저 압축하니 저장 용량과 전송량이 함께 줄었다. 7MB 원본을 서버에 올린 뒤 1MB로 줄이는 대신, 처음부터 1MB만 전송한다.
- 지문 기반 중복 제거 : 파일을 선택하면 압축된 파일의 SHA-256 해시를 계산하고, 업로드 전에 서버에서 같은 지문을 조회한다. 이미 저장된 파일이면 기존 경로를 재사용한다.
- 중복 파일은 네트워크로 전송하지도, 다시 저장하지도 않는다.
1
폭 1080px, 품질 0.8로 변환 → 2MB 이하면 채택 → 초과면 폭을 한 단계 줄여 재시도 (하한선까지)
최종 흐름은 파일 선택 시 압축과 지문 계산 → 저장 버튼 시 새 파일만 동시 3장씩 최종 경로로 업로드 → 서버는 경로만 기록 순이다.
클라이언트가 압축과 지문 계산을 맡고, 서버는 경로를 기록할 뿐이다.
결과
운영 환경 지표로 확인한 수치
| 지표 | 개선 전 | 개선 후 | 감소율 |
|---|---|---|---|
| 사진 1장 평균 저장 용량 | 7MB | 1MB | 약 86% |
| 월 신규 S3 저장량 | 30GB | 3GB | 약 90% |
| 사진 1장 평균 업로드 시간 | 3초 | 1초 | 약 67% |
- 첫 줄과 둘째 줄의 배수가 다르다. 압축만으로는 7분의 1인데 월 저장량은 10분의 1이 됐다. 그 차이가 중복 제거와 2중 저장 폐지의 몫이다.
- 개선을 여러 개 겹쳤을 때 총합에서 각각의 기여를 분해할 수 있었던 것은, 수치를 지표별로 나눠 측정해둔 덕이다.
- 업로드 시간은 업로드 함수에 건당 소요시간 로깅을 심어 측정했고, 저장량은 S3 지표의 전후 비교다.
압축이 7분의 1을 줄였고, 중복 제거와 2중 저장 폐지가 나머지를 줄였다.
회고와 남은 과제
작업 순서의 아쉬움과 남은 과제 정리
- 병렬화를 먼저 하고 복사 단계의 필요성을 나중에 확인했다. 순서가 반대였다면 1차 작업의 상당 부분은 필요 없었다. 이후에는 성능을 손대기 전에 없앨 수 있는 단계인지 먼저 확인하기로 했다.
- 압축과 해시 계산은 사용자 기기에서 돌아간다. 파일 선택 직후 계산을 시작해 사용자가 제목과 내용을 쓰는 동안 백그라운드에서 끝내도록 했다. 저사양 기기의 처리 시간은 계속 지켜봐야 한다.
- 구 경로 통합 정리 : 구 업로드 경로가 다른 화면에서 아직 쓰이고 있어, 신 구조로의 통합이 남아 있다.
- 미참조 파일 정리 : 취소된 글이 남긴 참조되지 않는 파일은 별도 배치로 정리해야 한다.
없앨 수 있는 단계를 먼저 확인했다면 1차 작업의 상당 부분은 필요 없었다.
This post is licensed under CC BY 4.0 by the author.
