이번 Yapp에서 진행하는 ‘그라밋’ 서비스는 대학원생들이 실험 공고를 올리고, 이를 피험자와 매칭해주는 플랫폼입니다. 이 서비스에서는 실험 공고에 이미지도 함께 첨부할 수 있는 기능을 제공하고 있습니다.
이미지 저장소로는 AWS S3를 사용하여 이미지 등록 및 조회 기능을 구현하고 있습니다. 하지만 이전 프로젝트를 진행하면서 알게 된 점은, 원본 이미지를 그대로 조회하면 서비스에서 이미지가 로딩되는 속도가 느려지는 문제가 발생합니다. 실제로 ‘삐삐’ 운영 중에도 이미지 조회 속도가 느리다는 피드백이 많았기 때문에, 이번 ‘그라밋’ 서비스에서도 이미지 조회 속도를 개선할 필요가 있다고 판단했습니다.
1. WebP
WebP는 구글에서 개발한 이미지 포맷으로, 손실 압축과 무손실 압축을 모두 지원하는 효율적인 포맷입니다.
기존에 많이 사용되는 JPEG나 PNG 파일을 WebP로 변환하면, 무손실 압축 기준으로도 약 20~30% 정도 파일 크기를 줄일 수 있습니다. 이렇게 이미지 퀄리티를 최대한 유지하면서도 용량을 효과적으로 줄일 수 있는 특성 때문에, 그라밋에서도 사용자가 업로드한 이미지를 WebP로 변환하는 방식을 채택하기로 했습니다.
다만, WebP 변환만으로는 최적화를 완벽히 달성하기 어려울 수 있어, 이미지 리사이징도 함께 진행하여 전체적인 용량을 더욱 개선하고자 했습니다.
2. AWS Lambda
AWS Lambda는 서버리스 플랫폼으로, 개발자가 서버 관리 없이 애플리케이션을 빌드하고 실행할 수 있는 클라우드 네이티브 개발 모델입니다. 즉, 서버 인프라에 대한 부담을 덜고, 비즈니스 로직에만 집중할 수 있는 장점이 있습니다.
Lambda 함수는 이벤트 트리거가 발생할 때 자동으로 실행되며, 개발자가 정의한 로직을 수행할 수 있습니다. 이를 통해 다양한 애플리케이션 로직을 효율적으로 처리할 수 있습니다.
이미지 리사이징과 WebP 변환에는 여러 가지 방법이 있지만, 저는 AWS Lambda를 활용하여 S3 버킷에 트리거를 설정하고, 사용자가 업로드한 이미지를 자동으로 리사이징하고 WebP로 변환하는 방식으로 구현했습니다. 서버를 직접 관리할 필요가 없어 인프라 관리의 부담을 크게 줄일 수 있었습니다. 특히 Lambda는 온디맨드 방식으로 작동하므로, 사용량에 맞춰 유연하게 비용이 청구되어 사이드 프로젝트에 매우 적합한 솔루션이라 판단했습니다.
실행 순서는 다음과 같습니다.
1. 프론트에서 Pre-SignedUrl으로 PUT 요청을 보내 버킷에 이미지 업로드
2. S3에 Event 발생
3. S3 trigger가 발생해 Lambda 함수 실행
4. 함수에서 이미지를 가져와, 리사이징 및 webp 변환을 수행한 후, 지정된 버킷에 리사이징 이미지 업로드
3. 구현
사용할 S3 버킷은 생성되었다는 전제 하에 설명하겠습니다.
1) Lambda 함수 생성
이미지 업로드 요청, 즉 PUT 작업이 수행될 때 실행 될 함수를 정의합니다.
2) 생성한 Lambda 함수에 사용할 S3 정책 연결
업로드 함수가 동작하는 데 시간이 다소 소요될 수 있으므로, 제한 시간을 50초로 늘렸습니다.
저는 기존에 생성해두었던 role이 있어 해당 역할을 연결했습니다.
해당 역할은 AWSLambdaExecute와 S3를 읽고 쓸 수 있는 액세스 권한을 포함하고 있습니다.
이렇게 역할을 연결한다면, 이제 Lambda 함수는 S3에 접근하고 업로드할 수 있는 권한이 부여되었습니다.
3) S3에 이미지가 업로드될 때 함수를 실행하는 트리거 생성
아까 생성했던 함수로 돌아와 사용할 S3 버킷을 대상으로 트리거를 추가해줍니다. 저는 이미지 업로드가 될 때마다 함수를 실행할 것이므로, Event type은 PUT으로 해주었습니다.
4) 이미지 리사이징과 webp 변환을 위한 함수 코드 작성
index.js
const { S3Client, GetObjectCommand, PutObjectCommand } = require("@aws-sdk/client-s3");
const { Readable } = require("stream");
const sharp = require("sharp");
const s3 = new S3Client({ region: "ap-northeast-2" }); // AWS S3 리전 설정
exports.handler = async (event) => {
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " "));
// 리사이즈된 이미지는 무한 업로드를 방지하기 위해 처리하지 않음
if (key.startsWith("resized-image/")) {
return;
}
const fileName = key.split("/").pop();
const baseFileName = fileName.split(".").slice(0, -1).join(".");
const dstKey = `resized-image/resized-${baseFileName}.webp`; // 리사이징된 이미지의 저장 경로
try {
// 원본 이미지 가져오기
const response = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
var stream = response.Body;
if (!stream instanceof Readable) {
console.log("Unknown object stream type");
return;
}
const content_buffer = Buffer.concat(await stream.toArray());
// 이미지 리사이즈 및 webp 변환
const width = 600;
const height = 600;
const output = await sharp(content_buffer)
.resize(width, height, { fit: "inside" })
.webp({ lossless: true }) // 무손실 webp로 변환
.toBuffer();
await s3.send(
new PutObjectCommand({
Bucket: bucket,
Key: dstKey,
Body: output,
ContentType: "image/webp",
})
);
console.log("Successfully resized and uploaded");
} catch (error) {
console.error("Error processing file", error);
}
};
저는 Node.js를 이용해 AWS Lambda를 활용한 이미지 리사이징 및 WebP 변환 기능을 구현했습니다. 이 코드는 사용자가 클라이언트에서 업로드한 이미지를 자동으로 리사이징하고, 용량을 줄이기 위해 WebP 포맷으로 변환하는 로직을 포함하고 있습니다. Node.js 환경에서 해당 로직을 index.js 파일로 작성한 후, 이를 AWS Lambda로 업로드하여 트리거 기반으로 동작하게 했습니다.
5) 결과 확인
같은 사진을 webp로 변환하고 이미지 리사이징을 적용한 결과 거의 이미지 용량이 5배 줄어든 걸 확인할 수 있었습니다.
실사용자가 많은 서비스에서는 여기서 더 나아가 CDN을 활용하여 이미지 로딩 속도를 크게 개선할 수 있지만, 사이드 프로젝트의 규모와 리소스 비용을 고려했을 때, WebP로 변환하고 리사이징하는 방식이 가장 적합하다고 판단했습니다.
나중에 기회가 된다면 Lambda@Edge를 이용해서 시도 해보고 싶네요. ☺️
참고 사이트
https://oliveyoung.tech/2023-05-19/aws-lambda-resize/
AWS Lambda Image Resize 도입기 | 올리브영 테크블로그
신규 상품 프로젝트에서 AWS Lambda 이미지 리사이징 적용하기
oliveyoung.tech
https://j-tech-dev.tistory.com/101
AWS Lambda S3 트리거를 사용한 이미지 리사이징 + webp 변환을 통한 이미지 최적화
이미지 리사이징 말 그대로 이미지의 크기를 조정하는 과정을 말합니다. 위의 사진은 Lighthouse에서 이미지의 크기가 적절하지 않다고 알려주는 화면입니다. 다시 말해, 렌더링 된 이미지의 사이
j-tech-dev.tistory.com
'백엔드 개발일지' 카테고리의 다른 글
[Architecture] 그라밋 프로젝트, 클린 아키텍처 입히기 (0) | 2025.02.23 |
---|---|
[AWS] 이미지 WebP 변환 Lambda 실행 속도 개선 일지 (1) | 2025.02.14 |
[Spring] FeignClient로 POST 요청 시 발생하는 Length Required(411) 에러 해결 방법 (2) | 2025.01.10 |
쉽게 알아보는 클린 아키텍처 (Clean Architecture) (2) | 2024.11.27 |
[Spring] Redis 캐시를 통해 조회 성능 개선하기 (3) | 2024.01.24 |