ETC

H2에서 청크 개수 제한은 성능에 영향을 줄까? 주는 것 같다.

partner_jun 2023. 12. 7. 22:54

얼마 전 회사에서 공유했던 문서 중 하나를 가져와 블로그에도 적어둔다. 다른 글과 다르게 회사에 공유했던 주요 내용만 요약한 글이지만, 제대로 확인된 내용이 아니기도 하고 평소에 어떤 식으로 이야기를 풀어 공유 해왔는지를 남겨두는데 의미를 둔다.
 

개요

신규 프로젝트 런칭 이후 성능 개선 작업 도중 문득 이상한 것을 발견했다. HTTP2를 지원하는 스테이지 서버임에도 때때로 굉장히 "느리게" 페이지의 JS가 실행되는 것이었다. 혹시나 하여 청크 개수 제한을 걸고 배포해보니 불규칙적인 로드 완료 지연이 크게 줄어든 것을 느꼈다. 이것을 제대로 확인해보진 않았지만 분명하게 빨라진 것을 느꼈고, 다른 성능 개선 작업에 더해(서비스에 아무런 영향이 없으니) 청크 제한을 건 상태로 라이브 배포를 진행했다. 다른 작업 요소가 포함되어 있었으니 명확하게 "이건 이만큼 영향이 있었다" 라고 이야기할 수 없었고, 이에 청크 개수 제한이 성능 개선에 도움이 될 수 있는 것인지 리서치, 테스트했던 결과를 팀 내에 공유했다. 팀원들의 다양한 의견을 얻고 싶었지만 다들 바쁜지 딱히 관심이 없어 보였다는 점이 아쉽다. 내용이 내용인지라 의견 내기 어렵긴 했을 것 같다.
 

HTTP2에서 청크 개수는 성능에 연관이 있을까?

 
 
아래 내용부터는 회사에서 공유했던 문서의 내용이다.
 
 


 

들어가며

  • 작업 중 발견한 Chunk 파일 개수 제한이 정말 성능 향상을 가져오는지, 어느정도 성과를 가지는지 확인하기 위한 테스트를 진행하고 결과를 공유하고자 함
  • 정량적 측정을 진행하진 못했지만, 청크 파일 개수를 제한 했을 때 속도가 더 빨라진 것으로 보여 적용한 상태
  • 앱에서 static 파일들을 캐시하지 않는 상황으로(매번 삭제하고 있음. 다른 문서를 통해 공유), 청크 파일 개수 및 HTTP2에 영향을 그대로 받는 상황
  • 해당 내용 확인을 위해 HTTP2가 지원되는 스테이지 환경에 지속적으로 배포하여 확인 진행

 
 

예상

해당 내용 확인을 위해 예상되는 제약 사항이나 배경에 대해 간단하게 고민을 전개함
 

  • HTTP2와 Chunk 파일 개수가 연관이 있을까?
    • HTTP2의 멀티플렉싱으로 JS 파일들이 동시에 다운로드되니 영향이 없을 것
      • 하지만 청크 제한을 걸지 않으면 오히려 전체 파일의 용량이 커지므로(gzip, brotli 관련) 더 느려질 가능성 있음

  • Hydration과 연관이 있을 수 있을까?
    • 논리적으로 생각했을 때 JS 파일이 모두 다운로드, 실행되어야 Hydration이 완료될 것
      • 컴포넌트 렌더링을 모두 해서 메모리에 들고 있어야 fiber로 비교를 하든 말든 하니까.

    • 그럼 JS 파일이 일부만 다운로드 되어도 Hydration이 가능할까?
      • 아래 -> 위 순서로 트리를 구성할 순 없으므로 전체 파일을 다운로드하지 않으면 Hydration 자체는 불가능함
        • dynamic 등 임의적으로 분리한 컴포넌트는 연관이 없을 것

  • 결국
    • HTTP2를 기반으로 각 번들 파일이 더 빠르게 다운로드 하더라도 결국 실행이 가장 늦게 다운로드 된 파일에 블라킹 될 것으로 예상됨
    • 단 하나의 파일이라도 더 "느리게" 다운로드 완료된다면 전체 Scripting이 지연되므로 네트워크 환경에 영향을 받지 않을까?
      • 대부분이 모바일 사용자인 상황이라면 네트워크 환경이 고르지 않을 확률이 훨씬 높다.
      • 결국 청크 개수 제한은 간접적으로라도 성능 향상에 도움을 줄 것이다

 
 

