안녕하세요. 리뷰커뮤니티 스쿼드의 백엔드 개발자 소보르빵🍞 입니다!
여러분은 올리브영 셔터를 알고 계시나요?
건강한 아름다움을 리딩하는 플랫폼 올리브영이 운영하는 뷰티 특화 커뮤니티 Shutter 셔터📷는
단순 제품 리뷰를 넘어서 다양한 이미지를 활용하여 일상 속의 취향과 영감을 공유하는 공간입니다.
고객들이 언제든지 모바일앱에 접속해 일상을 공유하고 라이프스타일 트렌드와 상품 정보를 얻도록 셔터를 통해
고객 간 소통을 지원하고 모바일앱을 더 활성화하는 것이 서비스의 목표입니다.
사용자들이 더욱 편리하게 이미지를 업로드하고 즐거운 커뮤니티 경험을 누릴 수 있도록,
올리브영의 리뷰커뮤니티 스쿼드에서는 이미지 업로드 성능 개선 프로젝트를 진행했습니다.
왜 이미지 업로드 속도를 개선하려고 했나요?
스마트폰 카메라 성능의 발전으로 인해 우리는 이제 일상 속 아름다움을 고화질 이미지로 담아낼 수 있게 되었습니다.
하지만 고화질 이미지는 적게는 3MB 에서 많게는 5MB 이상의 큰 용량을 차지하기 때문에, 이를 커뮤니티에 업로드하는 데 상당한 시간이 소요됩니다.
네트워크 환경에 따라서는 최대 몇 분까지 소요될 수 있고 일부 사용자들은 느린 업로드 속도로 인해 대기 시간이 길어져 게시글 작성을 포기하기도 합니다.
이렇게 이미지 업로드 기능은 사용자가 직접 콘텐츠를 생성하고 빠르게 공유할 수 있게 함으로써 애플리케이션의 상호작용성을 높이기 때문에
사용자 경험을 풍부하게 만드는 핵심 요소 중 하나로써 신속하게 기능을 통합하고 일관된 성능을 유지하기 위해 많은 시행착오를 겪게 합니다.
그래서 이미지 업로드 기능을 구현할 때는 사용자의 편의성, 서버의 리소스 관리, 보안 세 가지 측면을 모두 균형있게 고려해야 합니다.
오늘 소개하는 올리브영 셔터는 이미지 중심의 커뮤니티 공간이기 때문에, 이 문제가 더욱 중요하게 고려됩니다.
셔터 이용자들은 최대 10장까지 한 번에 여러 장의 고화질 이미지를 업로드하는 경우가 많은데,
10장의 이미지를 업로드하면, 총 50MB 이상의 이미지 파일을 한 번에 업로드하게 됩니다. 🙀
이렇게 큰 용량의 이미지 파일들을 업로드하는 경우, 사용자는 모든 파일이 업로드될 때까지 기다려야 하기 때문에
이미지를 업로드하는 영역의 개선점을 반드시 찾아야 했습니다!
기존에는 어떻게 이미지를 업로드하였나요?
이미지 업로드 시 서버 처리 과정 (As-Is)
셔터 게시글 작성 화면은 웹 페이지 기반으로 구현되어 있습니다.
사용자가 이미지 파일을 선택하면 서버에서는 요청 받은 이미지 파일들을 AWS S3에 저장하는 방식으로 처리되었습니다.
하지만 이러한 방식은 다음과 같은 문제점을 가지고 있었습니다.
-
대용량 이미지 업로드 시 속도 저하 : 네트워크 대역폭 한계로 인해 전체적인 이미지 업로드 속도가 느려지는 문제가 발생했습니다.
-
서버 부하 증가 : 용량이 큰 이미지 파일을 클라이언트로부터 받고, 받은 이미지 파일을 저장하기 위해 AWS S3로 요청하는 과정에서
서버 부하가 증가했습니다.
결과적으로, 느린 이미지 업로드 속도는 사용자 경험을 저하시키고 커뮤니티 활성화에 걸림돌로 작용했습니다.
어떻게 이미지 업로드 속도를 개선했나요?
이미지 업로드 시 서버 처리 과정 (To-Be)
리뷰커뮤니티 스쿼드는 아래의 4가지 단계들을 거쳐 이미지 업로드 속도를 차근차근 개선했습니다.
1) 이미지 병렬 업로드
2) PreSignedURL을 활용한 이미지 업로드
3) Canvas API 및 OffscreenCanvas를 활용한 최적화 및 이미지 압축
4) UI/UX 를 활용한 사용자 경험 향상
1. 이미지 병렬 업로드
기존에는 여러 개의 이미지 파일을 하나의 request 로 묶어 서버에 전송하고, 서버에서 순차적으로 S3에 업로드하는 방식을 취했습니다.
하지만 해당 경우에는 클라이언트에서 request 를 보낼 때까지 상당히 많은 시간이 소요되는 문제가 있었습니다.
이에 각 이미지 파일을 개별적인 request 로 분리하여 병렬적으로 업로드함으로써 네트워크 대역폭을 효율적으로 활용하여
전체적인 이미지 업로드 시간을 단축하는 효과를 얻었습니다.
특히, 적은 수의 이미지를 업로드하는 경우에는 병렬 처리의 효과가 극대화되어 업로드 속도가 크게 향상되었습니다.
하지만, 다음과 같은 한계점이 발견되었습니다.
- 대용량 이미지 다수 업로드 시 성능 미미 : 8장 이상의 용량이 큰 이미지 파일을 동시에 업로드하는 경우, 네트워크 대역폭이 여러 파일로 분산되어
드라마틱한 개선이 일어나지 않았습니다.
이러한 한계점을 극복하기 위해 추가적인 개선 작업이 필요했습니다.
이미지 병렬 업로드
const uploadImages = async (images) => {
const uploadPromises = images.map(image => {
const formData = new FormData();
formData.append('file', image);
return fetch('/upload', {
method: 'POST',
body: formData
});
});
await Promise.all(uploadPromises);
};
2. PreSignedURL을 활용한 이미지 업로드
PreSignedURL 이란?
- 서버에서 클라이언트에게 S3로 직접 파일을 업로드할 수 있도록 미리 서명한 URL 입니다.
추가 개선 방안으로 PreSignedURL을 활용한 이미지 업로드 방식을 적용했습니다.
기존에는 다음과 같이 이미지 파일을 업로드하였습니다.
-
클라이언트에서 사용자가 선택한 이미지 파일을 request 에 담아 서버에 업로드 요청
-
서버에서는 올리브영 셔터 사용자의 이미지 업로드 요청이 맞는지 다시 검증
-
이미지 파일을 저장할 S3 bucket 과 경로, 파일명을 정하여 서버에서 AWS S3에 요청 후 저장
PreSignedURL 을 적용한 이미지 업로드 방식은 다음과 같습니다.
-
사용자가 선택한 이미지 파일명으로 서버에 PreSignedURL 발급 요청
-
서버에서는 올리브영 셔터 사용자의 이미지 업로드 요청이 맞는지 다시 검증
-
이미지 파일을 저장할 S3 bucket 과 경로, 파일명을 정하고 유효 시간을 설정하여 PreSignedURL 발급 후 클라이언트에 전달
-
클라이언트에서 PreSignedURL 을 통해 직접 AWS S3에 이미지 파일 업로드
-
서버에서 지정한 이미지 저장 경로와 파일명으로 AWS S3에 저장
발급 당시 설정된 유효시간 동안에만 PreSignedURL 을 통한 이미지 업로드가 가능합니다.
이처럼 이미지 업로드 작업은 JSON 데이터를 주고받는 일반적인 API 대비 서버에 부하가 많이 걸리는 작업이었으나,
PreSignedURL을 이용하면 서버에서는 클라이언트로부터 이미지 파일을 받지 않아 부하를 최소화할 수 있었습니다.
업로드할 파일의 용량이 크면 클수록 PreSignedURL을 활용한다면, 서비스하고 있는 서버에 부하를 주지 않고, 업로드할 수 있었습니다.
// 기존 방식
amazonS3Client.putObject(
bucketName,
"$awsS3Path/$uploadPath",
uploadFile.inputStream,
objectMetadata
)
// PreSignedURL 적용 방식
val preSignedUrl : URL = amazonS3Client.generatePresignedUrl(
awsS3Config.getShutterBucketName(),
tempUploadPath,
Date.from(Instant.now().plus(Duration.ofSeconds(PRE_SIGNED_URL_EXPIRATION))),
HttpMethod.PUT
)
3. Canvas API 및 OffscreenCanvas를 활용한 최적화 및 이미지 압축
Canvas API 및 OffscreenCanvas를 활용한 멀티 스레드
canvas API에서 제공하는 service worker와 OffscreenCanvas를 적용하여,
메인 스레드가 아닌 별도의 워커 스레드에서 canvas 작업을 처리할 수 있도록 하였습니다.
이를 통해 메인 스레드의 부하를 줄여 보다 빠르게 이미지 압축 작업을 처리할 수 있도록 하였습니다.
이미지 압축 방식 변경 (JPEG -> WebP)
기존에는 JPEG 포맷을 사용했지만, WebP 포맷으로 변경하는 로직을 적용하였습니다.
WebP 포맷은 같은 화질을 유지하면서도 JPEG보다 파일 크기를 줄이는 데 매우 효과적으로,
WebP로 변환된 이미지는 업로드할 파일의 용량을 줄여주어 업로드 시간을 더욱 단축할 수 있었습니다.
OffscreenCanvas를 이용한 WebP 변환
- Step 1 Web Worker 파일 생성 (optimizeImageWorker.js)
self.onmessage = (event) => {
const file = event.data;
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = (event) => {
const img = new Image();
img.src = event.target.result;
img.onload = () => {
const offscreenCanvas = new OffscreenCanvas(img.width, img.height);
const ctx = offscreenCanvas.getContext('2d');
ctx.drawImage(img, 0, 0);
offscreenCanvas.convertToBlob({
type: 'image/webp',
quality: 0.8
}).then((blob) => {
self.postMessage(blob);
});
};
};
};
- Step 2 메인 스레드에서 Web Worker를 사용하여 이미지 최적화
const optimizeImage = (file) => {
return new Promise((resolve, reject) => {
const worker = new Worker('optimizeImageWorker.js');
worker.onmessage = (event) => {
resolve(event.data);
};
worker.onerror = (error) => {
reject(error);
};
worker.postMessage(file);
});
};
위 코드에서는 OffscreenCanvas를 사용하여 이미지 크기를 최적화하고, WebP 포맷으로 변환합니다.
UI/UX 를 활용한 사용자 경험 향상
기존에는 셔터에서 이미지를 업로드할 때, 업로드 진행 상태를 확인할 수 있는 UI를 제공하지 않았습니다.
진행 상태를 확인할 수 있는 UI를 제공하지 않는 경우 다음과 같은 문제점을 가지고 있었습니다.
-
이미지 업로드가 진행 중인지, 얼마나 진행되었는지를 알 수 없어 느리다고 느낄 수 있습니다.
-
기다림의 불확실성으로 인해 사용자는 언제까지 기다려야 하는지 알 수 없어 불편함을 느낄 수 있습니다.
이를 해결하기 위해 아래의 두 가지의 개선 방안 중 어떤 UI/UX가 사용자에게 더 좋은 경험을 제공할 수 있을지 고민했습니다.
-
업로드 진행 상태를 실시간으로 표시하는 방안
-
UI 상에서는 이미지를 업로드하자마자 완료된 것처럼 표현하면서, 별도의 스레드로 계속 이미지 업로드를 진행하는 방안
스쿼드에서는 업로드 진행 상태를 실시간으로 표시하는 방안이 아래의 장점들이 있다고 판단하여 적용하였습니다.
-
사용자의 기다림의 불확실성을 해소할 수 있다.
-
이미지 업로드 완료 예상 시간을 예측할 수 있게 해준다.
-
시스템이 정상적으로 작동하고 있다는 피드백을 준다.
아래는 onprogress 이벤트를 통해 업로드 진행 상태를 실시간으로 표시 하도록 적용한 이미지 입니다.
마치며
이번 프로젝트를 통해 저희는 고화질 이미지 시대에 발맞춰 이미지 업로드 속도를 향상시키고, 사용자 경험을 혁신적으로 개선하고자 노력했습니다.
병렬 처리, Presigned URL 도입, 이미지 최적화, 그리고 사용자 중심의 UI/UX 개선까지,
다양한 기술적 해결책을 통해 이미지 업로드 성능을 평균 50% 이상 향상시켰습니다.
더불어 이번 성과는 단순히 기술적인 문제 해결을 넘어, 사용자들이 셔터를 더욱 편리하고 즐겁게 이용할 수 있도록 만들었다는 점에서 큰 의미가 있습니다.
하지만 여기서 만족하지 않고, 더욱 발전된 기술을 통해 사용자들에게 최고의 경험을 제공하기 위해 노력할 것입니다.
앞으로도 빠르고 안정적인 이미지 업로드 환경을 구축하고, 다양한 기능 개선을 통해 사용자들의 만족도를 높여나갈 셔터를 위해
많은 관심과 응원 부탁드립니다.