[Optimization] ALOG 웹 서비스 성능 개선 - LCP 최적화

hyeondoonge·2023년 11월 1일
2
post-thumbnail

개요

최근 성능 최적화와 관련된 학습들을 했다. 이를 통해 사용자가 좀 더 좋은 환경에서 서비스를 이용할 수 있도록 함으로써 UX를 향상하고, 결과적으로 경쟁력있는 서비스를 만들 수 있다고 생각해 필요성을 느꼈다.
그래서 내가 간간이 유지보수하고있는 ALOG 서비스에 성능 최적화를 적용해보고자했다.

성능측정은 PageSpeed Insights에서 진행했다. 측정할 때 마다 결과가 조금씩 달랐기 때문에 5번 분석 결과(+5번)들 중 자주 나온 결과를 기준으로 성능 개선 전과 후를 비교했다.

성능 개선 전 측정 결과

Main Page

User Page

Write Page

LCP를 보면 모든 페이지에 대해 2초 후반대이며 성능 점수가 50점대를 왔다갔다하는 것을 볼 수 있다.

두 값을 이제 개선해보도록하자~!

해결 방안

LCP가 무엇인지 이해하고 어떠한 요인으로 LCP 성능이 저하되는지 이해하면, 해결방안을 찾을 수 있을 것이다.

최대 콘텐츠 렌더링 시간 (LCP) 측정항목은 페이지가 처음 로드하기 시작한 시점을 기준으로 표시 영역 내에 표시되는 가장 큰 이미지 또는 텍스트 블록의 렌더링 시간을 보고합니다.

따라서 LCP를 최적화한다는 건 페이지에서 사용자에게 보이는 가장 중요한 요소라고도 할 수 있는 걸 더 빠른 시점에 보여준다는 것이다. 아래와 같이 페이지가 로드되기부터 해당 요소를 그려내기까지 크게 4가지 작업이 발생하는데 이 시간을 단축하면된다.

LCP 최적화 방안에 대한 아이디어는 공식문서에 친절히 설명하고있다. 이를 통해 여러 방법으로 개선할 수 있고, 운영하는 서비스에 따라 방법이 개선될수도, 그러지 않을 수도 있다는 것을 알 수 있다.

적용해볼 수 있는 방안은 다음과 같다.

  1. 리소스 로드 시간 단축: JS 번들, 최적 이미지 크기, 이미지 압축, CDN 등
  2. 리소스 로드 지연 제거: HTML에서 LCP 리소스 검색 가능하게 하기, LCP 리소스 우선순위 높이기

이번에는 JS번들을 줄이기위해 Code Splitting 시도해봤다. 왜냐하면 Alog 서비스에 적용해볼만한 기능이 있어 성능 개선에 도움이 되는 부분이 있을 것이라 기대했고, 빠르게 적용하여 개선하고싶었기 때문이다. 추후에 다른 방법들도 적용해볼 계획이 있다.

목표 설정하기

Code Splitting 기법을 적용하여 LCP 점수 및 성능점수를 향상한다. 단 기능에 성능 향상이 있다하더라도, 영향을 받는 다른 기능에서 눈에 띄는 성능저하가 없도록 해야한다.

적용하기

Code Splitting 달성하는 방법 중 지연로딩을 적용해보자.

작은 컴포넌트 단위로 적용하면 오히려 HTTP 통신 비용 때문에 성능이 저하될 수 있을 것이라고 생각해 우선 페이지 단위로 적용했다. 그래서 사용자 관심사를 기준으로 두어 글 작성자, 글 독자를 구분했고 따라서 글 수정(EditPost) 및 등록(WritePost) 컴포넌트에만 지연로딩을 적용했다.

컴포넌트 지연로딩은 React에서 제공하는 Lazy API를 통해 빠르게 적용할 수 있다.
사용시 주의할 것은 지연로딩을 함으로써 비동기적으로 분리된 컴포넌트를 로드하기 때문에, 로드 중일 때의 처리가 필요하다. 이를 위해서는 React에서 제공하는 Suspense를 조합해서 사용할 수 있다.

Suspense사용 시 유의할 점은, 아래 코드를 보면 Suspense로 전체 앱을 감싸준 상태이기 때문에 하위 컴포넌트에서 부분적으로 로딩 처리가 필요한 부분을 꼼꼼히 처리해줘야한다는 것이다. 현재 나의 코드 상에서는 Loading 렌더링이 필요한 기능을 모두 처리하고 있어 문제없이 동작하는 것을 확인한 후 적용했다.

