$30 off During Our Annual Pro Sale. View Details »

CDNフル活用でつくる、高速Webアプリ / Using CDN To Improve Web...

CDNフル活用でつくる、高速Webアプリ / Using CDN To Improve Web Performance

Kazunari Hara

October 23, 2019
Tweet

More Decks by Kazunari Hara

Other Decks in Technology

Transcript

  1. CyberAgent Web Team is now on WebパフォーマンスとプロダクトKPIの相関を可視化 する話 https://developers.cyberagent.co.jp/blog/archives/9540/ アクセシビリティへの取り組み

    https://www.cyberagent.co.jp/way/csr/accessibility/ アメブロ2017 – 大規模サービスhttps化 https://developers.cyberagent.co.jp/blog/archives/7743/
  2. “Spike” - a sharp increase in the magnitude or concentration

    of visitors Spike reasons: - Breaking news - Launch days - Disasters - Bots - System issues
  3. Cache strategy - Keep resources static - Define consistent URLs

    - Set long cache time (TTL) - Divide endpoint if the resource changes frequently - Tagging cacheable resources and purging if necessary
  4. Static: - Same for everyone - Cacheable Dynamic: - Variable

    by user/session - Hard to cache efficiently Keep as many resources static as possible
  5. Keep as many resources static as possible Web App Assets:

    Entry Assets: {
 “id”: “12345”, 
 “title”: “voice” 
 }
 

  6. Most resources of こえのブログ are static since the app was

    made by a Client Side Rendering (CSR) App {
 “id”: “12345”, 
 “title”: “voice” 
 }
 
 Fetch data from JS when necessary
  7. Define consistent URLs using HTTP methods GET - Retrieve resource

    information only - Do not change the state of the resource - Return the same result every time
  8. Define resources as cacheable or not Cacheable: - Most GET

    endpoints Not cacheable: - Few GET endpoints with session - Few POST/PUT/DELETE endpoints
  9. URL example GET / GET /editor GET /src/**/*.js GET /images/**/*.{png|jpeg|svg}

    GET /assets/audios/${userId}/${entryId}/ GET /assets/images/${userId}/${entryId}/ GET /api/entry/${userId}/${entryId}/ POST /api/entry/${userId}/ PUT /api/entry/${userId}/${entryId}/ DELETE /api/entry/${userId}/${entryId}/ GET /api/auth/${userId}/status/ Cacheable
  10. Set cache TTL as long as possible Response header for

    CDN: “Surrogate-Control: max-age=” - Default: 2592000 (30 days) - Fallback: 86400 (1 day)
  11. Set cache TTL as long as possible Response header for

    browsers: “Cache-Control: max-age=” - Default: ※ 120 (2 minutes) - Fallback: 86400 (1 day) ※ Enabled longer cache with service worker
  12. Divide endpoint if the resource changes frequently Entry { “id”:

    “12345”, “title”: “voice”, “view-count”: 2946 } Meta data is immutable View count changes frequently
  13. Divide endpoint if the resource changes frequently Response header for

    stale content: “Stale-While-Revalidate:” CDN Origin Update data in background
  14. Purge the tag when resources have been updated New version

    release curl -XPOST -H “Fastly-Key:${KEY}” \n -H “Accept:application/json” \n “https://api.fastly.com/service/${SERVICE}/pu rge/webapp”
  15. Purge the tag when resources have been updated Data updated

    curl -XPOST -H “Fastly-Key:${KEY}” \n -H “Accept:application/json” \n -H “Surrogate-Key:blogger/ameba entry/12345” \n “https://api.fastly.com/service/...” Cloud Filestore
  16. These key ideas are also useful even when your app

    uses server side rendering (SSR) To be continued in the fastly meetup #3
  17. Cloud Filestore Cloud Storage Cloud Functions Stable Serverless with Fastly

    and GCP - High CDN cache coverage - Event-driven execution - Auto scalling - Deployability
  18. Cloud Storage Static files in Cloud Storage - Scalable -

    As an origin server - HMAC Authentication https://docs.fastly.com/en/guides/google-cloud-storage
  19. “deployability” is an important metric for app quality Over 1,000

    web app releasesin 10 month Over 200 VCL deploymentsin 10 month
  20. Browser cache strategy No Service Worker: - Cache-Control header Service

    Worker: - Precaching - Runtime caching Service Worker is available in most modern browsers today
  21. Web app assets are cached with service worker at first

    visit and are never re-fetched until updated // service-worker.js workbox.precaching.precacheAndRoute([ { url: "images/title_service_header.svg", revision: "6a048b548112674c9e65ed" }, { url: "index.html", revision: "6805b47012688211c81521" }, { url: "src/components/voice-app.js", revision: "fdf10df6f530cbce55c8f5" }, ... ]);
  22. Query normalization import querystring; sub vcl_recv { set req.url =

    querystring.sort(req.url); set req.url = querystring.regfilter_except( req.url, "^(language|format|offset|limit)$" ); } https://docs.fastly.com/vcl/query-string-manipulation/
  23. Vary normalization sub vcl_recv { if (req.http.User-Agent !~ "Edge" &&

    req.http.User-Agent ~ "Chrome/([^.]*)") { set req.http.X-UA = "esm"; } else { set req.http.X-UA = "es5"; } } sub vcl_fetch { set beresp.http.Vary = "Accept-Encoding, X-UA"; } https://docs.fastly.com/vcl/query-string-manipulation/
  24. Basic Auth table customer_keys { "Basic a2FrZXJ1OmthZXJ1": "kakeru", "Basic a2FlcnU6a2FrZXJ1":

    "kaeru" } sub vcl_recv { if (!table.lookup( customer_keys, req.http.Authorization )) { error 401 "Restricted"; } } https://docs.fastly.com/en/guides/basic-authentication
  25. Basic Auth sub vcl_error { if (obj.status == 401) {

    set obj.http.Content-Type = "text/html; charset=utf-8"; set obj.http.WWW-Authenticate = "Basic realm=Secured"; synthetic {"<!DOCTYPE..."}; return (deliver); } } https://docs.fastly.com/en/guides/basic-authentication
  26. Access control acl office_ip_ranges { "192.0.2.0"/24; "198.51.100.4"; } sub vcl_recv

    { if (!(client.ip ~ office_ip_ranges)) { error 403 "Forbidden"; } } https://docs.fastly.com/en/guides/about-acls
  27. Routing to different origins sub assets_origin { set req.backend =

    F_assets_origin; set req.http.host = "${api-assets-domain}"; } sub api_origin { set req.backend = F_api_origin; set req.http.host = "${api-org-domain}"; } sub vcl_recv { if (req.url.path ~ "^/assets/") { call assets_origin; } else if (req.url.path ~ "^/api/") { call api_origin; }... }
  28. Adding surrogate key sub vcl_fetch { declare local var.SurrogateKey STRING;

    set var.SurrogateKey = "assets"; if (req.http.x-url ~ "/audios/standard/([a-z0-9-]{3,24})/([a-zA-Z0-9]+)") { set var.SurrogateKey = var.SurrogateKey + " blogger/" + re.group.1 + " entry/" + re.group.2; } set beresp.http.Surrogate-Key = var.SurrogateKey; } Extracting keys from URL path
  29. Modifying response headers sub vcl_deliver { unset resp.http.via; unset resp.http.server;

    add resp.http.Server-Timing = fastly_info.state {", fastly;desc="Edge time";dur="} time.elapsed.msec; set resp.http.Referrer-Policy = "origin-when-cross-origin"; set resp.http.Strict-Transport-Security = "max-age=31536000"; set resp.http.X-Content-Type-Options = "nosniff"; add resp.http.Content-Security-Policy = "default-src 'self';...” add resp.http.Content-Security-Policy = "upgrade-insecure-requests"; } Removing unnecessary headrers Adding useful headers
  30. @herablog Related documents: - アメブロ2019: こえのブログでのPWA https://developers.cyberagent.co.jp/blog/archives/20506/ - Web App

    Checklist 〜高品質のWebアプリケーションをつくる ために〜 https://speakerdeck.com/herablog/web-app-checklist-2019-at-inside-frontend - こえのブログでのPWA ~ PWA編 ~ https://speakerdeck.com/herablog/pwa-night-vol-dot-4 - こえのブログでのPWA ~ 開発現場編 ~ https://speakerdeck.com/herablog/koe-no-blog-pwa - 最新CDN入門 WEB+DB PRESS Vol.109 https://gihyo.jp/magazine/wdpress/archive/2019/vol109