Notice
Recent Posts
Recent Comments
Link
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

mimizae 님의 블로그

aws lambda로 이미지 리사이징 기능 구현하기 본문

FE

aws lambda로 이미지 리사이징 기능 구현하기

mimizae 2025. 10. 12. 14:33

 

우리 서비스 kick-on은 축구 경기 예측 커뮤니티 사이트이다 보니, 1.5MB 이상의 고화질 배너 이미지나 선수 사진, 경기 영상 등이 게시글이나 프로필 사진으로 다량 업로드될 수 있다.

 

그러나 구현 초기에는 이미지 최적화를 고려하지 않고 원본 고화질 이미지를 그대로 S3 버킷에 업로드하고 불러왔기 때문에, 1MB가 넘는 이미지를 게시글에 업로드할 경우 로딩에만 10초 이상이 걸린다는 피드백을 받았다.

 

이 문제를 해결하기 위해, 이미지 최적화가 반드시 필요하다고 판단했고 ‘이미지 업로드 단계’와 ‘이미지 로딩 단계’, 두 가지 측면에서 최적화 전략을 수립했다.

 

이미지 업로드 - 이미지 압축

이미지 업로드 시, 업로드 속도에 가장 영향을 끼치는 건 이미지의 용량일 것이다.

기존 이미지 업로드 플로우는 사용자가 파일에서 이미지를 고르면 s3 버킷에 업로드 되고 난 후에야 에디터에 미리 보이기 시작했다.
즉, 이미지 용량이 크면 클수록 s3 버킷 업로드 속도가 늘어나 미리 보여지기까지의 시간도 늘어나는 것이었다.

이를 두 가지로 해결하고자 했다.
1. 미리 보기 URL
2. 이미지 압축

우선 사용자가 이미지를 고르면 그 즉시 에디터나 썸네일 공간에 미리 보기 URL을 보이게 했다. 

그 뒤, 이미지가 s3 버킷에 업로드 되면 s3 URL로 미리 보기 URL을 바꿔치기 하는 방식이다.

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
		const file = event.target.files?.[0];
		if (!file) return;

		if (file.size > 2 * 1024 * 1024) {
			// 2MB 이상 -> 모달 먼저
			setPendingFile(file);
			setShowModal(true);
		} else {
			// 2MB 미만 -> 미리보기 + 업로드
			showPreviewAndUpload(file);
		}
	};

	const showPreviewAndUpload = (file: File) => {
		const previewUrl = URL.createObjectURL(file);
		onChange(previewUrl); // 미리보기 url을 부모(에디터에 전달)

		const img = document.createElement('img');
		img.src = previewUrl;
		img.onload = () => {
			setIsPortrait(img.height > img.width);
		};

		handleFileUpload(file);
	};

	const handleFileUpload = async (file: File) => {
		try {
			const presignedResponse = await getPresignedUrl({
				type: 'news-files',
				fileName: file.name,
			});

			const { presignedUrl, s3Url } = presignedResponse.data;
			await uploadToS3(presignedUrl, file);
			onChange(s3Url); // s3 버킷 업로드가 완료되면 s3 Url을 부모에 전달
		} catch (error) {
			console.error('파일 업로드 실패:', error);
		}
	};



이 방식은 실로 효과가 있었다. 이미지가 바로바로 보이니 사용자 입장에서는 훨씬 잘 돌아가는 느낌이 들었다.
그런데 이 방식의 문제점은 URL 바꿔치기 시기와 사용자의 게시글 작성 완료 시기가 잘 맞아떨어져야 한다는 점이다.

이게 무슨 말이냐면, 사용자가 고화질의 이미지를 선택했을 경우 이미지는 미리보기 URL로 바로 보여지지만 고화질이기에 아직 s3 버킷 업로드가 진행되고 있다.
이때 사용자는 이미지가 보이니 모두 완료되었다는 느낌에 바로 작성 완료 버튼을 누를 수 있다. (이게 문제)


이때 url이 바꿔치기 되기 전에 작성이 완료 되면... 올라가는 게시글에는 사진이 온데간데 없어지는 것. 미리보기 url은 가짜 url이니까!!


