본문으로 건너뛰기

브라우저 이미지 최적화 파이프라인 구축기

·11 min read

엑셀과 이미지 ZIP을 업로드하면 PPT 슬라이드를 자동 생성하는 서비스를 다뤘다. 엑셀의 매체명, 위치, 위치상세를 기준으로 슬라이드를 만들고, ZIP에는 현장 사진, 광고 소재, 매체 설치 사진 같은 이미지가 들어간다. 이 이미지는 대부분 모바일이나 카메라 촬영본이라 해상도와 용량이 컸다.

원본 이미지를 그대로 서버에 보내던 구조가 부담이었다

초기 구조는 ZIP 내부 이미지를 그대로 서버에 전달하는 방식이었다. 이 방식에서 몇 가지가 걸렸다.

  • 원본 이미지가 과도하게 크면 업로드 시간이 길어졌다
  • 서버가 ZIP을 처리하고 S3에 업로드하는 과정의 I/O 부담이 커졌다
  • PPT 슬라이드에 실제로 필요한 해상도보다 훨씬 큰 이미지를 쓰는 비효율이 있었다
  • PPT 생성 시 이미지 처리 시간이 늘어났다
  • 사용자가 최적화 효과를 확인할 수 없어 용량이 줄었는지 판단하기 어려웠다

PPT 슬라이드는 고정된 16:9 캔버스와 제한된 이미지 배치 영역을 쓴다. 원본의 전체 해상도를 유지할 이유가 없었다. 그래서 서버 업로드 전에 브라우저에서 이미지 크기를 슬라이드 사용 목적에 맞게 줄이기로 했다.

ZIP 내부 이미지를 브라우저에서 먼저 처리했다

사용자가 ZIP을 업로드하면 프론트엔드에서 JSZip(브라우저에서 ZIP을 읽고 쓰는 라이브러리)으로 ZIP 내부 파일을 읽고 이미지 파일만 필터링했다. 대상 확장자는 .png, .jpg, .jpeg, .webp로 제한했다. 폴더 구조는 유지하면서 각 이미지 파일을 Blob으로 추출한 뒤(필요 시 File로 래핑) 리사이징 파이프라인에 전달했다.

browser-image-compression으로 리사이징과 압축을 했다

이미지 최적화에는 browser-image-compression(브라우저에서 이미지를 압축·리사이징하는 라이브러리)을 썼다. 적용한 주요 옵션은 다음과 같다.

const IMAGE_COMPRESSION_OPTIONS = {
  maxSizeMB: 0.8,
  maxWidthOrHeight: 1600,
  useWebWorker: true,
  initialQuality: 0.82,
}
  • maxWidthOrHeight: 1600 — width 또는 height 중 큰 쪽(장축)이 1600px 이하가 되도록 비율을 축소한다. PPT 슬라이드 이미지 영역에 비해 과도하게 큰 원본을 제한한다.
  • maxSizeMB: 0.8 — 개별 이미지 파일 크기 상한. 다수 이미지가 든 ZIP을 업로드할 때 전체 payload가 커지는 걸 막는다.
  • initialQuality: 0.82 — 압축을 시작하는 초기 품질값(0~1). maxSizeMB 상한을 맞추기 위해 이 값에서 품질을 더 낮추기도 한다.
  • useWebWorker: true — 압축 작업이 메인 스레드를 과도하게 점유하지 않도록 워커 스레드에서 처리한다. 이미지가 여러 장이어도 UI 응답성 저하를 줄인다.

리사이징한 이미지로 ZIP을 다시 묶었다

리사이징이 끝난 이미지는 기존 ZIP 폴더 경로를 유지한 채 새 ZIP으로 다시 묶었다. 서버의 기존 폴더 매핑 로직과 호환되도록 하기 위한 선택이었다. 예를 들어 기존 ZIP 구조가 대구버스외부/북구3/3248/image-01.jpg였다면 최적화 후에도 같은 경로를 유지했다. 서버 입장에서는 동일한 폴더 구조의 ZIP을 받지만, 내부 이미지는 이미 슬라이드 생성에 적합한 크기로 줄어든 상태가 된다.

서버 API를 건드리지 않기로 했다

