Preload, Prefetch And Priorities in Chrome

Yesl Koh
23 min readJun 15, 2017

--

https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf 을 번역한 내용입니다.

오늘 우리는 크롬의 networking stack으로부터 얻은 통찰점들에 대해 얘기할것이다. 웹 로딩 초기 동작 <link rel=“preload”> & <link rel=“prefetch”>)와 같은 것들이 어떻게 동작하는지에 대한 명료한 이해를 통해 효과적으로 이용할 수 있길 바란다.

다른 기사들에서 다뤘듯이, preload는 선언적 fetch이다. Document의 onload event를 막지 않으면서 브라우저가 자원을 요청하도록 강제할 수 있다.

Prefetch는 자원이 필요할수도 있다는 것은 브라우저에게 힌트를 주지만, 로딩할지 말지는 브라우저가 결정하게 한다.

Preload can decouple the load event from script parse time. If you haven’t used it before, read ‘Preload: What is it Good For?’ by Yoav Weiss

Production sites에서의 preload 성공 사례들

세세한 면을 다루기 전, 작년 1년 동안 preload를 사용하면서 얻은 긍정적 효과에 대한 짧은 요약이다:

housing.com은 Progressive Web App의 주요 late-discovered scripts를 preloading함으로써 Time to Interactive까지의 시간을 10% 감소시켰다.

Shopify는 웹 폰트를 preloading함으로써 time-to-text-paint 시간을 크롬 데스크탑(유선)에서 50%(1.2초) 향상시켰다. 이를 통해 flash-of-invisible text 문제를 완벽히 해결하였다.

Left: with preload, Right: without (video)
Web Font loading using <link rel=”preload”>

Treebo, 인도의 가장 큰 호텔 중 하나,는 header 이미지와 key webpack 번들을 preloading함으로써 3g 데스크탑에서의 Time to First Paint와 Time to Interactive 시간을 1초 줄였다.

비슷하게, key bundles를 preloading함으로써 Flipkart는 route chunks들이 PWA에서 계산되기 전 main thread에서 보내는 대기 시간을 크게 줄였다.(성능 안 좋은(low-end), 3G로 실행해봤을 때의 결과)

Top: without preload, Bottom: with preload

Chrome Data Saver 팀은 preload를 사용할 수 있는 스크립트, css 스타일시트에 있어 first contentful paint까지의 시간이 평균적으로 12% 정도 단축되는 것을 보았다.

prefetch는 구글에서 광범위하게 사용되고 있고, 검색결과 페이지에서 목적 페이지를 렌더링하는데 걸리는 시간을 줄일 목적으로, 중요한 자원을 prefetch하기 위해 여전히 사용하고 있다.

Preload는 여러 케이스에서 실배포된 큰 사이트에서 사용하고 있고, 이 글에서 이에 대한 내용을 볼 수 있다. 그 전에, 우선 network stack이 preload와 prefetch를 어떻게 다르게 다루는지 알아보자.

<link rel=“preload”> vs <link rel=“prefetch”> 언제, 무엇을 사용해야할까?

팁: 현재 페이지서 사용될거라 확신이 드는 자원들은 preload하여라. 미래의 여러 navigation boundaries에서 사용될거라 생각되는 자원들은 prefetch하라.

Preload는 브라우저에게 페이지에서 필요한 자원을 일찍이 fetch하라는 지침이다. (key scripts, web fonts, hero images)

Prefetch는 사용 경우가 조금 다르다. 받아온 요청이나 자원이 유저의 앞으로의 navigation(예를 들어 다른 뷰나 페이지 사이)에까지 지속되어야한다. 만약 Page A가 Page B에 꼭 필요한 자원을 prefetch하는 요청을 날리는 경우라면, 중요한 자원과 navigation requests는 병행으로 수행될 수 있다. 만약에 이 경우에 preload를 사용한다면 즉시 Page A의 unload가 취소될 것이다.

Preload 와 prefetch 중 하나를 택함으로써, 현재 navigation 혹은 미래의 navigation시 중요 자원을 로딩하는데 적합한 해결책을 찾을 수 있다.

What is the caching behavior for <link rel=”preload”> and <link rel=”prefetch”>?

