성능 베이스 캠프

김윤진·2022년 9월 5일
0

우아한테크코스

목록 보기
1/8

https://github.com/woowacourse/perf-basecamp/pull/34

성능 최적화에서 가장 중요한 것은 빌드라고 생각한다.

앱이 커질수록 그에 상응하는 코드가 늘어날 확률이 높아진다.

빌드 파일의 용량이 커질수록 앱의 속도는 저하하게 된다. 왜냐 앱을 실행할 때 빌드한 파일을 통신을 통해 받아오는데 파일 용량이 클수록 느릴 수 밖에 없다. 왜냐 통신을 통해 받아오는 파일의 크키가 커진다면 이 파일을 쪼개서 서버에서 보내준다. 파일을 쪼개는 시간과 이 쪼갠 파일을 다시 합치는 시간이 추가되기 때문에 파일의 용량이 커질수록 속도가 느려지는 것이다.

많은 프론트엔드 영역에서 사용하는 빌드 모듈은 webpack이다. webpack 사용법을 잘 알아야 빌드한 파일의 크기가 줄어들지 않을까?

요청 크기 줄이기

소스코드 크기 줄이기

webpack 4버전에서는 terser-webpack-plugin 이 포함되어 있지 않았다.

  • terser-webpack-pluginterser 을 사용해서 자바스크립트를 축소한다.
  • terser 는 자바스크립트를 파싱, 미니마이즈, 압축하는 툴킷이다.

webpack 5버전으로 가면서 포함되므로 추가로 설치할 필요는 없다.

참고로 terser-webpack-plugin 에서 쓸만한 기능 중에 빌드할 때 console을 지우는 옵션도 있다.

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
						drop_console: true
					},
        },
      }),
    ],
  },
};

TerserWebpackPlugin | webpack

compression-webpack-plugin을 활용해서 번들파일의 압축버전을 만들 수 있습니다. 이는 CloudFront에서도 가능하지만 CloudFront에서 하는 일이 많기에 webpack에서 하는 것이 더 좋은 방법이라고 생각했습니다.

CompressionWebpackPlugin | 웹팩

이미지 크기 줄이기

이미지 포맷은 요즘 여러가지가 있습니다. 최근에 나온 webp, 기본 이미지 사이즈도 작아서 많이 사용합니다. 하지만 webp를 지원하지 않는 브라우저는 아직 존재합니다.

그래서 webp 이미지를 사용못할 경우에 대비해서 jpg 포맷도 추가해준다. 이를 HTML tag인 picture 태그에서 쉽게 적용할 수 있다. 그리고 뷰포트에 대한 이미지 사이즈도 정할 수 있다. 그러나 사이즈와 포맷이 많아진다면 source 태그가 많아질 수 밖에 없다. 그래서 컴포넌트를 만들어서 이미지를 보여주는 것이 좋다.

// before
<picture>
  <source type="image/webp" media="(min-width:1920px)" srcSet={heroImage1920} />
  <source type="image/webp" media="(min-width:1366px)" srcSet={heroImage1366} />
  <source type="image/webp" media="(min-width:768px)" srcSet={heroImage768} />
  <source type="image/jpg" media="(min-width:1920px)" srcSet={heroImageJpg1920} />
  <source type="image/jpg" media="(min-width:1366px)" srcSet={heroImageJpg1366} />
  <source type="image/jpg" media="(min-width:768px)" srcSet={heroImageJpg768} />
  <img className={styles.heroImage} src={heroImageJpg1920} alt="hero image" />
</picture>

// after
<Picture imageName={'hero'} imageStyle={'style'} />

