문제 상황
1. 팀원의 제보
특정 기기에서만 발생하는 이슈이다. 확인을 해보자.
2. PC에서 테스트
용량이 조금만 커지면 용량을 초과했다며 413에러가 발생하며 에러를 출력한다.
현재 이미지 업로드 방식은 다음과 같다.
- 브라우저 → 백엔드 서버에 이미지 전송
- 백엔드 서버 → AWS S3에 이미지 업로드
- 백엔드 서버 → 브라우저에 AWS로부터 받은 이미지 URL 전달
13 과정동안은 사용자에게 base64로 변환한 이미지를 사용자에게 보여준다. 이 때, 13번 과정의 시간도 단축시키면 좋지 않을까?
단순히 이미지 url을 받아오기 위한 방식이라면, 브라우저에서 S3에 다이렉트로 업로드하여 url을 받아오는 방식이 더 빠르지 않을까?
해결 방법은 다음과 같다.
- 백엔드 서버가 아니라 직접 S3에 이미지를 업로드하여 이미지 url을 받아온다.
- 이미지의 용량을 브라우저에서 직접 줄여서 백엔드 서버에 전송한다.
백엔드 팀에서 이미지 업로드 용량에 제한을 둔 것은 이유가 있을 것이다. 이에 대해 우선 백엔드 팀에 물어보았다. 그리고 이미지 용량을 무한대로 업로드하게 할 수도 없는 격이다.
또한, 1번과 2번 중 선택이 아니라 둘 다 적용하는 것도 좋은 방법이라 생각했다.
해결 과정
1. 이미지 용량을 어떻게 브라우저단에서 줄일까?
-
browser-image-compression라이브러리 설치 -
comPressImage 함수 만들기
export const compressImage = async (file: File) => { const options = { maxSizeMB: 0.2, maxWidthOrHeight: 1920, useWebWorker: true, }; return await imageCompression(file, options); }; -
압축하기
압축하여 측정해보았다.
console.log('압축 전', fileList[0].size / 1024 / 1024); const compressedFileList = await compressImage(fileList[0]); console.log('압축 후', compressedFileList.size / 1024 / 1024);
기존 : 12.7MB → 0.5MB
용량이 약 25배 감소했다.
업로드가 안되던 13MB의 이미지도 이제 업로드가 된다.
❓ 배열 메서드 map 에서 await문을 사용할 수 있을까?
기존 poseFiles함수는 여러 이미지 업로드를 위해 배열 메서드를 사용하고 있다. map함수의 각 문의 비동기 함수를 동기적으로 실행하고자 한다. 병렬적으로 처리하고 싶은 여러 개의 Promise를 담은 배열을 map함수를 이용하여 만들어 Promise.all의 인자로 넘겨주면 된다.
2. map 배열메소드와 Promise.all을 사용하여 여러 Promise 병렬적으로 처리하기
const compressedFileList = await Promise.all(fileList.map((file) => compressImage(file)));
compressedFileList.forEach((file) => {
formData.append('fileList', file);
});
console.log(compressedFileList, formData);
기존 File배열이었던 fileList는 Blob배열로 바뀌었다. File객체가 아닌 Blob객체를 만난 formData는 아무런 값도 담지 못했다.
❓ File객체가 뭐고 Blob객체가 뭔데?
- Blob과 File객체는 모두 웹 개발에서 파일 데이터를 다루는 데 사용되는 JS 객체이다.
- Blob 객체 : 바이너리 데이터를 나타내는 객체로, 텍스트나 이미지와 같은 멀티미디어 콘텐츠를 포함할 수 있다.
- 큰 데이터를 효율적으로 다룰 수 있도록 설계되엇따.
- File 객체 : Blob을 기반으로 하며, 사용자 시스템의 파일에 대한 정보를 포함한다.
- Blob의 모든 속성과 메소드를 상속받는다.
- 파일 이름, 마지막 수정 시간과 같은 파일 관련 메타데이터를 포함한다.
❓ 왜 File에서 Blob으로 변환이 된걸까?
// node-modules/browser-image-compression declare function imageCompression(image: File, options: Options): Promise<File>;browser-image-compression의 imageCompression함수의 리턴값은 분명히 Promise
이다. 코드를 뜯어보자.
- 내가 사용한 browser-image-compression라이브러리의 레포지토리이다.
// lib/image-compression.js export default async function compress(file, options, previousProgress = 0) { // .. let compressedFile; // .. compressedFile = await canvasToFile(newCanvas, outputFileType, file.name, file.lastModified, quality); return compressedFile; }// lib/utils.js export async function canvasToFile(canvas, fileType, fileName, fileLastModified, quality = 1) { let file; // .. return file; if (fileType === 'image/png') { file = new Blob([png], { type: fileType }); // .. } }그렇다. canvas를 이용하여 사이즈를 줄이고, 이 canvas를 파일로 변환하는 과정에서 new File이 아니라 new Blob를 이용하여 Blob객체로 생성하는 것을 확인할 수 있었다.
❓ 애초에 new Blob이 아닌 new File로 생성할 수는 없을까?
- 직접 라이브러리를 다운로드 받아 우리 프로젝트 패키지에 추가한다.
- 이 방법도 가능은 하지만, 상당히 비효율적이라 생각했다.
- 라이브러리에 기여한다.
- new Blob을 new File로 변환했을 때 발생할 수 있는 사이드이펙트가 있을까?
- 서로 다른 객체여서 발생할 수 있을 것이라 생각하긴 한다. File은 Blob객체에 해당 파일에 대한 정보(이름, 최근 수정 날짜 등)을 추가하여 확장한 객체이다.
- 아직 File을 지원하지 않는 브라우저가 있을 수 있어 호환성 문제가 있다.
- 포크를 하여 수정 후 테스트코드를 돌려보니, 테스트에 실패했다.
3. Blob객체를 File객체로 변환하기
File객체는 Blob을 기반으로 하며, Blob객체에 사용자 시스템에 대한 정보를 일부만 포함하면 File객체가 된다. 생성은 new File로 할 수 있다.
// /utils/compressImage.ts
export const compressImage = async (file: File) => {
const options = {
maxSizeMB: 0.2,
maxWidthOrHeight: 400,
useWebWorker: true,
};
const BlobImage = await imageCompression(file, options);
return new File([BlobImage], file.name, { type: file.type });
};
return을 할 때, 기존 Blob배열에 파일의 이름과 타입을 설정하여 new File을 이용해 File객체를 생성했다.
3. 검증의 과정
1. 이미지를 압축하는 과정에서 이미지가 깨지지 않을까?
- 기존 이미지를 업로드했을 때

- 압축한 이미지를 업로드했을 때

이미지 용량이 클 경우 업로드 자체가 안되어서, 용량이 작은 이미지로 비교를 했는데 거의 차이를 못느끼겠다.
이미지를 압축하여 업로드햇을 때 느낀 가장 큰 효과는 용량이 큰 이미지도 업로드가 된다는 점이고, 그리고 그만큼 모든 이미지에서 업로드 속도가 빨라졌을 것이다.
2. 압축하는 시간 때문에 더 느려지지는 않았을까?


이미지를 압축하지 않았을 때보다 이미지를 압축을 했을 때 업로드 속도가 2배 가까이 더 빨랐다. 용량이 큰 이미지일 경우 차이가 더 커질 것이다.
정리
- 이미지를 프론트단에서 압축하여 백엔드 서버에 전송합니다. 이를 통해 이미지 업로드 속도를 개선하고, 용량이 큰 이미지를 업로드할 수 있습니다.
- 추후 백엔드 팀과 논의를 통해 직접 S3에 업로드하는 방식도 좋을 것 같습니다.