크롬에는 네 가지의 캐시가 있다. the HTTP cache, memory cache, Service Worker cache & Push cache 이다. Preload, precache된 자원은 모두 http cache에 저장된다.

자원이 preload나 prefetch될때 net stack부터 http cache를 거쳐 renderer의 memory cache까지 타고 올라간다. 만약 자원이 캐시 가능하다면(예: 유효한 max-age를 가진 유효한 cache-control이 있는), 자원은 http cache에 저장되고 현재와 앞으로의 세션 때 사용 가능하다. 만약 자원이 cacheable하지 않다면, http cache에 저장되지 않고 대신 memory cache까지 가고 사용되기 전까지 이 쪽에 저장된다.

How does Chrome’s network prioritisation handle preload and prefetch?

Chrome46 Blink이후로의 버젼에서 자원들이 어떤 우선권을 갖는지에 대한 표이다.(출처: Pat Meenan)

* Preload using “as” or fetch using “type” use the priority of the type they are requesting. (e.g. preload as=style will use Highest priority). With no “as” they will behave like an XHR. ** “Early” is defined as being requested before any non-preloaded images have been requested (“late” is after). Thanks to Paul Irish for updating this table with the DevTools priorities mapping to the Net and Blink priorities.

스크립트가 어디에 위치하는지 따라, 그리고 async, defer, blocking인지 따라 다른 우선권을 갖는다.

  • 첫 이미지(문서 내 앞쪽에 있는 이미지) 전 요청되는 blocking script는 Net: Medium
  • 첫 이미지 이후로 요청되는 blocking script는 Net: Low
  • (문서 내 위치에 상관없이) async/defer/injected script Net:Lowest

viewport내에 있고 visible한 이미지들(Net:Medium)은 viewport내에 있지 않은 이미지(Net:Lowest)들보다 높은 우선권을 갖는다. 그렇기에 어느 정도까지는 크롬이 우선권이 낮은 이미지들에 대해서는 lazyload 비스꾸므리하게(pseudo-lazy-load) 로딩하도록 노력할 것이다.

이미지들은 낮은 우선권으로 시작하고 layout 완료시 viewport내에서 발견되면 우선권이 올라간다.(하지만 layout완료시 이미 요청된 이미지들은 다시 우선권이 계산되어 부여되지는 않는다)

“as” attribute를 사용하여 preloading되는 자원들은 요청되는 자원의 타입과 같은 우선권을 부여받는다. 예를 들어, preload as =“style”은 가장 높은 우선권을 갖는 반면 as=“script”는 low 혹은 medium priority를 갖는다. 이 자원들은 같은 CSP 정책에 따른다. (예: script는 script-src에 따른다)

“as” 없이 preload된 자원들은 async XHR와 같은 우선권으로 요청된다. (high로 요청됨)

자원이 어떤 우선권으로 로딩되었는지 궁금하다면 DevTools Network section의 Timeline/Performance에서 찾을 수 있다.

Network Panel의 “priority” column에서도 찾을 수 있다.

What happens when a page tries to preload a resource that has already been cached in the Service Worker cache, the HTTP cache or both?

상황에 따라 크게 다를 수 있지만, 대체로 (something good should almost always happen in this case) 좋은 결과가 나와야 한다. service worker이 의도적으로 refetch하거나 http cache에서 기한 만료되지 않았다면 자원은 네트워크를 통해 refetch하지 않는다.

만약 자원이 http cache에 있다면(SW Cache & network 사이) preload는 같은 자원으로 부터 cache hit을 받아야할 것이다.

Are there risks with these primitives of wasting a user’s bandwidth?

preload나 prefetch 사용시, 유저의 bandwidth를 낭비하는 위험에 노출되어 있다. 특히 자원이 cacheable하지 않을 경우, 이런 위험에 노출될 수 있다.

사용되지 않은 preloads이 있을 경우 onload되고 3초 정도 후에 크롬 콘솔에 경고를 찍는다.

이 경고가 뜨는 이유는, 아마 개발자가 성능 향상을 위해 preload를 사용하여 다른 자원들을 cache 하려 했던 것으로 보이는데, preload된 자원이 사용되고 있지 않다면 성능에서의 이점이 없는데 이 기능을 사용하고 있기 때문에 경고로 알려준다. 모바일 환경에서의 경우, 쓸데없이 사용자의 데이터를 낭비하는 상황이 발생하는 것이니 무엇을 preloading하는지 충분히 알고 사용하자.

