서비스 워커 script 위치에 따른 성능

ryun·2024년 3월 12일
post-thumbnail

성능 개선을 하던 중, 스크립트 태그 위치에 따라 성능이 달라진다는 것이 기억나서 서비스워커 script 태그 위치는 성능에 영향을 미치지 않을까? 라는 궁금증이 들었습니다.

기존에는 서비스워커 script 태그를 body의 하단부에 위치

<!doctype html>
<html lang="en">
  <head>
    기존 스크립트 태그들
    <title>AI Canvas</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
     <script>
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker
          .register('./service-worker.js')
          나머지 서비스 워커 등록 태드
      }
    </script>
  </body>
</html>

아래와 같이 head 태그로 위치시키면 성능에 영향을 주는 점이 있을까?

<!doctype html>
<html lang="en">
  <head>
    기존 스크립트 태그들
    <script>
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker
          .register('./service-worker.js')
          나머지 서비스 워커 등록 태드
      }
    </script>
    <title>AI Canvas</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

의문점

현재 리팩토링 하고 있는 프로젝트에서 사용하는 웹 폰트의 로드 시간이 꽤 길었습니다.
따라서 서비스워커를 통해 캐싱을 해서 사용하고 있는데,
처음 화면 렌더링시 웹 폰트 로드 / 서비스워커에서 자원을 캐싱하면서 웹 폰트 로드
위와 같이 동일한 웹 폰트 로드라는 작업이 두번 발생하는 상황에서
처음 서비스워커 자원에서 캐싱한 웹 폰트를 사용해서 두번 로드되는 작업을 줄일 수는 없을지 궁금했습니다.

찾아본 자료

구글링을 해보다가 스택오버플로우에서 저와 비슷한 의문을 가지고 있는 글을 발견했습니다 🥹
Where to put ServiceWorker snippet - head or body?
댓글에서 해당 질문에 대한 정확한 답변은 아니었지만,
서비스 워커 등록에 대한 글(바로 이 글)을 추천해줘서 읽어볼 수 있었습니다.

아래는 제가 이해하고 정리한 핵심 내용입니다.

첫 방문하는 사용자에게 좋은 경험을 제공하려면 초기 페이지가 로드된 후로 서비스 워커 등록을 지연시키는 것이 좋습니다.특히 느린 네트워크 연결을 사용하는 모바일 장치 사용자에게는 더 그렇습니다.
서비스 워커 스레드를 시작해 백그라운드에서 자원을 다운로드하고 캐싱하는 것은
사이트에 처음 방문한 사용자에게 좋은 인터랙티브 경험을 제공하는 목표를 반하는 작업이 될 수 있습니다. 사용자가 사이트에 처음 방문했을 때, 서비스 워커가 즉시 백그라운드에서 실행되면서 자원을 다운로드하고 캐시하는 과정은 네트워크 대역폭을 사용하게 되며, 이는 동시에 페이지의 메인 컨텐츠를 로드하는데 필요한 네트워크 자원과 경쟁하게 됩니다. 결과적으로 이러한 경쟁은 사이트의 첫 화면이 사용자에게 보여지기까지의 시간, 즉 TTI(Time to Interactive)시간을 지연시킬 수 있습니다.
이러한 이유로, 개발자들은 때로 사용자가 사이트의 주요 컨텐츠를 더 빠르게 볼 수 있도록 서비스 워커의 자원 다운로드 및 캐싱 작업을 지연시키는 전략을 선택하기도 합니다.
이를 해결하기 위해 navigator.serviceWorker.register()를 호출하는 시기를 조정할 수 있습니다. window의 load 이벤트가 발생한 후에 등록을 지연시키는 것입니다.

  • 기존 서비스 워커 등록 코드
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/service-worker.js');
}
  • 서비스 워커 등록을 지연하는 코드
    • 적절한 경우
      • 기본 화면으로 제공 전 짧은 애니메이션을 제공하는 서비스
      • 사용자에게 최고의 첫 방문 경험을 제공하는 것이 최우선 과제인 서비스
