- Today
- Total
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 그림으로 배우는 http&network
- 이터러블
- C++
- map
- JavaScript
- http
- 백준
- 알고리즘
- Java Script
- git error
- 상태관리
- 프론트엔드
- 에러처리
- es6
- get
- 모던 자바스크립트
- 자바스크립트
- 비동기
- 모던 자바스크립트 deep dive
- React
- 백준 실버
- html
- 네트워크
- async
- 웹
- git
- deep dive
- Angular
- error
- js
sharingStorage
WebP 변환을 통한 이미지최적화로 LCP 성능개선과 S3 리소스 절감 본문
서론
최근 성능에 대한 관심을 보이면서 몇몇 글모임의 상세페이지에서만 유독 좋지 않은 LCP지표를 보이고 있는 것을 확인하였습니다. 이것에 대한 원인을 유추하던 중 presigned url을 활용하여 서비스를 운영하며 10MB에 육박하는 대용량 이미지가 아무런 전처리나 최적화 작업 없이 S3에 업로드되었고 이것이 곧 LCP의 Load Time과 전체 Network 요청 리소스에 큰 영향을 주고있는 것을 확인하였습니다.
이러한 문제를 NEXT JS에서는 모든 이미지를 WebP로 변환하는 과정을 통해 이미지 최적화를 진행한다는 것에서 착안하여 업로드 과정에서 이미지 최적화를 진행하여 개선해보려고 합니다.
위에서 말씀드린 성능에 대해 조금 더 자세히 확인해보자면 아래와 같이 유독 LCP점수만 낮게 측정된 것을 확인할 수 있었고 이런 이슈는 모든 글 페이지에서 발생하는 것이 아닌 몇몇 특정 글이나 글모임에서 발생하였습니다.
개발자 도구의 Network tab을 확인하여 리소스에 대한 지표를 확인해본 결과 전체 네트워크 요청중에 대용량 이미지가 size면으로나 Time면으로나 가장 큰 리소스를 차지하고 있었고 이것이 LCP에 영향을 주고있다고 판단하였습니다. 실제로 해당 네트워크 요청의 waterfall을 확인해보면 Content Download에 많은 리소스를 사용하고 있고 이 문제를 업로드 과정에서의 이미지 최적화을 통해 해결해보려고 합니다.
LCP를 개선할 수 있는 방법
LCP 개선의 기회를 파악하기 위해서는 가장 먼저 두 가지 요청을 살펴보아야합니다.
1. 초기 HTML 문서
2. LCP 리소스 (존재하는 경우)
페이지의 다른 요청이 LCP에 존재할 수 있지만 이 두 요청을 확인하는 것은 페이지가 LCP에 최적화되어있는지를 확인할 수 있는 좋은 기회가 될 것입니다. (특히 LCP리소스의 시작과 종료 시간)
LCP요소를 식별하려면 개발자도구를 사용하여 아래와 같은 DIGNOSTICS 지표를 확인하면 아래와 같은 세부 지표를 확인할 수 있습니다.
아래 이미지는 LCP요소를 렌더링하는데 필요한 일반적인 페이지 로드의 네트워크 폭포식 다이어그램입니다.
위 이미지에서 보시다 싶히 LCP에는 총 4가지 지표가 있습니다.
- TTFB : 로드 시작부터 HTML문서 응답 첫 바이트 수신까지 걸린 시간
- Load Delay : TTFB이후 LCP를 로드하기 시작하는 시점 사이 걸린 시간
- Load Time : LCP 리소스를 로드하는데 걸린 시간
- Render Delay : LCP 리소스 렌더링 시간
위 네가지 지표에 대한 시간을 모두 합치면 LCP 시간이 됩니다.
이러한 지표와 core web vital 대한 설명은 아래 링크에 더 자세히 설명되어 있으니 궁금하신 분들은 확인해보시길 바랍니다!
https://anstrengung-jh.tistory.com/107
LCP 최적화 유의해야할 점
LCP를 최적화할 때는 이러한 하위 부분을 각각 신경써서 최적화하는 것이 좋지만 이것이 모든 항목에 대한 최적화로 이어져야합니다.
예를 들면 LoadTime을 줄이기 위해 LoadDelay 시간이 늘어난다면 이것은 LCP개선이 아닌 절약된 시간을 다른 부분으로 리소스를 이전시키는 것과 같아 개선된 것이라고 볼 수 없습니다.
이미지를 WebP로 변환하면서 알아야할 것들
이미지의 확장자를 변경하기 위해서 저는 File을 canvas에 복사하여 Blob객체를 활용해 webp 확장자로 변환시키는 방법을 선택하였습니다.
Blob객체란?
파일 형식을 반드시 따를 필요 없는 이진 형식의 데이터 덩어리입니다.
해당 타입을 통해 큰 용량을 다룰 수 있는 파일류의 불변하는 미가공 데이터이며 텍스트와 이진 데이터의 형태로 읽을 수 있습니다.
File 객체는 Blob에 기반한 인터페이스타입으로 사용자 시스템의 파일(수정시간, 파일명 등의 메타데이터)을 지원히기 위해 Blob을 상속해 기능을 확장한 것입니다.
즉 File 객체는 Blob의 한 종류로 Blob을 사용할 수 있는 모든 맥락에서 사용할 수 있는 객체인 것이며 아래와 같은 메서드를 상속받아 사용할 수 있습니다.
File객체와 Blob객체 눈으로 확인하기
실제로 File객체에서 File시스템을 위한 메타데이터가 존재하고 Blob의 WebP타입으로 변환하였을 때 이것들이 사라지는 것을 볼 수 있습니다.
Canvas API
js와 html 요소를 통해 주로 2D 그래픽을 그리는 수단을 제공하는 API입니다.
데이터 시각화, 사진 조작 및 비디오 처리에 사용할 수 있음
getContext
- 요소의 컨텍스트를 가져오는 메소드
- 렌더될 drawing요소를 가져온다.
- 기본적으로 Canvas는 비어있어서 해당 스크립트로 canvas 컨텍스트를 가져오고 그리기 작업을해야 컨텐츠를 표시할 수 있습니다.
(canvas element는 width와 height 두가지 attribute밖에 없고 둘다 optional)
이미지를 WebP로 변환하는 함수
저는 이미지를 WebP 확장자로 변경하기 위해 canvas API를 사용하여 해당 이미지를 캔버스에 그린 후 이를 toBlob메서드를 사용하여 원하는 타입 (webp)로 변경하는 함수를 만들어 이미지를 업로드하는 과정에 적용하였습니다.
export const convertImageToWebP = (file: Blob) => {
return new Promise((resolve, reject) => {
//이미지 객체 생성
const img = new Image();
//파일 객체를 나타내는 url 저장 후 이미지에 복사
const blobUrl = URL.createObjectURL(file);
img.src = blobUrl;
//이미지가 DOM에 세팅된 후 진행되는 canvas 복사 로직
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx?.drawImage(img, 0, 0);
//canvas 객체를 toBlob메소드를 통해 webp 확장자 변환
canvas.toBlob(async (blob) => {
if (blob) {
resolve(blob);
//메모리에 참조하고 있는 Blob객체를 지워 명시적으로 해제시키는 과정을 통해 js gc동작하도록 함
URL.revokeObjectURL(blobUrl);
} else {
reject(new Error('Image conversion failed'));
}
}, 'image/webp');
};
img.onerror = reject;
});
};
지표를 통한 결과 확인
S3 비용 절감
한가지 감안해서 보셔야하는 점은 서버측에서 presigned URL 로직 활용에 있어서 이미지 타입을 미리 인지할 수 없어서 모든 이미지를 jpg로 변환하고 있다고 합니다.
S3에서 똑같은 이미지를 올렸을 때 아래와 같은 용량의 차이를 볼 수 있습니다.
첫번째 이미지는 거의 99% 정도의 용량을 감소시켰고 두번째 이미지는 약 80%가량 용량을 감소시켰습니다. 다른 몇가지 이미지로도 더 테스트해보면 60% 정도의 용량을 감소시키는 것까지 볼 수 있으며 이러한 성능개선 지점의 차이는 비트레이트와 해상도의 차이에 있습니다.
비트레이트란 이미지가 저장할 때 초당 처리하는 데이터 양이고 고화질 이미지일 수록 더 많은 비트로 데이터를 표현합니다.
고비트레이트 이미지(고화질 이미지)는 일반적으로 저화질일 때에 비해 더 많은 중복 데이터를 가지고 있으므로 이는 더 큰 성능개선으로 이어질 수 있습니다.
실제로 WebP는 압축방식에서 예측코딩을 사용하는데 이는 픽셀의 인접한 블록의 값을 사용하여 블록의 값을 예측한 다음 그 차이만 인코딩하는 방식입니다. 이렇게 중복데이터를 빼서 압축효율을 높이는 방식이라 비슷한 색상이 반복적으로 나타나는 고화질 이미지는 더 큰 성능개선을 보일 수 있습니다.
LCP 세부 지표
Load Time이 약 300ms (약 35%) 단축되었고 Render Delay는 살짝 올라간 것을 볼 수 있지만 S3 리소스 감소와 Load Time 감소에 비해 작은 수준이라고 생각됩니다.
Nerwork 요청 지연 및 리소스
고화질 이미지 기준 Network 요청 사이즈 및 시간이 아래와 같이 감소하였습니다.
결론
NEXT JS의 이미지 최적화 방법에서 착안하여 image를 WebP로 변경하여 최적화하는 로직을 구성해보았습니다. 사실 당근 인턴면접에서 DOMContentResource를 41MB -> 8MB로 개선했다고 했는데(현재는 여러 성능개선까지 거쳐 1.8MB) 이것도 너무 크다는 말씀을 해주셔서 시작된 범인 찾기 중 S3에 이런 대용량 이미지가 아무런 처리 없이 들어가도 괜찮은 것인가? 에 대한 고민을 하였고 이것이 곧 LCP성능과도 연결되어 있다는 것을 확인하여 이를 동시에 해결해볼 수 있던 좋은 기회였습니다.
최근 여러가지 성능개선이나 SEO개선을 진행하면서 NEXT JS가 정말 편한 기능들을 잘 제공해주고 있다는 생각이 드네요. 실제로 이런것들을 직접 느낀 후 사용하고 싶었고 이제 곧 시작할 NEXTJS 프로젝트가 너무 기대가 되네요!!
Reference
'Front-End > 성능개선' 카테고리의 다른 글
성능개선기1 - 올바른 이미지 포멧사용의 중요성 & code splitting (2) | 2024.10.03 |
---|---|
성능개선 (core-web-vitals & Lighthouse) (2) | 2024.09.24 |