여기에서 생각해 본 해결책은 이미지 압축이다.
s3 업로드가 느려져 사용자의 글 작성 시기와 업로드 시기가 잘 안 맞는 것에는 이미지 용량의 문제가 있으니 특정 용량 이상이 되면 이미지를 압축한 뒤 업로드한다. (업로드 속도를 올리기 위함)

(추후 있을 서버리스 환경인 lambda에서도 이미지 리사이징 작업 중 메모리 과부하로 리사이징 실패할 가능성도 예방하고자 이미지 압축은 가져가야 한다.)


이때 이미지를 과도하게 압축해서 올리지는 않는다.

리사이징 작업도 바로 더해질 것이기 때문에 퀄리티를 굉장히 낮추어 서버에 과부하가 오지 않을 정도로만 압축하고자 한다.

그렇게 생각한 이미지 업로드(압축) 플로우는...

  • 이미지 용량 기준 초과 (2.5MB) ➡️ 미리보기 URL로 사용자에게 먼저 제시 ➡️ 이미지 용량 커 압축 진행 됨을 사용자에게 알림
    (confirm modal) ➡️ 이미지 압축 ➡️ 압축된 이미지 s3 업로드

  • 이미지 용량 기준 미만 (1.5MB) ➡️ 미리보기 URL로 사용자에게 먼저 제시 ➡️ 이미지 s3 업로드 

(두 경우 모두 이미지들의 s3 업로드가 완료되지 않으면 글 작성 완료 버튼 비활성화)

 

이미지 렌더링 - aws lambda로 이미지 리사이징

 

 

이미지 업로드 최적화 전략을 구상했다면, 이제는 이미지 로딩 과정에서의 최적화도 고려해야 했다. 이때 중점적으로 본 부분은 로딩 컴포넌트나 스켈레톤이 아니라 (중요하지 않다는 건 아님) 바로 이미지 해상도였다.

 

처음 이미지 업로드 전략을 구상했을 때, 이미지 단순 압축 후 업로드하는 방식을 택했다. 이때 용량이 적은 이미지를 그대로 로딩하니 속도는 빨라졌어도 품질 손상이 눈에 띄게 보였다.

특히 우리 서비스의 핵심 후킹 요소인 배너 이미지의 화질이 깨지면서, 사이트 전체의 인상이 나빠지는 문제가 있었다. 

 

그래서 이미지의 품질 손상을 가장 덜할 방법이 무엇일지 생각해 보았다.

(이미지의 품질 손상은 가장 덜하면서, 용량도 적게 만드는 방법... 최적화 숙명 같은 것 )

 

이미지 품질 손상이 무섭다고 이미지 압축을 안 할 수도 (5MB, 10MB 넘는 이미지들을 서버에서 그냥 다루게 할 수는 없고 이를 리사이징 한다고 해도 과부하가 올 것 같았다...), 그렇다고 무지성 압축을 할 수도 없는 노릇이었다. 

 

이때 이미지 해상도와 이미지 리사이징에 대해 생각하게 되었다.

"화질이 깨졌다는 건... 너무 과도한 압축으로 이미지의 밀도의 문제... 일 테고 그럼 픽셀 수 자체를 줄여야 하는 건가? 그럼 리사이징으로 이미지 크기 (= 픽셀 수)를 줄여서 개선할 수 있지 않을까?"

라는 생각을 했다.


잠깐!! 이미지 압축과 이미지 리사이징의 차이를 간단히만...!!!!! 알아보자.

 

이미지 압축은 이미지의 픽셀 수는 그대로 두고, 데이터 자체를 줄이는 것이다.

예를 들어, 4000×3000 이미지의 픽셀 수는 그대로 유지하면서 압축률을 높여 용량을 줄이는 것이다.

픽셀 수를 그대로 유지하면서 압축하려 하니 그 정도가 강할수록 화질 손실(노이즈, 뭉개짐) 발생한다. 시각적으로 “깨진다”, “뿌옇다” 느낌이 나는 것이다... 

안녕 ㅎㅎ

반면 이미지 리사이징픽셀 수 자체를 줄여서 용량을 줄이는 것이다.