What can cause double fetches?

Preload and prefetch는 잘 알고 사용하지 않을 경우, double fetching이 일어날 수도 있다.

preload가 안 될 경우에 사용할 목적으로 prefetch를 사용하지 말자. 다시 한 번 강조하지만, prefetch preload는 다른 용도로 사용되는 두가지이다. 두가지를 같이 쓸 경우 의도와 달리 double fetching이 일어날 수도 있다.미래의 세션을 위한 경우엔 prefetch를 사용하고 현재 세션에서 사용하기 위해 cache를 준비하는 경우라면 preload를 사용하여라. prefetch, preload를 fetch안되면 load 사용해야지 혹은 load 안되면 fetch 사용해야지 식으로 사용하지 말자.

아직은 preload와 함께 fetch()가 안정적으로 작동한다 생각하지 말자. 크롬에서 preload와 함께 fetch() API를 사용할 경우 두번 다운로드가 된다. 보통 XHR 사용할 경우엔 이런 버그가 발생하지 않고, 이 버그를 현재 해결하고 있다.

preloading시 “as”를 붙이자! 안 그럼 장점을 사용하지 못한다.

“as”를 사용하여 무엇을 preload할지 유효한 표시를 해놓지 않은 경우, 예를 들어 scripts를 preload시 “as”를 안 붙이면, 두번 fetch하게 된다.

cross origin이 없는 preloaded font들은 double fetch될 것이다! 폰트를 preload하여 가져올 경우, crossorigin attribute가 붙어있는지 꼭 확인하자. 안 붙어있으면 두번 다운로드될 것이다. 이들은 CORS anonymous 모드로 요청된다. 현 페이지와 같은 origin의 폰트에도 이 조언은 적용된다. 다른 anonymous fetches에도 적용된다. (예: default로 XHR)

(현재로선) integrity attribute이 붙어있는 자원은 preload된 자원을 재사용하지 못하며 이것은 double fetches를 불러일으킨다. Link elements에서 사용되는 `integrity` attribute는 아직 구현되지 않았고 이에 대한 open spec issue가 이미 나와있다. integrity metadata가 있으면 현 상황으로선, preload된 자원들은 버려진다(/가져오지 못한다). 실배포 상황에서 이는 보안과 성능 중 하나만 택해야하는 중복 요청으로 이어질 수 있다.

마지막으로, double fetches로 이어지지는 않지만, 대체로 따르면 좋은 것은:

모든 것을 preload하려하지 말아라. 대신 특별히 늦게 로딩되는 자원 중 빨리 로딩하고 싶은 것들을 찾고, 이들의 존재를 브라우저에게 preload를 통해 알려라.

Should I just preload all the assets that my page requests in the head? Is there a recommended limit like “only preload ~6 things”?

이는 tools, not rules(도구일 뿐, 꼭 지켜야 하는 규칙은 아님)의 좋은 예이다. 얼마나 preload를 페이지에 로드하는 다른 리소스들의 넥웍 연결 상태(가능한 bandwidth 등 다른 넷웍 상태들)에 영향을 미치는 요소가 될 수 있다.

페이지 뒷부분에서 발견될 것으로 보이지만 가능한한 빨리 가져오는게 중요한 자원들을 preload해라. <script async>는 윈도우의 onload event를 막지만, 스크립트의 주요 번들을 preloading하여 사용할 경우 fetching(가져오는 부분)과 execution(실행되는 부분)이 분리되기 때문에 막히는 문제가 발생하지 않는다. 이미지, 스타일, 폰트, 미디어 등 거의 모두 다(어떤 자원이던) preload할 수 있다. 중요한 것은 페이지의 저자로서 어떤 것들이 페이지의 앞부분서 필요한지 알고 early-fetching을 사용하는 것이다.

Does prefetch have any magical properties you should be aware of? Well, yes.

크롬에선, 다른 페이지들에 대해 prefetch 요청이 진행중일때 페이지에서 유저가 나와도 이 요청들은 종료되지 않는다.

