ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 로딩 성능 최적화 수업 정리
    Web/Optimization 2021. 9. 22. 19:38
    반응형

    #1. 요청 크기 줄이기

    어떤 타입의 요청이 크기가 큰지를 파악하고, 해당 요청의 크기를 줄인다.

    1. 텍스트 컨텐츠 (소스 코드)

     Minify & Uglify

      - 번들러에서 빌드 옵션으로 제공

        - Webpack 5부터 production mode에서는 기본으로 최적화

      - CSS 파일은 별도로 minify

        - CSS in JS는 Babel transpile 과정에서 minify

     

     GZIP 압축

      - 모던 브라우저에는 모두 내장되어있는 압축 프로그램

      - AWS, 스프링 등 서버에서 설정할 수 있음

      - Brotli는 더 효율적으로 압축, 그러나 아직까진 지원이 미비

     

    2. 이미지

      평균적으로 웹페이지 용량에서 이미지가 파지하는 비율은 60% 이상.

      이미지 용량만 잘 관리해도 어느 정도의 성능은 잡고갈 수 있다.

     

     이미지 해상도가 화면에 보이는 크기보다 크네?

      - Image resize

        - webpack에서 image resize loader를 사용하는 방법

        - 서버에서 처리해주는 방법

        - Google squoosh 등을 이용해 직접 resize해주는 방법

     

     모바일에선 이렇게까지 큰 이미지가 필요 없는데?

      - srcset을 활용한 '반응형 이미지(Responsive image)'

     

     해상도를 잘 맞췄는데도 용량이 크네?

      - 이미지 포맷(Image format) 점검

        ο PNG

          - 무손실 포맷

          - 투명도 지원

          - JPEG에 비해 5~10배까지도 커질 수 있음

          - PNG-8, PNG-24에 따라 용량 차이

     

        ο JPEG

          - 손실 압축 방식

          - 투명도 지원 X

          - 상대적으로 작은 크기

          - Progressive JPG: 처음에 저화질로 렌더링 후 점진적 화질 개선

            - CPU 자원을 좀 더 소모한다.

            - 모바일 사파리에서는 지원 미비.

    JPEG vs. Progressive JPEG

    참고: https://www.airpair.com/ios/posts/loading-images-ios-faster-with-progressive-jpegs

     

        ο WebP

          - 손실/무손실 압축 모두 지원

          - 손실 압축에서 JPEG보다 25~34%, 무손실 압축에서 PNG보다 26% 작은 크기

          - 브라우저 지원 범위 확인 필요

     

        ο GIF

          - 무손실 포맷

          - 대부분 매우 큰 용량

          - 애니메이션, 투명도 지원

     

        ο HEIF / HEIC (High Efficiency Image Format / Container)

          - 고효율, 즉 저용량 고품질

          - 애플 전용

     

      - 이미지 압축(Image compress)

        - 손실 압축(lossy comperssion) vs 무손실 압축(lossless compression)

     

      - 이미지 메타 정보 제거(EXIF 등)

        - 검색어: 'remove image metadata', 'remove image exif'

     

    이미지 품질과 성능 사이에서 항상 균형점을 찾기 위해 고민해야한다.

    사용자 경험을 개선하기 위해 이미지를 압축하는건데, 깨진 이미지가 사용자 경험을 해칠 수 있기 때문.

     

    3. 폰트

     폰트 구성요소 중 필요 없는 게 있나?

      - font-weight

      - subset: 사용하지 않는 글자들을 제거한 서브셋을 사용 

        - '뷁'과 같은 잘 사용되지 않는 글자

        - CJK(China, Japan, Korea)에서 더 효과적

     

    참고자료 1. 웹 폰트 최적화 하기

    참고자료 2. 웹 폰트의 사용과 최적화의 최근 동향

     

    #2. 필요한 것만 필요한 때에 요청하기

    - HTTP의 브라우저 호스트 당 최대 Connection 수 제한

      - 브라우저는 하나의 호스트 당 동시에 맺을 수 있는 Connection 수를 제한하고 있다.

      - 브라우저마다 제한 갯수는 다르지만, 대부분의 최신 브라우저들은 호스트 당 최대 6개의 연결을 지원한다.

      - HTTP/1.1 기준.

     

    ※ Domain Sharding

    여러 개의 서브 도메인을 생성하여 정적 파일을 병렬로 가져옴으로써, 정적 파일의 로딩 속도를 개선하는 방법.

    동시 요청으로 속도가 무작정 빨라질 것 같지만, DNS 조회로 인해 꽤 많은 시간과 CPU, 전력을 소모한다.

    따라서 Domain Sharding은 더 이상 동시요청에 대한 좋은 방법이 아니고, HTTP/2가 더 나은 대안.

    참고: https://wonism.github.io/domain-sharding/

     

    ※ HTTP/2

    HTTP/1.1에 비해 성능 개선에 초점을 맞춰 개발된 프로토콜.

    구글이 HTTP/2 개발에 참여하게 되면서, 구글의 SPDY 프로토콜이 자연스레 녹아들었다.

    HTTP/2는 Multiplexing이 지원되기 때문에 호스트 당 최대 연결 제한이 풀려있다.

    Multiplexing을 이용하면 브라우저는 하나의 TCP 연결을 이용하여 여러 개의 데이터 요청을 보낼 수 있고 요청 순서에 상관없이 응답을 받을 수 있게 된다. 그러면 앞선 요청이 끝날 때까지 기다리지 않아도 될 뿐더러, 여러 개의 연결을 생성하는 데에 필요한 오버헤드도 생략될 수 있다.

    HTTP/2에 추가된 기능들 중에 Server Push라는 기능도 있는데, 이를 이용하면 서버가 클라이언트의 요청 하나에 여러 개의 응답을 할 수가 있게 된다. 클라이언트의 요청을 받아 응답할 때 함께 푸시해주고 싶은 리소스의 목록을 함께 전달해서 클라이언트가 불필요한 요청을 하지 않게끔 도와줄 수 있다.

    참고: https://ibocon.tistory.com/257?category=879638

    https://stackoverflow.com/questions/36517829/what-does-multiplexing-mean-in-http-2

     

    AWS에서는 아마 기본적으로 HTTP/2가 적용될텐데, Spring이나 그런 것들과 함께 사용되는 경우에는 버전을 따로 명시해줘야 할 때도 있다고 하니, 잘 살펴보고 적용할 수 있도록 하자.

     

    1. 요청 수 줄이기

     요청은 하는데 쓰이는 데가 없다면?

    - 불필요한 리소스 요청이 있는지 점검하기

    - Tree Shaking(실제 코드 상에서는 쓰이지 않는 코드들을 제거)

      - 실제로는 사용되지 않는데 쓸데없이 빌드 결과물에 포함되는 코드 제거하기

      - import 최적화

     

     지금 보이는 화면에서 당장 필요하지 않다면?

    - Dynamic Import

    - Lazy Loading & intersectionObserver

    - (React) Route 별 Code Splitting (React Lazy, Suspense)

     

     이미지를 너무 자잘자잘하게 여러 번 가져온다면?

    - Image Sprite

      - 여러 개의 이미지를 하나로 합쳐서 관리.

      - 사용할 때는 CSS 속성을 이용해 background 좌표를 옮겨서 원하는 이미지를 적용

      - 이미지를 캐싱할 때 전체 이미지 중 아이콘 하나만 변경되어도 전체 이미지가 캐싱 무효화되는 단점.

      - 이미지 리더가 제대로 읽지 못하는 탓에 접근성 문제도 존재.

     

    - 다른 방법으로 이미지 대체하기

      ο data URI로 대체하기

        - 텍스트 포맷으로 이미지 노출

        - HTML 내부에서 스크립트처럼 함께 포함될 수 있다. 

        - decoding에 브라우저 리소스가 소모된다. 권장하는 방식은 아님.

        - 번들 사이즈가 커질 수 있다.

     

      ο CSS로 대체하기

        - 브라우저, OS 등에 따라 폰트가 전부 달라지는 경우도 많다.

        - 그래서 폰트같은 것들을 이미지화 시켜서 이미지 스프라이트에 포함시키는 경우가 많다.

        - 이런 부분들을 CSS 대체할 수 있다면 최대한 대체하는 것도 방법.

     

      ο SVG로 대체하기

        - 역시나 너무 남용하면 번들 사이즈가 커진다.

     

    2. 브라우저 리소스 우선순위 조정하기

     바로 필요하니까, 미리 가져와야할 것 같은데?

    ο preload

    <link rel="preload" href="/style.css" as="style" />

      - 리소스 우선순위: 높음

      - 현재 페이지에서 바로 필요한 리소스. 빠르게 가져와야한다고 브라우저에게 알려줌

      - 바로 받아오되(fetch), 바로 실행(execute)하지는 않는다.

        - 스크립트를 미리 받아와도 실행은 나중에 하는 것처럼

      - onload 이후 3초 이내에 preload한 리소스를 사용하지 않을 경우 크롬에서는 경고 로그가 발생

      - 받아온 뒤 브라우저에 캐시된다.

      - 예시) 폰트, 초기 렌더링(Critical Rendering Path)에 반드시 필요한 리소스 등

     

    ο prefetch

    <link rel="prefetch" href="/chunk.js" as="script" />

      - 리소스 우선순위: 낮음

      - 브라우저에게 미래에 필요할 것 같은 페이지 혹은 리소스(script, css 등)를 미리 다운받으라고 알려줌.

      - 현재 페이지가 다 로드된 이후 후순위로 다운받아 prefetch cache에 넣어둔다.

      - 현재 페이지의 로드 시간에는 영향을 미치지 않음.

        - 다음 navigation(페이지 이동)의 FCP나 TTI에 영향

      - 예시) 동일한 앱의 다른 페이지에서 사용되는 JS 번들, 현재 페이지에서 쓰이지만 초기 렌더링에는 필요 없는 리소스(ex. 모달), 페이징된 목록에서 다음 페이지의 컨텐츠 등

     

    ο preconnect

    <link rel="preconnect" href="https://example.com" />
    <link rel="preconnect" href="https://cdn.example.com" />

      - 브라우저에서 서버와 연결만 미리 맺어두고 있으라고 알려주는 것

        - connection 미리 맺어두기로 개별 요청당 100 ~ 500ms 정도의 로드 시간을 절약

        - 다운로드는 미리 하지않고 connect만 미리 해두기 때문에 딱 연결 시간만큼만 단축된다.

      - 예시) 외부 도메인 레소스(ex. 구글 폰트)

    참고: https://www.airpair.com/ios/posts/loading-images-ios-faster-with-progressive-jpegs

     

     이건 바로 필요없을 것 같아.

    자바스크립트는 파서 차단 리소스(Parser blocking resource)이다.

    ο defer

      - script fetch, script execution 모두 HTML parsing에 영향을 끼치게 하고 싶지 않을 때.

      - script fetch는 HTML parsing과 함께 비동기로 이루어진다.

      - HTML parsing이 끝날 때까지 스크립트 실행을 지연한다.

        - defer script fetch > DOMContentLoaded > defet script execute

     

    ο async

      - script fetch만 HTML parsing이랑 상관없이 하고싶을 때.

      - script execution은 script fetch만 다 되면 당장에라도 HTML parsing 중간에 일어날 수 있다.

      - DOMContentLoaded, 혹은 다른 스크립트들과 독립적으로 동작한다.

      - 예시) Google Analytics, 광고 등. 문서 내용이나 앱 자체와는 관련없이 독립적으로 동작하는 스크립트

    참고: https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html

     

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

    HTTP Caching

    캐싱(Caching)은 리소스의 사본을 저장해두었다가 같은 요청이 들어왔을 때 저장해 둔 사본을 제공하는 것을 의미한다. 브라우저도 자체적으로 캐시를 가지고 있는데, 브라우저 캐시에 있는 리소스가 유효한 리소스이면 서버에 요청 자체를 보내지 않는다.

     

    - 캐시는 HTTP Response Header에 정해진 값에 따라 세팅된다.

      ο 브라우저가 서버에 요청하지 않고, 캐싱된 자원을 바로 사용하도록 하고싶을 때

        - 리소스를 요청받았을 때 서버는 응답으로 주는 리소스의 유효 기간을 설정할 수 있다.

          - Cache-Control: 클라이언트에 어떻게 캐싱할 지 지정. max-age에 유효기간을 초 단위로 지정한다.

          - Expires: 절대 유효기간 지정(deprecated)

          - max-age 설정은 평균적으로 최대 1년 = 31,536,000초

        - 지정된 캐시 유효 기간 내에 다시 요청하면 서버에 요청하지 않고 캐시된 리소스 사용

     

    ※ no-cache, no-store

    리소스를 요청받았을 때 서버는 응답으로 주는 리소스를 캐시하지 말라고 알려줄 수 있다.

    - Cache-Control: no-store

      - 캐시 불가능. 매번 서버에서 새로 받아와야한다.

    - Cache-Control: no-cache

      - 캐시 가능하긴 하지만, origin 서버에 매번 캐시된 리소스의 유효성 검증 요청

      - 유효성 검증은 연결 비용은 동일하지만, 받아오는 데이터는 없고 유효성 여부만 받아오므로 좀 더 가볍다.

     

    ※ Cache-Control: must-revalidate

    만료된 캐시만 서버에 검증을 받도록 한다. 

    즉, max-age에 아직 도달하지 않았다면 캐시된 리소스를 사용하고, 그렇지 않다면 재검증을 한다.

    참고: https://yceffort.kr/2020/10/http-cache

    https://stackoverflow.com/questions/18148884/difference-between-no-cache-and-must-revalidate

     

      ο 브라우저가 캐싱된 자원이 최신이 맞는지 서버에게 추가로 확인해야할 때: 조건부 요청(Conditional Request)

        - 캐시를 읽어왔는데 유효기간이 지났다면(stale), 클라이언트는 서버에 캐시에 있는 걸 써도 되는지 신선도 재검사(freshness revalidation) 요청을 보낸다.

          - 받았던 응답 헤더에 ETag가 있었다면

            - If-None-Match 요청 헤더에 캐시의 ETag 값을 넣어 서버의 ETag와 같은지 비교(ETag가 바뀌었으면 업데이트 해야하는 리소스라는 의미)

          - 받았던 응답 헤더에 Last-Modified가 있었다면

            - If-Modified-Since에 캐시의 Last-Modified 값을 넣어 서버에서 그 이후로 수정이 있었는지 확인

     

    - 브라우저 캐시 용량

      - 대부분의 최신 브라우저들은 default disk cache size를 아주 작게 세팅한다.

        - 파이어폭스는 50MB, IE는 8 ~ 50MB, 크롬은 80MB 이하

     

    - Disk Cache와 Memory Cache의 차이

      - Memory Cache는 RAM에 저장되기 때문에 읽기와 쓰기에 빠른 반면, 컴퓨터가 종료될 때 휘발된다.

      - Disk Cache는 하드 드라이브에 저장되기 때문에 읽기 쓰기가 느린 반면 디스크에 항상 저장되어있다.

     

    참고: https://toss.tech/article/smart-web-service-cache

    https://imagekit.io/blog/ultimate-guide-to-http-caching-for-static-assets/

     

    CDN(Contents Delivery/Distribution Network)

    컨텐츠 전송 네트워크. 클라이언트가 원본 서버와 지리적으로 거리가 있는 경우, 보다 효율적으로 데이터를 전달하기 위해 클라이언트에 가까운 지점의 서버, 즉 CDN에 원본 서버의 컨텐츠를 캐시한다.

    ex) CloudFront, CloudFlare 등

     

    ο Cache-Control: public

      - Cache-Control: public,s-maxage=31536000,max-age=0

      - 중간 프록시(ex. CDN)에 캐시를 저장할 수 있다.

      - s-maxage: 프록시와 같은 공유(public) 캐시에만 적용되는 유효기간

     

    ο Cache-Control: private

      - 최종 끝의 클라이언트만 캐시 가능

      - CDN에는 캐싱이 안된다.

     

    default는 아마 private이므로, CDN 캐싱을 이용하고 싶다면 public을 지정해줄 것.

     

    Cache Invalidation

    유효기간이 아직 끝나지 않았지만 클라이언트가 캐시된 리소스를 사용하지 않고 서버에서 갱신된 최신 리소스를 사용하도록 강제해야 한다면

      - 브라우저 캐시 무효화는 불가능하다. (사용자가 직접 캐시를 제거하지 않는 이상)

      - CDN 캐시 무효화 기능은 해당 플랫폼에서 제공(ex. CloudFront의 Invalidation)

     

     정적 리소스에 고유한 값 붙여서 쓰기(캐시 날리기)

      - content hash / chunk hash

        - 라이브러리 코드같은 것들은 변하지 않으므로, 따로 chunk로 분리해서 캐싱하기도 한다.

      - 키워드: 'cache busting'

     

     API Cache

      - React Query, SWR

    반응형

    'Web > Optimization' 카테고리의 다른 글

    렌더링 성능 최적화 수업 정리  (0) 2021.09.24
    이미지 프리로딩(Image Preloading)  (2) 2021.09.23
    프론트엔드 웹 로딩 최적화  (2) 2021.08.16

    댓글

Designed by Tistory.