AWS S3 + Vue.js + SpringBoot(3/3) - CDN, Lambda 적용하기
1. 이미지 로딩 속도 개선
원본 사이즈를 그대로 올리는 건 엄청 오래 걸립니다.
보통 하나의 이미지 사이즈가 5MB라고 가정했을 때, 100개의 사진 목록을 한꺼번에 불러오려면 무려 500MB를 로딩해야 합니다. 이러면 로딩이 늦을 수밖에 없겠죠? 그래서 빠른 이미지 로딩을 위해서는 이미지 사이즈를 줄이는 게 필수입니다.
이미지 사이즈를 줄이는 데 2가지 방법이 있습니다. 첫 번째는 서버에서 직접 이미지 크기를 조정하는 것입니다. 두 번째는 SERVERLESS 한 LAMBDA를 활용하는 것입니다.
서버에서 많은 이미지를 리사이징 하면, CPU 리소스가 상당히 되기에 서버에 상당한 부하를 줄 수 있습니다.
이 포스팅에서는 LAMBDA의 방법을 설명하겠습니다. 또한 CDN도 사용하면 이미지 로딩 속도가 빨라지기에 사용하는 이유와 적용 방법에 대해서 설명하겠습니다.
전체적인 플로우를 설명하면 아래와 같습니다.
S3 버킷에 지정된 폴더에 새로운 이미지가 생성 => LAMBDA 함수를 실행 => lambda 함수는 새로 생성된 이미지를 불러오기 => sharp 모듈로 이미지를 변환시키기 => 특정 크기 폴더에 리사이징된 이미지 넣기
2. LAMBDA
Serverless. 서버가 없고, 소스코드만 올리면 lambda가 알아서 해줍니다.
다른 서비스와 달리 사용하는 순간에만 과금이 됩니다. 비용도 매우 저렴한 편에 속합니다.
예시로 함수에 512MB 메모리를 할당하고 한 달에 300만 회 실행하여 매번 1초간 실행됐다면 대략 18.34$입니다. 23.01.05 기준 22,846원인 셈입니다.
요금에 관한 자세한 설명은 링크를 참조하시면 됩니다.
사용법은 간단합니다. lambda에서 함수를 생성하고 트리거를 설정합니다. 트리거는 어떤 AWS 서비스에 의해서 액션이 발생했을 때 함수를 실행하라고 명령할 수 있습니다.
3. 이미지 리사이징
람다에 들어가서 함수 생성을 합니다.
함수 이름을 정하고 함수를 생성하시면 됩니다. 함수에 들어가고 트리거를 추가해 줍시다.
저희는 S3 버킷 안의 이미지를 리사이징 할 것이기에 S3를 선택하시면 됩니다.
트리거를 생성하면 정상적으로 작동되는지 테스트해 봅시다. 정상적으로 작동이 되면 본격적으로 트리거를 생성해 봅시다. 트리거 추가를 누릅니다.
Event type은 POST를 합니다. Prefix는 폴더명을 넣어줍니다. thumbnail 폴더 안에 이미지가 등록됐을 때 트리거가 작동한다는 설정을 한 것입니다.
위의 경고문은 재귀 호출의 경고문입니다. 이벤트를 계속 호출하면 무한 루프에 빠질 수도 있으니 특정 폴더 안에서만 트리거가 발생하게끔 만드는 게 중요합니다. 만약 폴더명을 지정 안 하고 트리거를 생성했다면 무한 루프에 빠질 위험이 있고 비용이 엄청 발생할 겁니다. 위의 유의사항에 숙지했으면 체크를 하고 트리거를 생성하면 됩니다.
트리거가 생성이 됐습니다. thumbnail 폴더에 이미지가 업로드가 되면 자동으로 트리거가 발생한다는 설정입니다.
앞으로 설정할 흐름은 아래와 같습니다.
이미지를 S3 버킷에 불러오기 => 불러온 이미지 여러 사이즈로 리사이징 => 다시 S3 버킷에 해당 파일을 저장
이미지를 리사이징 할 때, 이미지를 리사이징하는 sharp 모듈을 사용하겠습니다. 이를 위해 VS Code IDE를 사용하겠습니다. lambda 폴더 안에 index.js 파일을 만듭니다.
터미널을 넣어 npm init -y 명령어를 칩니다. npm sharp를 사용할 겁니다.
npm sharp는 이미지를 가공하는 많이 사용됩니다. AWS lambda는 리눅스 환경입니다. 그냥 npm install sharp 보다는 리눅스 환경에 맞게끔 npm install --arch=x64 --platform=linux sharp 명령어를 사용합니다. 리눅스를 사용하는 람다 환경을 맞춰줘야 합니다.
정상적으로 생성이 됐으면 파일 목록이 위와 같이 나올 겁니다.
exports.handler = async (event) => {
console.dir(event.Records[0].s3)
const response = {
statusCode: 200,
body: event,
};
return response
};
그리고 AWS 트리거에서 기본적으로 제공해주는 위의 코드를 index.js에 붙여줍니다. 기존 코드를 아래처럼 변경하겠습니다.
const sharp = require("sharp");
const aws = require("aws-sdk");
const s3 = new aws.S3()
const transformationOptions = [
{ name:"w140", width: 140},
{ name:"w600", width: 600},
];
exports.handler = async (event) => {
try{
const Key = event.Records[0].s3.object.key;
const keyOnly = Key.split("/")[1];
console.log(`Image Resizing: ${keyOnly}`)
const image= await s3
.getObject({Bucket:"hospital-image-upload",Key})
.promise();
await Promise.all(
transformationOptions.map(async ({ name,width }) => {
try {
const newKey = `${name}/${keyOnly}`;
const resizedImage = await sharp(image.Body)
.rotate()
.resize({width,height:width,fit:"outside"})
.toBuffer();
await s3
.putObject({
Bucket:"hospital-image-upload",
Body:resizedImage,
Key:newKey
})
.promise();
} catch (err) {
throw err;
}
})
);
return{
statusCode:200,
body: event,
};
} catch(err){
console.log(err);
return {
statusCode: 500,
body:event,
};
}
};
transformationOptions은 어떤 크기들로 조정해줄 건지 배열들로 선언합니다. 만약 내가 이미지를 저장하는 데 여러 개가 아닌 한 개의 리사이징 형태로 만들고 싶다면 숫자만큼 줄이시면 됩니다.
const Key = event.Records[0].s3.object.key;
const keyOnly = Key.split("/")[1];
console.log(`Image Resizing: ${keyOnly}`)
const image= await s3
.getObject({Bucket:"hospital-image-upload",Key})
.promise();
s3.getObject로 S3 버킷에 있는 이미지를 가져옵니다. 이때 Bucket:"S3 버킷 이름"으로 선언해야 합니다.
await Promise.all(
transformationOptions.map(async ({ name,width }) => {
try {
const newKey = `${name}/${keyOnly}`;
const resizedImage = await sharp(image.Body)
.rotate()
.resize({width,height:width,fit:"outside"})
.toBuffer();
await s3
.putObject({
Bucket:"hospital-image-upload",
Body:resizedImage,
Key:newKey
})
.promise();
} catch (err) {
throw err;
}
})
sharp 함수로 이미지를 리사이징 해주고 putObject으로 S3 버킷에 리사이징 된 이미지를 저장합니다. 이 파일들을 트리거에 넣으려면
Explorer로 열고
zip 형태로 파일들을 통째로 압축해 줍니다. 이 압축 파일들을
업로드 버튼을 눌러서 올리고 저장하면 됩니다.
이런 식으로 코드 정보가 나오면 정상적으로 등록이 된 겁니다.
만약 여러 함수를 관리해야 된다면
Serverless 프레임워크를 사용하시면 됩니다. 다양한 클라우드 사에 배포할 수 있습니다. 이 포스팅은 간단한 이미지 리사이징이라 넘어가겠습니다.
위의 과정을 걸쳐서 이미지를 올려도 이미지 리사이징은 자동으로 안 될 겁니다. 그렇기에 lambda 함수에 권한을 줘야 합니다.
lambda 함수에 S3 권한을 부여하는 방법을 알아보겠습니다.
구성을 눌러줍니다.
역할 이름을 눌러줍니다. 이전에 만들었던 S3 putObject, getObject에 권한을 lambda에 줄 겁니다.
이전에 만들었던 정책을 연결해주면 정상적으로 권한을 부여할 수 있습니다.
그리고 람다의 기본 설정을 변경할 수 있습니다. 실행 역할의 편집을 누르면
람다 함수를 실행하는 데 제한 시간을 두거나 메모리를 설정하는 등 제약을 걸 수 있습니다.
이제 이미지를 등록하고 S3 버킷에 가면
기존 thumnail 폴더만 있던 것이 w140과 w600 폴더가 생긴 걸 볼 수 있습니다. 이 변경된 이미지를 프론트에서 불러오려면
<img alt="thumbnail" class="image__thumbnail"
:src='`https://hospital-image-upload.s3.ap-northeast-2.amazonaws.com/w140/${contentItem.imageKey}`'/>
기존 .com/thumbnail/$을 .com/w140/$으로 변경하시면 됩니다. 만약 width가 600인 이미지 파일을 불러오고 싶다면 .com/w600/$으로 변경하시면 됩니다.
참고로 w140, w600 폴더 안의 이미지들을 못 보실 겁니다. 권한이 없기 때문이기에 이전과 동일하게
권한에 들어가서
편집 버튼을 눌러줍니다.
위의 사진처럼 w140과 w600을 추가하시면 됩니다. 그리고 w140에 저장된 이미지 파일들을 불러오면 정상적으로 조회할 수 있습니다.
람다로 리사이징 된 이미지 크기들을 보시면
원본 파일보다 크기가 확연히 줄어든 걸 보실 수 있습니다.
4. CDN
CDN은 요즘 파일을 저장하는데 필수로 들어갑니다.
사용하는 이유는 사용자들이 밀집된 게 아니라 전 세계로 흩어졌습니다. 미국과 한국 사이의 데이터를 전송할 때 거리는 너무 길어서 소요 시간이 엄청 걸립니다.
서버는 두 종류입니다. 메인 서버(S3가 있는 서버)와 세계 곳곳의 엣지 서버(CDN 서버)입니다. 만약 CDN을 이용하면 사용자가 요청한 곳에 캐싱이 됩니다. 첫 로딩은 캐시 서버에 만약 데이터가 없다면 S3에서 불러와서 유저한테 전달하고 캐시에도 저장이 돼서 로딩 시간이 길어질 겁니다. 이후 다른 유저가 이 지역에서 해당 파일을 요청하면 S3에서 불러오는 게 아니라 자기 지역의 캐싱이 되어있는 엣지 서버에서 불러옵니다. 이러면 엄청 빨라져서 로딩 속도가 빨라집니다.
그 외에도 디도스 공격 같은 경우 알아서 차단을 해주는 등 보안적으로 이점이 있습니다. 무엇보다 메인 서버의 부하가 줄어들어 전체 사용 비용이 경감될 수 있습니다. 다만 CDN도 비용이 있어서 경우에 따라서 꼭 비용을 낮추기는 않기에 이 점을 고려해야 합니다.
CDN에 관한 자세한 설명은 링크를 참조하시면 됩니다.
CDN 생성은 아주 간단합니다. cloudFront에 들어가서
배포 생성을 누릅니다.
설정에서 정책에 Redirect를 체크. HTTP로 호출이 들어오면 HTTPS로 바꿔 달라는 의미입니다.
나머지 설정들은 default 값으로 내버려두고
배포 생성을 합니다. 만들었으면
생성된 URL이 있습니다.
<img alt="thumbnail" class="image__thumbnail"
:src='`http://d123wf46onsgyf.cloudfront.net/w140/${contentItem.imageKey}`'/>
위의 코드처럼 기존의 https://hospital.s3.ap-northeast-2.amazonaws.com을 CDN을 적용한 URL로 바꿔주면 됩니다.
5. 총정리
lambda가 서버의 부담을 줄여준다고 무조건 좋을까요? 아닙니다. 비용을 생각해야 합니다.
lambda도 저렴한 편이지만 이미지 크기를 줄일 때마다 비용이 들어갑니다.
서버가 이미지 리사이징 하는 게 비용적인 면에서 괜찮고, 서버 성능도 감당할 수 있다고 판단되면 서버에서 이미지 리사이징을 하면 됩니다. 반면 비용이 더 나가거나, 서버 성능을 더 개선해야 한다면 LAMBDA를 활용하는 것도 나쁘지 않습니다.
어떤 기술을 사용할지는 자신의 상황에 맞게끔 선택해야 합니다.