또한, prefetch 요청은 자원의 cacheablity(캐시가능성)과 상관없이 unspecified net-stack cache에 적어도 5분 이상동안 남아있다.

I’m using a custom “preload” implementation written in JS. How does this differ from rel=”preload” or Preload headers?

preload는 js 프로세싱과 실행과 자원을 가져오는 작업을 찢어놓는다. 그렇기 때문에 마크업에 명시되어 있는 preloads들은 크롬의 preload scanner에 의해 최적화 된다. 이는 달리 말해, 많은 경우 preload는(priority적혀 있는) html parser이 태그에 미처 도달하기 전부터 가져오기 시작할 것이다. 이 점은 custom 보통? preload 구현법에 비해 더 좋은 성능을 내게 해준다.

Wait. Shouldn’t we be using HTTP/2 Server Push instead of Preload?

자원들의 로딩 순서를 명확히 알고 있을 때 푸쉬를 사용하고 cache된 자원들이 다시 push 될 수 있는 요청들은 service worker이 intercept 하도록 해라.

자원의 다운로드 시작 시간을 최초 요청 시간과 더 가까이 두기 위해 preload를 사용하라- first, their-party resources 모두에 있어 유용하다.

다시 한 번 이건 ‘때에 따라 달라요’케이스가 될 것이다. Google play store의 장바구니 기능을 구현하고 있다 상상해보자. play.google.com/cart에 요청을 보내면:

Using Preload to load key modules for the page requires the browser to wait for the play.google.com/cart payload in order for the preload scanner to detect dependencies, but after this contains sufficient information to saturate a network pipe with requests for the site’s assets. (사이트의 자원들에 대한 요청들이 넷웍 파이프를 saturate하기까지 충분한 정보가 있다면, 페이지의 key modules를 로딩하기 위해 preload를 사용하려면 브라우저가 dependencies를 감지하고 preload banner play.google.com/cart payload를 대기하고 처음 부팅할 때 제일 좋은 방법은 아니지만 추후 요청에 있어서는 cache와 bandwidth사용에 있어 유용하다. )?? 이상함;;

H/2 Server Push를 사용함으로써 play.google.com/cart 로 요청 보낼 때 넷웍 파이프를 바로 saturate할 수 있지만, 이미 자원들이 http나 service worker cache에 push되고 있을 경우 bandwidth를 낭비하게 될수도 있다. 이 두 접근법은 늘 얻는게 있으면 잃는 것도 생길 것이다.

Push는 매우 소중하지만, preload와 같은 용도로 사용할수는 없다. (Although Push is invaluable, it doesn’t enable all the same use-cases as Preload does.)

preload는 다운로드와 실행을 찢어놓는 장점이 있다. 문서의 onload events덕분에 scripting이 어떤 경우에, 어떤 자원이 언제 적용될지

제어할 수 있다. 이 점은 js bundles를 가져오고 idle blocks에서 실행할 때나 css를 가져오고 적당한 때에 적용할 때 매우 유용하게 사용될 수 있다.

push는 third-party가 제공하는 콘텐츠에서는 사용할 수 없다. 즉시 자원을 보냄으로써 브라우저 자체의 자원 최적화 로직을 효율적으로 단축시킬 수 있다. 명확히 어떤 일을 하고자하는지 알 때는 성능에서의 이점을 가져갈 수 있지만 무엇을 하는지 정확히 모르면서 사용할 경우 성능을 크게 해칠 수 있다.

What is the Link preload header? How does it compare to the preload link tag? And how does it relate to HTTP/2 Server Push?

다른 타입의 link들과 같이, preload link도 html tag나 html header(link preload header)로 명시될 수 있다. 두 경우 모두, preload link는 브라우저가 자원을 메모리 캐시로 로딩 시작하게 한다.(페이지가 높은 신뢰도를 갖고 자원을 사용할 것으로 보여서 preload scanner나 parser이 발견하기까지 기다리지 않을 것이다란 것을 표시)

Financial Times는 link preload header를 사이트에 도입함으로써 masthead image를 보이기까지 1초를 줄였다.