4000×3000 → 1200×900으로 줄이면 픽셀 자체가 줄어들기 때문에 저장해야 할 데이터 양이 근본적으로 줄어든다.

 


 


이미지 리사이징은 ‘필요 없는 픽셀을 줄이는 것’이고,
이미지 압축은 ‘같은 픽셀을 억지로 줄이는 것’이다.

 

이미지 압축은 이미지의 크기는 그대로 두고 그 안에 든 데이터를 억지로 줄이는 것이니 정보가 손실되어 노이즈나 뭉개짐이 생길 수밖에 없다.

반면, 이미지 리사이징은 이미지 자체를 작게 만들어서 데이터가 자연스럽게 줄어든다. 화질 손실이 덜하고, 눈으로 보기에도 부드럽고 자연스럽다.

 

 

그래서 이미지를 단순히 압축만 하는 대신,

“이미지를 기준에 따라 선택적으로 압축하고, 리사이징함으로써 용량을 줄이고 그 이미지를 로딩하자” 라는 방향으로 접근했다!!

 

이를 위해 이미지 리사이징 라이브러리 Sharp를 사용해 보았으나, 프론트엔드 환경(로컬)에서 실행할 경우 OS 의존성 문제가 발생했다. 

 

Sharp는 Node.js 환경에서 이미지(예: JPG, PNG, WebP 등)를 빠르게 리사이징·압축·포맷 변환할 수 있게 해주는 네이티브 모듈이다.
즉, 단순한 JS 코드가 아니라 C/C++로 작성된 libvips라는 이미지 처리 엔진을 내부적으로 사용한다.

 

그래서 npm install sharp 하면 단순히 JS 파일만 설치되는 게 아니라, 운영체제(OS) 에 맞는 libvips 바이너리를 다운로드하거나 빌드해서 함께 설치 된다.

 

이 libvips 바이너리가 바로 문제의 핵심이다. 로컬 개발 환경이나 브라우저 기반의 프론트엔드에서 Sharp를 직접 실행하기에는 환경 종속성과 빌드 문제가 빈번하게 발생하는 것이다.

 

 

그래서!!!! AWS Lambda를 이용하는 서버리스 접근 방식으로 전환했다.

 

프론트엔드 환경에서는 Sharp의 네이티브 의존성(libvips) 문제로 인해 빌드나 실행 오류가 자주 발생했고, 운영체제나 개발 환경이 달라질 때마다 별도의 설정과 빌드 과정을 반복해야 했다.

이 문제를 해결하기 위해 Lambda의 Layer를 활용해 Sharp를 AWS Lambda 환경에 맞게 별도로 빌드하고, 이를 Lambda 함수에 포함시켰다. 이를 통해 OS나 환경에 따른 빌드 의존성 문제를 근본적으로 해소할 수 있었다.

 

또한 이미지 리사이징은 사용자의 요청마다 즉각적으로 처리할 필요가 없고, 업로드 시 한 번만 수행하면 되는 비동기적·이벤트 기반 작업이기 때문에, 항상 서버를 띄워둘 필요가 없는 서버리스 아키텍처에 적합했다.

 

최종적으로 Lambda 함수 내에 Sharp가 포함된 Layer를 올려서 Sharp를 활용한 이미지 리사이징 로직을 작성했고, 이미지가 S3의 원본 버킷(origin)에 업로드될 때마다 그 Lambda 함수가 자동으로 트리거 되어 이미지를 리사이징하도록 했다.

리사이징된 이미지는 별도의 resized image 버킷에 저장되고, 웹에서는 이 버킷의 이미지를 불러와 렌더링하도록 구성했다.

 


 

 

그럼 이제... aws 설정을 할 시간이다...!! 올영 테크 블로그를 참고했다.

 

이 설정은 다음 포스트에. . . 

'FE' 카테고리의 다른 글

낙관적 업데이트 적용하기  (0) 2025.12.05
스크롤 감지 Tab-Bar 구현하기  (0) 2025.11.26
실시간 알림 구현하기  (0) 2025.11.16
Virtual DOM이란?  (0) 2025.11.07
SEO에 대한 고찰  (2) 2025.10.16