웹 성능 최적화. 많은 회사에서 우대사항으로 최적화 경험이 있는 개발자를 찾는다. 나도 최적화 관련 구글링을 통해 방법을 찾아봤고 직접 적용해 보려고도 했으나 아쉽게도 코드스플리팅(suspense, lazy loading)과 Reflow, Repaint외에는 실전에 써먹을만한 특별한 수확은 없었다. 굉장히 파편화 되어있다는 느낌?
좋은글도 발견했음!
TOAST UI
무언가 큰 그림이 필요했다. 그래서 인프런의 프론트엔드 개발자를 위한, 실전 웹 성능 최적화 part 1, 2를 모두 질렀다.
평소에는 udemy를 애용해서 그런가.. 강의가 싸다고 느껴지지는 않았다. 그래도 가격에 맞는 좋은 강의라면 상관없지.
항상 마음속 한구석에서 불안요소로 잡혀 있던 성능 최적화. 이제는 좀 친해져보자. (몸값 올릴 수 있는거야? 그런거야?)
사실 강의 듣고 자꾸 까먹어서 기록으로 좀 남겨두려고 한다. 디테일한 강의 내용보다는 아 이런식이었지 라는것만 기록해 놓으려고 한다.
사실 당연하게도, 프론트엔드는 화면을 유저에게 얼마나 빨리 배달 시키느냐가 중요하다. 한참동안 유저가 흰 화면을 보고 있거나 이미지가 버벅거리며 로딩되는 현상은 절대 좋지 않다. 당장 나만해도 화면이 좀 늦는다 싶으면 뒤로가기 눌러버린다. 뭔가가 뜨긴 떠도? 이미지가 한창동안 나 로딩중이야~ 하면서 찔끔찔끔 보여지는걸 보고 있자면 복장이 터진다. 이는 곧 유저 이탈로 이어질 것이고, 유저 이탈은 당연히 수익과 연관이 될 것이다.
그리고 요즘 우대사항을 보면 거의 70~80프로가 성능 최적화 경험이 있는 개발자를 찾고있다. 적어도 최적화에 관심을 가져봤고, 실제 어떤 개선경험을 해 봤는지 본인만의 스토리가 필요해보인다. 요즘 이력서에 수치로 본인을 어필하라 라고 하던데, 이런점에서 아마 최적화가 좋은 부분이지 않을까 싶다.
웹 최적화는 결국 로딩 성능과 렌더링 성능을 끌어올리는 것이다.
이미지의 크기를 실제로 화면에 보여줄 크기에 맞게 최적화 해 줄 필요가 있다. 강의에서 스치듯이 레티나 디스플레이 얘기가 나오는데, 관련해서는 알아서 찾아보기로 하고 아무튼 레티나 디스플레이 덕분에 이미지 크기는 보여줄 크기의 두배정도 크기가 적절하다고 한다. 또한 이미지 CDN이라는 키워드 또한 등장했다.
물리적으로 거리가 멀리 있는 콘텐츠를 받아올 때 시간이 오래 걸리기 때문에 이를 해소하고자 나온 기술이다.
최초 요청 때 데이터를 저장, 즉 캐싱 해 놓으면서 고객에게도 전송한다. 이후 요청은 모두 CDN 업체에서 지정한 컨텐츠 만료 시점까지 CDN 장비에서 컨텐츠를 가져다 쓰는 것이다. 더 이상 저 멀리 있는 데이터를 가져오는 수고를 하지 않아도 된다.
이미지 CDN
특정 함수가 js로딩 시간을 엄청나게 잡아먹는 경우가 있을 수 있다. 우리의 영원한 동반자 개발자 모드를 열어 Performance 탭으로 가보면 정말 아찔하게 생긴 그래프들이 날 맞이한다. 난 아직 이걸 보는 연습을 더 해야한다.
정말 단순 무식하게 오래 걸리는 즉시함수를 만들어보고 performance를 돌려봤다.
function App() {
const [number, setNumber] = useState(0);
useEffect(() => {
(function wowAmazingFunction() {
for (let i = 0; i < 100000; i++) {
setNumber((cur) => cur + 1);
}
})();
}, []);
return <h1>{number}</h1>;
}
export default App;
wowAmazingFunction함수가 한번에 처리되지 못하고 중간중간 엄청나게 잘리면서 실행된걸 확인할 수 있다. 함수가 너무 많은 리소스를 잡아먹어서 가비지 콜렉터가 중간중간 끊어버린 것이라고 한다.
강의 내용대로라면 Minor GC가 중간중간 끊길때마다 개입했다는게 표시되어야 하는데 흠.. 거의 끝부분에 한번 길죽하게 Minor GC가 있는거 말곤 찾을 수 없었다.
이런식으로 병목현상을 일으키는 함수를 찾아서 개선시킬 수 있다면 개선하면 되겠다.
어떤 코드 덩어리가 번들에서 얼만큼의 사이즈를 차지하고 있는지 여부를 시각적으로 보여준다. 이를 통해 초기 앱에 접근했을시에 바로 필요한 파일이 아니라면 code splitting, lazy loading을 통해서 번들 사이즈를 획기적으로 줄일수도 있게된다. 나는 react를 사용하니까 React공식문서의 코드분할을 참고했다.
React 코드 분할
아마도 이 압축은 서버쪽에서 압축하는것 같다. 그러니까 파일을 제공하는 측에서 해주는 작업같다. nginx나 서버에서 하겠지...? 아무튼 텍스트 압축을 거치면 굉장히 많은 사이즈를 줄일수 있다.
nginx Compression and Decompression
면접 질문으로도 자주 등장하는 브라우저 렌더링 과정은 대략적으로
HTML 파일을 스크립팅해서 DOM을 만들고, CSS파일을 파싱해서 CSSOM을 만들고 이 둘을 합쳐 Render Tree를 만든다.
그리고 Render Tree를 기반으로 Layout과정을 거쳐서 Paint, Composite까지 발생한다.
딱 봐도 과정이 만만치 않다. 이런 하나의 흐름이 요소가 브라우저상에서 이동하거나 애니메이션 할 때 마다 계속 발생한다는 것은 그만큼 계산해야 할게 많아진다는 뜻이되고, 이는 곧 성능 저하 이슈로 발전할 수 있다.
Reflow가 위에서 말한거처럼 이 흐름을 전부 다 다시 해버리는 작동이다. 브라우저에 부담이 많이 가겠지.
Repaint는 Layout을 생략한다. paint라는 이름에 맞게 색과 관련된 css 변경이 일어나면 발생한다.
여기서 한단계 더 나아가서 Reflow와 Repaint 모두를 피할 수도 있다. (transform, opacity등)
각각을 대표하는 css속성이 너무 많다. 다 외울수 있을리가.. 찾아가면서 해야겠지.
관련해서 opacity가 상황에 따라 Reflow와 Repaint를 모두 피하진 못한다는 글을 봤다. 포스팅하고 실험해봐야겠다.
앞서도 언급했지만 나는 react를 사용하니까 React공식문서의 코드분할을 참고했다. 아주 쉽게 적용했고, 로딩처리도 css만 좀 신경쓴다면 꽤 그럴싸하게 나온다. 다만 Lazy Loading이라는 의미답게, 최초 가지고 오는 자바스크립트의 사이즈는 줄었지만 정작 필요할 때 다운을 받는 형식이라서 필요한 순간에는 오히려 늦게 해당 파일을 받게 된다. 여기서 preloading을 할 수 있다.
방금은 lazy라더니 이제는 pre다. lazy는 최초 진입시에 자바스크립트 번들 사이즈를 최소한으로 줄이기 위함이고, pre는 앱에서 다른 인터렉티브를 통해 새로운 js가 필요할 때 어느 시점에 해당 파일을 다운로드 할지를 결정하는 것이다. 즉, 유저가 새로운 컨텐츠에 접근하고자 클릭한 이후 다운을 시작하면 그만큼 유저가 기다리는 시간이 생기니까, 어느 시점에 파일을 받아서 최대한 빨리 유저앞에 대령할 것이냐를 결정한다.
정말이지 생각 못했다. 오마이갓
아무튼 이러한 방법들로는 유저가 컨텐츠에 접근하려고 버튼위에 마우스를 올렸을 때나, 최초 페이지의 마운트가 끝났을 때 같은 경우에 preloading을 할 수 있다.
// 마운트 이후에 preloading
const LazyComponent = lazy(()=> import('./somethingLazyComp'))
// ...
useEffect(() => {
const comp = import('./somethingLazyComp')
}, [])
이는 이미지 preloading에서도 활용된다. 이미지의 캐싱여부도 중요하다. 캐싱되어있지 않다면 매번 새로 불러오기 때문에 프리로딩 한 이유가 없어진다.
// 마운트 이후에 preloading
const LazyComponent = lazy(()=> import('./somethingLazyComp'))
// ...
useEffect(() => {
const comp = import('./somethingLazyComp')
const img = new Image()
img.src = 'amazing-img-source'
}, [])
강의 1편을 다 봤다.(사실 복습임) 좀 짧았지만 정말 재밌게 봤고, 역시 내가 모르는 세계는 너무도 많다. 2편은 강의 내용이 훨씬 길다. 무엇을 배우게 될지 벌써 기대돼!