실제 배포를 통한 성능 지표 비교

1. Chunk Limit 4 적용된 상태 (4개 파일로 나누어짐)

Waiting for Server가 약 117ms 발생
스크립팅에 약 650ms가 소요
  • Content download까지의 대기 시간(Waiting for server response)가 100ms 이상 소요
  • 청크 개수 제한이 없을 때에 비해 파일 다운로드에 상대적으로 더 긴 시간이 소요됨 (340ms 이상)

 
 

2. Chunk Limit 미적용 상태 (24개 이상의 파일로 나누어짐)

같은 버전-프로젝트임에도 Waiting for Server에 700ms 소요(약 7배?)
Scripting 시간은 별 차이가 없음
  • Content Download까지 대기 시간(Waiting for server response)가 700ms 이상 소요
  • 각각의 파일 다운로드에는 큰 시간이 소요되지 않음 (20ms 내외)

 
 

체크포인트

  • 청크 개수 제한 여부와 Scripting 시간 자체는 큰 연관 관계가 없는 것으로 보임
  • Document가 다운로드 된 이후, JS파일 다운로드가 시작되기까지 지연은 동일하게 있음
    • 하지만 Chunk 파일의 개수가 많은 경우에 Waiting for server response 지연 시간이 평균적으로 더 길게 기록됨
    • Waiting for server response은 무엇인가?
      • Chrome 문서의 설명에 따르면 네트워크가 느리거나 브라우저가 다른 작업을 하고 있을 때 지연될 수 있다고 함
        • 네트워크 지연의 경우, Node.js(Next.js) 서버 단일 쓰레드 문제로 의심할 수 있음
          • 하지만 S3업로드 후 CDN을 거치는 상태이니 연관 없을 것
        • 브라우저의 다른 작업이라 하면 곧 스크립트 작업, Rendering 및 Parsing & Execution을 뜻할 것으로 예상

  • 결국
    • 다운로드 시간 지연은 곧 스크립트 실행 지연으로 직결되므로 청크 개수 제한을 걸지 않으면 H2에서 생각보다 빠르지 않다
      • 예상했던 모바일 네트워크 상태로 인한 불균형은 차치하고,
        Waiting for server response 지연에 의해 더 나쁜 성능을 보이고 있었을 지도 모르는 상황
      • "Waiting for server response"가 무엇인지, 어떻게 감소시킬 수 있는지 확인이 필요함

 
 

결과 (뇌피셜)

명확한 참고 자료, 혹은 정량적 측정이 불가능 하였지만 HTTP2라 할지라도 매번 다시 다운로드 받는 환경이라면 청크 파일의 "적당한" 개수 제한이 필요한 것으로 보임

  • 현재 서비스는 모바일 중심의 서비스로 불안정한 모바일 네트워크 특성상 파일 개수가 많을수록 다운로드가 크게 지연되는 파일이 생길 가능성이 높음
    • 특정 파일의 다운로드 지연 -> 스크립트 실행 지연으로 직결됨
        • 특정 파일의 다운로드 지연 확인 방법
          • Chrome Network 탭
            • → JS 파일(청크 파일) URL block
            • → 새로고침 (JS 실행 안됨)
            • → URL blocking 해제
            • → console 탭에서 script insert 구문 작성 (JS 실행됨)
          • 위 순서로 특정 JS 파일이 다운로드 지연되는 상황을 인위적으로 만들어 확인할 수 있음
청크로 나누어진 JS 파일의 URL 블락

 

JS 파일이 블라킹되어 다운로드 되지 않는 모습

 

URL 블라킹 해제 후 콘솔에서 직접 JS 파일 삽입

 
위 순서를 따르면 JS 파일이 삽입된 이후에 JS가 실행됨. Webpack 자체에서 청크 파일이 모두 다운로드 될 때까지 JS 실행을 미루고 있음을 확인할 수 있음.
 

  • 프로그래밍 특성상 JS 파일의 parsing → eval → parsing → ... 작업의 반복 회수를 줄이는 것이 효율적일 것이라고 예상
  • gzip 혹은 brotli 로 인한 압축으로 번들링하여 파일의 개수가 줄어들면 압축률이 상승, JS 파일의 절대적인 용량 자체도 줄어듦
    • 다운로드 시간 역시 JS실행 시간에 큰 영향을 주므로 파일 용량 감소도 큰 영향을 줄 수 있음
      • 참고) webpack의 http2-aggressive-splitting과 같은 라이브러리는 첫 로드 속도를 위한 것이 아닌
        변경되지 않은 파일에 대한 캐시를 전략적으로 하고자 하는 것임.

  • 결론적으로, 청크 개수 제한을 설정하지 않을 이유도 없음
    • 청크 개수 제한은 Webpack의 기본 플러그인으로, 성능이나 서비스에 악영향을 주거나 운영 환경 변화가 필요하지 않기 때문

 
 