Bottom: with preload, Top: without. Comparison for Moto G4 over 3G: Before: https://www.webpagetest.org/result/170319_Z2_GFR/, After: https://www.webpagetest.org/result/170319_R8_G4Q/

preload link들을 어느 형태로던 사용해도 되지만, 꼭 이해하고 가야할 중요 차이점 한가지가 있다. 스펙에 명시되어 있듯, 많은 서버들은 http header form 내의 preload link를 본 후 http/2 server push를 시작한다.

H/2 Server Push의 성능 이슈는 preloading의 것과는 다르기 때문에 의도치 않게 push들을 trigger하지 않도록 조심해야한다.

header 대신 preload link tags를 사용하거나 header에 ‘nopush’ atttribute를 추가함으로써 원하지 않는 push를 보내는 것을 방지할 수 있다.

How can I feature detect support for link rel=preload?

다음의 snippet을 사용하여 <link rel=”preload”>의 feature detecting을 사용할 수 있다.

const preloadSupported = () => {
const link = document.createElement('link');
const relList = link.relList;
if (!relList || !relList.supports)
return false;
return relList.supports('preload');
};

The FilamentGroup은 async css loading librrary인 loadCSS에서 preload check를 사용하였다.

Can you immediately apply preloaded CSS stylesheets?

당연히 가능하다. preload는 비동기적으로 로딩된 마크업을 지원한다. 다음과 같이 사용하면, <link rel=”preload”>을 사용하여 로딩된 stylesheets는 onload event를 사용하여 현 문서에 바로 적용될 수 있다.

<link rel="preload" href="style.css" onload="this.rel=stylesheet">

더 많은 예제를 볼려면, Yoav Weiss의 Use cases를 봐라.

What else is Preload being used for in the wild?

HTTPArchive에 의하면, <link rel=”preload”> 을 사용하는 대부분의 사이트들은(Teen Vogue와 앞서 언급한 Shopify포함) Web Fonts를 preload하기 위해 사용하고 있다.

LifeHacker, JCPenny와 같은 몇 유명 사이트들은 css를 비동적으로 로딩하기 위해 사용하고 있다.(FilamentGroup’s loadCSS에 의하면)

그리고 current navigation을 위해 PRPL패턴을 사용하기 위해 preload scripts를 사용하는 Twitter.com모바일, Flipkart, Housing와 같은 progressive web apps들이(점점 증가하고 있다) 있다.

기본적인 아이디어는 artifact를 high-granularity로 유지하여 (monolithic bundles이 아니라) 앱의 어느 기능/면에서도 dependencies 로드를 요구하거나 다음으로 필요할 것 같은 것들을 preload하여 cache를 댑혀둘 수(/풀가동 준비) 있도록 하는 것이다.

What is the current browser support for Preload and Prefetch?

canIUse에 의하면 <link rel=”preload”>는 전세계적으로 ~50% 정도의 유저에게 제공되고 있고 Safari Tech Preview에 구현되어 있다. <link rel=”prefetch”>는 71% 정도에게 제공되고 있다.

도움될만한 자료 몇가지 더:

  • Yoav Weiss가 찾은(landing) preload와 맞붙는 css & blocking scripts을 방지하는 크롬의 최신 변경 사항(현재 링크 작동하지 않음)
  • media를 preload 할 때 3가지의 타입 video, audio, track 따라 preload할 수 있도록 기능 추가함
  • Domenic Denicolasms es6 Modules에 preloading 지원이 가능하도록 spec 변경할 방법 모색 중
  • Yoav는 최근 Link header support for “prefetch” 위한 지원을 출시했다. 이를 통해 더 쉽게 다음 navigation위해 필요한 추가 자원에 대한 힌트(resource hints)를 얻을 수 있다.

Further reading on these loading primitives:

With thanks to @ShopifyEng, @AdityaPunjani from Flipkart, @HousingEngg, @adgad and @wheresrhys at the FT and @__lakshya from Treebo for sharing their before/after preload stats.

With many thanks for their technical reviews & suggestions: Ilya Grigorik, Gray Norton, Yoav Weiss, Pat Meenan, Kenji Baheux, Surma, Sam Saccone, Charles Harrison, Paul Irish, Matt Gaunt, Dru Knox, Scott Jehl.

--

--