if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
        navigator.serviceWorker.register('/service-worker.js');
    });
}

위 내용과 같이 서비스 워커를 지연시키는 것 또한 하나의 전략이 될 수 있겠다는 생각이 들었습니다.

테스트 결과

따라서 위 내용을 바탕으로 4가지 경우를 테스트해보았습니다.
1. head 태그 내 등록
2. head 태그 내 등록 지연
3. body 태그 내 등록
4. body 태그 내 등록 지연

제 프로젝트의 service-worker 파일은 waitUntil을 사용해 웹폰트 캐싱 작업 후 설치 단계가 진행되도록 구현해놓았습니다.

테스트 환경은 로컬 호스트 시크릿 모드로 하였고,
네트워크 상태는 사용자가 느린 장치를 사용한다고 가정하고 네트워크 탭에서 요청을 더 확실하게 확인해보고자 '느린 3G'로 설정했습니다.
테스트 할 때마다 개발자 도구에서 캐시는 모두 지우고, 서비스워커도 등록 취소하여 새로운 사용자가 처음 방문하는 상황을 가정했습니다.
그리고 네트워크 탭에서는 캐시 사용 중지를 체크해 디스크 캐시를 사용하지 않도록 설정했습니다.

head 태그 내 서비스워커 스크립트 위치

  • 등록 지연 안했을 경우

  • 등록 지연했을 경우

body 태그 내 서비스워커 스크립트 위치

  • 등록 지연 안했을 경우

  • 등록 지연했을 경우

4가지 경우 모두, DOMContentLoaded와 로드 시간은 20-21초 대로 큰 차이가 나지 않았습니다. 이를 통해서 서비스 워커 스크립트 위치는 DOM Tree와 이미지까지 렌더링 시간의 성능에는 큰 영향을 미치지 않는 것을 확인했습니다. 즉, head 태그와 body 태그 위치에 따라서는 유의미한 차이가 없다로 판단했습니다.
결과를 보고 더 공부해보면서 이는 서비스 워커가 별도의 스레드에서 DOM 렌더링과 병렬적으로 수행되기 때문이라는 것을 알게되었습니다.

그리고 제가 궁금했던 첫 방문시 서비스워커 캐싱 자원을 곧 바로 사용할 수 없는지에 대한 의문도 여기서 해소되었습니다.
서비스 워커가 설치되는 동안 페이지는 이미 로딩과 렌더링을 시작했고,
이미 로딩과 렌더링을 시작한 페이지는 제어할 수 없기 때문에 서비스 워커에서 하는 캐싱 작업따로, 이미 로딩된 자원 다운로드 따로 이렇게 진행되는 것으로 파악되었습니다.

load 이벤트 직후로 서비스워커 등록을 지연한 테스트에서는 웹 콘텐츠가 로드된 직후에 캐싱을 진행했습니다. 지연하지 않았을 경우에는 스크립트가 즉시 캐싱을 수행했습니다.

결론

결론적으로 저는 아래와 같이 이해할 수 있었습니다.

  • 서비스워커는 페이지와 서버 사이에서 인터셉터를 하는 프록시 같은 역할을 하는 것
  • 웹 워커처럼 서비스 워커라는 이름에서도 알 수 있듯이 별도의 스레드에서 실행
  • 그러나 처음 설치할 때, 캐싱 수행 과정에서 네트워크 자원과 경쟁을 할 수 있기 때문에 (특히 느린 네트워크 환경이라면) 사이트 특성에 따라 적절히 불러와서 사용

사용하면서도 미지의 느낌이 있었던 서비스워커를 보다 구체적으로 이해할 수 있게 되었습니다.
제가 진행한 테스트 이상으로, 서비스와 캐싱하는 자원의 규모에 따라 더 유의미한 차이를 보일지도 모르겠습니다. 결국 내가 만드는 서비스 특성과 사용자의 요구를 고려한 전략을 사용하는 것이 중요하다고 생각합니다!



참고한 사이트

0개의 댓글