XOXO
는 이미지 위주의 SNS 서비스이기 때문에, 이미지를 최적화하는 것에 있어서 많은 신경을 써야만 했습니다.
또한 각 피드와 포스팅에 대한 썸네일 리스트를 보여주는 화면이 존재했는데, 해당 썸네일의 원본 이미지를 보여주는 것은 비효율 적이라 생각하였기 때문에, 각 피드와 포스팅의 썸네일을 따로 Object Storage
에 저장해야 하는 상황을 마주치게 되었습니다.
→ 그러던 중 CDN에 대한 것을 찾아보라는 멘토님의 조언이 있었고, 이후 CDN과 Image Optimizer에 대해 학습할 수 있었습니다.
CDN
Content Delivery Network 서버와 사용자 사이의 물리적 거리를 줄여 콘텐츠 로딩에 소요되는 시간을 최소화 시킨다.Image CDN
이미지에 특화된 CDN 이미지를 사용자에게 보내기 전에 특정 형태로 가공하거나 혹은 사이즈를 줄인다거나 이미지 포멧을 바꾼다거나 하는 등의 처리과정을 거쳐 사용자에게 이미지를 전달해주는 것 일반적인 이미지 CDN에서 제공하는 주소는 다음과 같이 이루어져 있다. 🔗http://cdn.image.com?src={**image_src**}&width=240&height=240
이와 같이, 이미지 CDN 서버 주소에 쿼리스트링으로 가져올 이미지의 주소, 그리고 필요에 따라 변경하고자 하는 형태를 명시해 줌으로써, 내가 명시한 이미지를 변환된 상태로 받아올 수 있다.Naver Cloud Platform
에서 제공하는 Image CDN
서비스이다!
https://api.ncloud-docs.com/docs/common-objectstorageapi-objectstorageapi
Access Key
와 Secret Key
의 한 쌍으로 구성되어 있으며, 이는 네이버 클라우드 플랫폼 계정 생성 시 자동으로 1개가 생성된다. 인증키 관리는 마이페이지 > 계정 관리 > 인증키 관리 에서 할 수 있다.리전 | 리전 이름 | 호출 도메인 |
---|---|---|
한국 | kr-standard | https://kr.object.ncloudstorage.com |
미국서부(New) | us-standard | https://us.object.ncloudstorage.com |
싱가포르(New) | sg-standard | https://sg.object.ncloudstorage.com |
일본(New) | jp-standard | https://jp.object.ncloudstorage.com |
독일(New) | de-standard | https://de.object.ncloudstorage.com |
AWS
S3
API를 이용할 예정const upload = async (file: Express.Multer.File) => {
const region = process.env.NCLOUD_REGION;
const endpoint = process.env.NCLOUD_END_POINT;
const bucketName = process.env.NCLOUD_BUCKET_NAME;
const accessKey = process.env.NCLOUD_ACCESS_KEY;
const secretKey = process.env.NCLOUD_SECRET_KEY;
const fileName = `${uuidv4()}.${file.mimetype.match(/(?<=\/).*$/)}`;
// S3 오브젝트 생성
const S3 = new AWS.S3({
endpoint,
region,
credentials: {
accessKeyId: accessKey,
secretAccessKey: secretKey,
},
});
// 오브젝트 삽입
await S3.putObject({
Bucket: bucketName,
Key: fileName,
ACL: 'public-read',
Body: file.buffer,
ContentType: file.mimetype,
}).promise();
return fileName;
};
@Injectable()
export class ImageService {
async uploadImage(files: Array<Express.Multer.File>) {
const imagePathList = await Promise.all(
files.map((file: Express.Multer.File) => {
return upload(file);
}),
);
return imagePathList;
}
}
Project 생성
클릭Object Storage
를 선택한다.우리가 이미지를 변환하고 싶은 룰을 생성한다.
util/imageQuery.ts
...
const getThumbSize = (type: string, windowWidth: number) => {
const level = Math.floor(windowWidth / 100)
switch (type) {
case 'postingLQ' :
if (level >= 5) return 50
if (level >= 4) return 40
return 30
case 'posting' :
if (level >= 5) return 500
if (level >= 4) return 400
return 300
case 'feed' :
if (level >= 4) return 240
if (level >= 3) return 180
return 120
case 'feeds' :
if (level >= 4) return 120
if (level >= 3) return 90
return 40
default :
return 300
}
}
...
각 사용자의 window.innerWidth
에 따라 다른 사이즈의 크기를 반환할 수 있도록 util
을 구현한다. 이후에 해당 사이즈를 CDN주소/ObjectStorage파일경로/쿼리
로 요청하면 된다!const getQueryString = (type: string) => {
const windowWidth = window.innerWidth
const size = getThumbSize(type, windowWidth)
return `?type=m&w=${size}&h=${size}`
}
const getPostingThumbUrl = (url: string) => {
return `${process.env.REACT_APP_CDN_URI as string}/` + url + `${getQueryString('posting')}`
}
위의 getThumbSize
를 통해 받아온 이미지의 사이즈를 쿼리스트링으로 만든 뒤, 우리가 사용하고 있는 CDN 주소와 붙여 사용하면 정상적으로 변환된 이미지들을 실시간으로 불러올 수 있다.화면 크기와 상관없이, 원본 이미지(약 478kb)를 받아온다.
화면 크기에 알맞게 리사이징된 이미지(약 7kb)를 받아온다.
적용 전
적용 후
비용 절감 : 478kb → 7kb (약 60배 개선)
다운로드 속도 개선 : 64.23ms → 2.45ms (약 30배 개선)