2021. 11. 12. 22:05ㆍ공부내용 공유하기
작년 12월
연말의 어느 날. 꽤나 한가한 일정을 보내던 나는 평소 눈엣가시처럼 여기던 자체 서비스 리팩토링/최적화를 하고 싶다는 의견을 대표에게 피력했고, 1주일의 스프린트를 할당받아 레거시 코드에 대한 최적화 및 리팩토링을 진행했다.
길지 않은 시간동안 부족한 실력으로 해내려다 보니 부족한 점이 많았지만, 나름 뿌듯한 1주일의 기억으로 아직도 남아있다.
언젠간 써야지 써야지 해놓고 기록만 해뒀던 내용을 이제야 꺼내본다.
문제점
큰 문제가 3개가 있었다.
1. 번들 사이즈 무식하게 큼.
당시 자사의 서비스 웹은 CRA를 통해 제작된 페이지로, 최적화따윈 되지 않은 통짜 CSR 그 자체였다.
청크파일 용량이 총 1.8mb에 달했는데, 네트워크 환경이 느린 곳에서는 페이지 로드가 심각할 정도로 느렸다.
아무것도 모르는 초짜 둘이서 주먹구구식으로 개발을 했으니... 구현하기도 바빴고, 처음 배우는 기술들이 많다보니 서드파티도 마구잡이로 붙이고... 정말 엉망이 이런 엉망이 없었다.
2. 이미지 에셋 크기도 엄청나게 큼
export 하면서 깨지지 않게 하려다보니 png 파일들을 @2x 혹은 @3x 로 받기 일수였다.
메인 배너 이미지 용량이 6mb였나....? 어처구니 없이 큰 용량이었다.
3. 컴포넌트 파편화
버튼 컴포넌트를 페이지별로 만들고 있었다;;;;
변명을 좀 해보자면, 그 당시 웹사이트의 디자인 역시 정리가 되지 않은 상태여서 버튼 컴포넌트의 일관성이나 디자인 규칙등이 거의 없었다. 버튼의 규칙이나 일관성에 대해 정의해 달라고 부탁하기엔 너무 경험이 없었고, 큰 프로젝트를 해본 경험도 없다보니 엉망이라는걸 나중에 자각한 것 같다.
지금이야 스타일 가이드와 함께 공용 버튼 컴포넌트 하나 or 두 개로 앱 내 모든 버튼 컴포넌트를 커버하지만, 저 당시에는 저런 식으로 만든 컴포넌트를 또 만들고 또 만들고 하면서 중복된 컴포넌트들이 많이 쌓였다.
훗날 기술부채로 돌아와 유지보수를 하며 다 걷어내고 고생 ㅠㅠ
해결책
1. 초기 로딩속도 늘리기
TreeShaking
사용하지 않는 라이브러리 걷어내고, e6 모듈 import를 통해 번들사이즈 최적화.
사용중인 라이브러리 중에서도, 용량을 최적화 한 경량 버전의 라이브러리들이 있어서 최대한 최적화를 했다.
Dynamic Import
ploty라는 라이브러리는 특정 페이지의 특정 상황에서 사용중인데 용량도 무지하게 큰 관계로 dynamic import를 통해 초기 번들에서 제외했다.
청크 스플릿 및 빌드파일 Gzip 압축
craco.config.js
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin;
const CompressionPlugin = require("compression-webpack-plugin");
module.exports = {
babel: {
plugins: ["babel-plugin-styled-components", "syntax-dynamic-import"],
},
webpack: {
configure: {
optimization: {
splitChunks: {
cacheGroups: {
react: {
chunks: "initial",
name: "react",
test: /node_modules\/react/,
},
lottieWeb: {
chunks: "all",
name: "lottieWeb",
test: /node_modules\/lottie/,
},
},
},
},
},
plugins: [
new BundleAnalyzerPlugin(),
new CompressionPlugin()
],
},
};
아래는 물망에 올랐다가 여러 이유로 기각된 아이디어들
React-Snap을 활용한 메인페이지 한정 Static 제공 -> 클라이언트 로드 후 hydrate로직 추가(어설픈 next.js흉내인 것인가...)
큰 이미지들은 별도의 CDN으로 이관
2. 이미지 에셋 크기 줄이기 + LazyLoading
LazyLoad 이미지 컴포넌트 구현
intersection Observer를 활용한 이미지 Lazyload 컴포넌트 구현
이미지 포맷 PNG -> JPEG
포맷 변경만으로도 용량이 기하급수적으로 줄었다.
같은 래스터 이미지라도 손실 vs 무손실 포맷 여부에 따라 용량이 엄청나게 다르다는걸 이때 처음 알았다.
3. 컴포넌트 파편화
중복되어 공통으로 사용되는 컴포넌트들을 발굴하여 묶을 수 있는 코드들은 최대한 묶었다.
최적화 작업을 하면서 지속적인 번들 사이즈 체크 및 벤치마크를 시행했다.
벤치마크
*각각 3회 테스트 후 평균치 산출
**테스트 환경은 disabled cache, fast 3g(network throttling)
1월 4일(최적화 첫날)
청크파일 용량 1.3mb + 456kb + 47.5kb = 1803.5kb
번들 다운로드 완료 및 렌더 시작 : 12.8초
페이지 로드 완료: 38초
1월 6일(최적화 셋째날)
청크파일 용량 94kb + 69kb + 39.5kb + 11kb = 223kb
번들 다운로드 완료 및 렌더 시작: 4.8초
페이지 로드 완료: 17초
1월 8일(최적화 마지막 날)
청크파일 용량 108kb + 69kb + 39.5kb + 14kb = 234kb
(페이지 이동시 깜빡임을 유발하는 과도한 코드 스플릿이 있어 일부 보완했다)
번들 다운로드 완료 및 렌더 시작: 4.3초
페이지 로드 완료: 14초
Fast 3G 환경에서도 크게 느리지 않을 정도의 퍼포먼스로 개선되며 그렇게 무사히 막을 내렸다.
결론 :
1. 애초에 만들때 잘 만들자.
2. 개발자에게 성능 개선이란 정말 뿌듯한 일.