(코드는 실제 서비스에 적용한 필자의 사례이며 lazy가 적용된 부분, Suspense 위주로 코드를 봐도 충분하다.)

import Home from './page/Home';
import ReadPost from './page/ReadPost';
import Error from './page/Error';
import { Switch, Route, BrowserRouter as Router } from 'react-router-dom';
import { ThemeContextProvider } from './contexts/ThemeContext';
import { ModalContextProvider } from './contexts/ModalContext';
import UserContext, { UserContextProvider } from './contexts/UserContext';
import SignUp from './page/SignUp';
import { Suspense, lazy, useContext, useEffect } from 'react';
import UserHome from './page/UserHome';
import Loading from './common/Loading';
// ⭐️ 지연로딩적용
const WritePost = lazy(() => import('./page/WritePost'));
const EditPost = lazy(() => import('./page/EditPost'));

const MyComponent = ({ children }) => {
	//...
  return <>{children}</>;
};

export default function App() {
  return (
    // ...
          <Suspense
            fallback={
              <div
                style={{
                  height: '80vh',
                  display: 'flex',
                  flexDirection: 'column',
                  justifyContent: 'center',
                  alignItems: 'center'
                }}
              >
                <Loading />
              </div>
            }
          >
            <MyComponent>
              <Router>
                <Switch>
                  <Route path="/" exact component={Home} />
                  <Route path="/home/:ownerId" exact component={UserHome} />
                  <Route path="/write" exact component={WritePost} /> // ⭐️ 지연로딩 대상
                  <Route path="/edit" exact component={EditPost} /> // ⭐️ 지연로딩 대상
                  <Route path="/post" exact component={ReadPost} />
                  <Route path="/signup" exact component={SignUp} />
                  <Route path="/*" exact component={Error} />
                </Switch>
              </Router>
            </MyComponent>
          </Suspense>
    // ...
  );
}

측정 결과

번들 크기 (1.4kb 감소)

초기 로딩될 번들을 비교하면 js는 413kb → 411kb로 약 1.4kb가 줄어든 것을 확인할 수 있다.

과연 이러한 결과가 실제 사용자 경험 개선에 얼마나 영향을 줄 수 있을까?

Main Page (0.4초 개선, 성능점수 6점 향상)

User Page (0.5초 개선, 성능점수 11점 향상)

Write Page (0.2초 개선, 성능점수 2점 향상)

WritePost, EditPost 페이지 컴포넌트를 메인 번들로부터 덜어낸 결과 0.5초의 속도 개선과 10점 정도의 성능 점수 향상이 있었다. 하지만 이러한 최적화를 점차 적용하고, 테스트해보면서 적합한 형태의 최적화를 갖춰나간다면 지금은 0.5초 정도의 개선이 있지만 1초, 2초까지 더 빠르게 로드 속도를 향상할 수 있을 것이라 기대한다.

WritePost 페이지의 경우 더 많은 LCP시간이 소요될까 우려했었다. 하지만 결과에서 보이듯 0.2초가 개선이 됐고, 오히려 떨어질 때도 있었다. 이는 네트워크 상황 등 여러 요인에 따라 매번 다르게 성능결과가 측정되기 때문인 것임을 알 수 있었다. 따라서 WritePost 페이지 렌더링시 많게는 0.2초 정도의 향상과 저하가 모두 일어날 수 있었기 때문에 무조건 성능저하가 발생하는 것은 아니라고 할 수 있겠다.

결론

이와 같이 최적화 적용을 통해 WritePost, EditPost 페이지에 성능 저하가 무조건 발생하지 않고, 해당 페이지들을 제외한 페이지들에는 확실한 속도 개선이 있었다는 점에서 속도 개선을 성공적으로 했다고 생각한다.

하지만 이를 통해 깨달은 것은 무조건 lazy를 많이 적용한다고 해서 장점만 있는 것은 아니라는 것이다.

lazy적용을 위한 설계 + 테스트 + 성능 측정 비용이 적게 드는 작업이 아니라는 것과, lazy로드를 하게되면 이후 http 통신 비용을 독립적으로 사용해서 해당 모듈을 가져오기 때문에 오히려 성능 저하를 일으킬 수 있는 단점을 가진다는 것을 느꼈다.

따라서 무조건적으로 lazy를 적용하는게 아니라, lazy를 적용할 수 있는 규모를 계산해보고 성능 개선에 효과가 있다면 이행해도 좋을 것 같다고 생각한다.

0개의 댓글

관련 채용 정보