백엔드에는 이미 엑셀 기반 폴더 생성, ZIP 업로드, S3 업로드, PPT 생성 API가 구현돼 있었다. 그래서 서버 로직을 수정하지 않는 방향으로 설계했다. 프론트엔드는 다음 순서로 동작했다.

이 방식은 백엔드 API 계약을 바꾸지 않고, 기존 서버 폴더 매핑 로직을 그대로 재사용한다. 서버에 도착하는 시점부터 이미 최적화된 이미지를 쓰게 되고, S3 저장 전 단계에서 용량을 줄일 수 있었다. 프론트엔드 단독으로 개선 효과를 적용할 수 있다는 점이 컸다.

절감 내역을 사용자가 보게 만들었다

각 이미지마다 원본 파일 크기, 최적화 후 파일 크기, width, height, 절감량, 절감률을 기록했다. 절감률은 ((originalBytes - optimizedBytes) / originalBytes) * 100로 계산했다. ZIP 전체에 대해서도 전체 원본 용량, 전체 최적화 용량, 전체 절감 용량, 전체 절감률을 계산했다. 이 값을 "폴더 리사이징 내역" 팝오버 UI(Radix Popover)로 제공해, 사용자가 전체 절감량뿐 아니라 파일별로 어떤 이미지가 얼마나 줄었는지 확인할 수 있게 했다.

리사이징은 ZIP 내부 파일 수와 원본 크기에 따라 시간이 걸린다. 그동안 업로드 버튼을 비활성화하고 "처리 중" 상태를 표시했으며, 슬라이드 리스트와 미리보기 영역에는 스켈레톤을 띄웠다. 사용자가 멈춘 것처럼 느끼지 않고 처리 중임을 인지하게 하기 위해서였다.

용량보다 품질 기준을 먼저 잡았다

이미지 최적화에서 중요한 건 용량을 줄이는 것 자체가 아니라 PPT 보고서에서 확인 가능한 품질을 유지하는 것이었다. 이 프로젝트의 이미지는 원본을 정밀 검수하기 위한 게 아니라, 광고 게재 보고서에서 게재 지면을 확인하는 용도에 가까웠다. 원본 해상도를 그대로 유지하는 것보다, 확인에 필요한 품질을 유지하면서 업로드·저장 비용을 줄이는 쪽이 맞았다.

그래서 슬라이드 표시 영역보다 과도하게 큰 이미지만 축소했고, 장축 1600px로 제한했으며, 품질 계수는 0.82로 뒀다. Web Worker로 UI 응답성을 유지했고, 리사이징 후에는 이미지의 width/height를 다시 측정해 슬라이드 레이아웃 계산에 활용했다.

절감 효과는 추정이 아니라 실제 업로드 기준으로 보여줬다

실제 절감률은 원본의 해상도, 포맷, 압축 상태에 따라 달라진다. 일반적인 모바일 촬영 이미지나 고해상도 JPG를 기준으로 하면 대략 다음 정도의 절감을 기대할 수 있다.

이미지 유형원본 예시최적화 후 예상예상 절감률
고해상도 JPG3-8MB300-900KB약 70-90%
모바일 촬영 이미지2-5MB300-800KB약 60-85%
이미 압축된 이미지500KB-1MB300-800KB약 10-40%
PNG 이미지편차 큼포맷/내용에 따라 상이케이스별 상이

다만 이건 어디까지나 추정이고, 실제로는 팝오버 UI에서 업로드된 ZIP 기준의 원본 용량, 최적화 용량, 절감량, 절감률을 계산해 보여주도록 만들었다. 추정치가 아니라 실제 업로드 파일 기준의 절감 효과를 확인할 수 있다.

서버로 전송되는 ZIP 자체가 최적화된 이미지 기반으로 다시 만들어지니 네트워크 payload가 줄었다. 업로드가 긴 환경이나 대량 이미지 ZIP에서 특히 효과가 있었다. 서버가 최적화된 이미지를 S3에 올리는 구조라면 S3 저장 용량과 전송 비용도 함께 줄고, 광고 게재 보고서처럼 이미지가 누적되는 업무에서는 장기적으로 저장 비용 절감 폭이 커진다. PPT 생성 시 원본 고해상도 이미지를 직접 처리하지 않아도 되니 서버의 이미지 처리 부담과 최종 PPT 파일 크기도 줄일 수 있었다.