// Picture Component
const Picture = ({ imageName, imageStyle }) => {
	return (
		<picture>
		  <source type="image/webp" media="(min-width:1920px)" srcSet={`/public/assets/images/${imageName}-1920.webp`} />
		  <source type="image/webp" media="(min-width:1366px)" srcSet={`/public/assets/images/${imageName}-1366.webp`} />
		  <source type="image/webp" media="(min-width:768px)" srcSet={`/public/assets/images/${imageName}-768.webp`} />
		  <source type="image/jpg" media="(min-width:1920px)" srcSet={`/public/assets/images/${imageName}-1920.jpg`} />
		  <source type="image/jpg" media="(min-width:1366px)" srcSet={`/public/assets/images/${imageName}-1366.jpg`} />
		  <source type="image/jpg" media="(min-width:768px)" srcSet={`/public/assets/images/${imageName}-768.jpg`} />
			<img className={imageStyle} src={`/public/assets/images/${imageName}-1920.jpg`} alt={`${imageName} image`} />
		</picture>
)

import 구문 대신 scrSet에 경로로 집어넣게 되면 문제가 발생한다. fil-loader가 import로 가져온 이미지말고 img tag안에 경로로 넣은 이미지 파일들은 사용하지 않는것이라고 판단하고 빌드할 때 빌드에 포함하지 않는다.

이를 해결하기 위해 src 하위에 있는 assets 폴더를 public 폴더 하위로 옮기고 CopyWebpackPlugin을 추가하고

new CopyWebpackPlugin({
  patterns: [{ from: './public', to: './public' }]
}),

public 폴더를 넣어주면 public에 있는 asset 폴더도 함께 빌드되기에 이미지 파일이 삭제되는 것을 방지할 수 있고 배포 후 이미지 경로 문제도 발생하지 않는다.

CopyWebpackPlugin | webpack

CloudFront에서 Lambda edge에서 이미지 resize하는 함수를 만들어서 query param으로 이미지 크기를 정할 수도 있다.

[AWS] CloudFront Lambda@edge 를 이용한 이미지 리사이징
HTML IMG의 srcset과 sizes 속성(updated)

Webpack Bundle Analyzer

stat : 변환 전의 파일 크기이다. webpack에서 input 파일 사이즈이다.

parsed : 플러그인등의 코드 축소 후 output 파일 사이즈이다.

gzip : gzip 압축 후 파일 사이즈이다.

webpack-bundle-analyzer/README.md at master · webpack-contrib/webpack-bundle-analyzer


필요한 것만 요청하기

페이지별 리소스 분리

react 제공하는 lazy를 활용하여 lazy import를 통해 페이지별 리소스를 분리할 수 있다.

const Search = lazy(() => import('./pages/Search/Search'));

webpack에서 Tree Shaking을 하기 위해 package.json에 sideEffects : false를 추가하면 Tree Shaking이 된다. 만약 Tree Shaking이 되면 안 되는 파일이 있다면 false 대신 배열에 파일 이름을 추가하면 해당 파일 제외하고 Tree Shaking이 된다. Tree Shaking은 사용하지 않는 파일을 빌드할 때 빌드 항목에서 빼는 것이다. 그래서 한 파일에서 하나의 함수를 import해서 사용하고 다른 수많은 함수를 사용하지 않는더라도 해당하는 파일은 Tree Shaking이 되지 않는다. 그래서 webpack의 Tree Shaking을 효과적으로 활용하려면 파일을 최대한 분리하는 것이 좋지만 프로덕션 코드에서 그렇게 분리하는 것은 오히려 프로그래밍하기 힘들뿐더러 Tree Shaking되는 파일의 용량도 미미하다. 그러므로 파일을 분리해서 Tree Shaking을 활용하는 것은 모듈에 해당하는 것이다

Building a Tree Shaking Friendly JavaScript Package

Tree Shaking | 웹팩

같은 건 매번 새로 요청하지 않기

CloudFront 캐시 설정 (설정값, 해당 값을 설정한 이유 포함)

S3 (origin)

S3는 구글 드라이브라고 생각하면 편하다.

객체 : S3에 저장된 데이터 하나하나 (파일이라고 생각하면 됨)
버킷 : 객체가 파일이라면 버킷은 연관된 객체를 그룹핑한 최상위 디렉토리.

CloudFront (CDN)

  • 파일을 업로드하고 다운로드하는 일이 많을 때 좀 더 빠른 서비스를 제공하기 위한 CDN
  • 캐싱을 위해 전세계 이곳저곳에 edge sever(location)을 두고 clinet에 가까운 edge server를 찾아 latency를 최소화시켜 빠른 데이터 제공합니다.

origin server : 원본 데이터를 가지고 있는 서버 보통 S3, EC2
edge server : 전세계에 퍼져있는 서버. 같은 요청에 대해 빠르게 응답해주기 위해 cache 기능을 제공

S3와 CloudFront의 flow는
S3에 파일들을 업로드할 수 있다. 이 파일들은 객체라고 불린다. S3에 업로드하면 S3는 CloudFront로 객체를 쿼리하고 CloudFront에서 사용자 컴퓨터로 객체를 쿼리한다. 그리고 CloudFront는 S3의 객체가 업데이트 되었는지 확인하기 위해 통신을 하는 구조이다.

CloudFront에서는 캐시와 응답 헤더를 정할 수 있다.

  • 캐시
    • 기본 TTL : S3에서 새로운 객체가 업로드되면 CloudFront로 객체를 보내고 CloudFront는 기본 TTL로 지정한 시간만큼 CloudFront 캐시를 유지하기 위해 S3로 쿼리를 보내지 않는다. 그래서 기본 TTL이 지나기전 이미지 요청을 보내면 응답헤더에 x-cache가 Hit from CloudFront로 오는 것을 확인할 수 있다. 이 말은 CloudFront가 S3에 쿼리를 보내지 않는 것이죠. 기본 TTL의 시간이 지나면 응답헤더에 x-cache가 RefreshHit from CloudFront가 뜨는것을 확인할 수 있다. 이는 CloudFront가 S3에 객체가 유효한지 쿼리를 보낸 것이다.
    • 최소 TTL : 기본 TTL 시간이 지나면 최소 TTL로 지정한 시간마다 S3로 쿼리한다.
    • 최대 TTL : 최대 TTL시간이 지나면 S3에 무조건 쿼리를 보낸다.

정적 사이트이기 때문에 TTL은 전부 1년으로 지정했습니다. 만약 사용자와 액션이 있는 사이트라면 캐시 정책은 제 맘대로 정하기는 힘들 것 같다. 캐시 정책은 유저가 사이트에 머문 시간, 액션 등 통계에 따라 결정되는 것이 옳다고 생각한다.

  • 응답 헤더

cache-control : max-age=31536000

브라우저마다 캐싱 시간이 다르기 때문에 cache-control 시간을 추가하였다. 그리고 정적 이미지는 max-age를 1년으로 설정했다. 기준은 lighthouse에서 정적 리소스의 캐시 만료 기간이 1년이상이 적절하다고 하는 것을 확인할 수 있다.

  • x-cache
    • Hit from cloudfront : CloudFront가 원본에 요청하지 않고 캐시에서 응답을 제공했다. 캐시 o
    • RefreshHit from clodfront : CloudFront가 캐시된 객체가 여전히 유효한지 확인하기 위해 원본에 요청을 보낸 후 캐시에서 응답을 제공했다. 이 경우 CloudFront는 원본에서 전체 객체를 검색하지 않는다. 캐시 o
    • Miss from cloudfront : CloudFront가 캐시에서 응답을 제공하지 않는다. 이 경우 CloudFront는 응답을 반환하기 전에 원본에서 전체 객체를 요청했다. 캐시 x

배포를 만들거나 업데이트할 때 지정하는 값

No Cache-Control Header for files from AWS CloudFront with S3 Origin

콘텐츠가 캐시에 유지되는 기간(만료) 관리

캐시 TTL 산정

Serve static assets with an efficient cache policy

응답 헤더 정책 이해

배포를 만들거나 업데이트할 때 지정하는 값

최소한의 변경만 일으키기

검색 결과 > 추가 로드시 추가되는 결과에 대해서만 화면 업데이트가 새로 일어나야 한다.

react memo 활용

Layout Shift 없이 애니메이션이 일어나야 한다.

Layout Shift가 발생하지 않기 위해서는 reflow가 발생하지 않도록 지향해야 한다. 쌓임 맥락이 생성되는 조건에 따라 layout 변경을 top, left 속성이 아닌 transform의 translate 속성을 사용해서 변경해야 쌓임 맥락이 생성되어 reflow를 방지할 수 있다.

CustomCursor 변경은 top, left 가 아닌 transform.translate 속성을 활용한다.

검색결과 hover는 top 대신 transform translateY를 활용한다.

도움말패널은 right 대신 transfrom translateX를 활용한다.

쌓임 맥락 - CSS: Cascading Style Sheets | MDN

1개의 댓글

comment-user-thumbnail
2022년 9월 7일

결도 블로깅 열심히 했네

답글 달기