그 외

추가로 위 내용을 확인하며 HTTP2에 대한 이상할 정도의 신뢰가 형성되어 있는 것을 보게 되었음

  • 아래 적어둔 Why HTTP/2 is slower than plain HTTPS2?과 같은 문서들을 보면 무조건적인 상위 호환이라는 인식이 있음
    • 기본적으로 HTTP 1.1의 상위 호환이지만 이는 부족했던 점을 보완하려고 하는 것일 뿐, 발생할 수 있는 모든 문제가 해결되었다고 장담할 수 없음

  • 인터넷에서 볼 수 있는 HTTP2 벤치마크 결과는 버킷의 이미지 같은 완전 정적 파일의 결과
    • 브라우저에서 "실행" 해야하는 JS를 서빙하는 입장에서의 벤치마크로 갈음 할 수 없음
    • 불안정한 모바일 네트워크와 같은 환경에서의 테스트라고 볼 수도 없음

  • HTTP2의 문제가 아니더라도, 인프라나 소프트웨어(Nginx, Chrome, Webpack Chunk) 측면에서 한계가 있을 수 있지 않을까 하는 생각
    • 심지어 서비스의 앞단 WAF에서 발생하는 문제일 수도 있음

  • 늘 그렇듯, "은탄환은 없다"
    • 보험정도는 들어둘만 하다. 반대로 보험이 리스크를 주는 아이러니한 상황이 아니라면.

 
 

참고 문서

 
 


 

마치며

위 내용이 회사의 팀 문서로 남겨둔 내용이다. 결론이 딱히 없긴 하지만 누군가에겐 도움이 되지 않을까 하는 생각이다. 명쾌하게 답을 얻진 못했지만 의심가는 점들이 있으니 프로젝트에는 청크 개수 제한을 걸어두었다. 위 내용에 적었듯, 네트워크 환경의 문제로 느려질 수도 있지만 HTTP2 멀티플렉싱이 지원되는 상황에서도 JS 실행이 지연되는 것을 확인했고, 청크 파일 개수 제한을 걸지 않을 이유도 없기 때문이다. 센트리 퍼포먼스를 이용하면 사용자의 랜딩에 정말 유효한 영향을 미치는지 확인할 수 있을 것 같지만... 내 궁금증을 해소하기 위해 상용화된 회사 서비스로 실험을 하고 싶지 않았다.
청크 개수 제한을 걸고 마무리한 이후에도 주기적으로 이 내용에 대해 검색해보고 있다. 이미 H3로 넘어가는 시점이 와버려서인진 모르겠지만 딱히 명쾌함-혹은 고개가 끄덕여질만한 답을 얻진 못했다. 오히려 고민만 더해졌는데, What does multiplexing mean in HTTP/2 라는 스택오버플로의 답변을 보고 나서다.
 

H2에서 브라우저는 파일을 조각내어 다운로드 받고 다시 조립한다?

 
브라우저가 파일을 조각냈다가 다시 조립한다는 내용이다. 웹 브라우저에서 JS를 실행하는 아주 근본적인, 기본적인 룰은 스크립트를 만났을 때 그 즉시 스크립트를 실행한다는 것이다. 그렇다면 이렇게 "다시 조립한" JS 파일은 즉시 실행되는 것일까? 그럼 그 "무거운" JS 실행 중에도 다른 파일의 조립을 수행할까? 그럼 멀티플렉싱으로 받은 CSS로 만든 CSSOM은? 이미지나 레이아웃은?... 이건 크로니움의 탭(프로세스)와 관련된 문제이기도 하다. 크로니움의 관련된 문서를 살펴보아도 명확한 답을 얻을 수는 없었다. 안타깝지만 나는 여기까지만 고민해본 상태다.
 

몰름