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

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

2e0e89a34badf79dcff642cb7b5c126f?s=128

Kazunari Hara

October 23, 2019
Tweet

Transcript

  1. CDNフル活用でつくる 高速Webアプリ ~ こえのブログ実装例 ~ Oct. 23 2019, Yamagoya 2019

    Kazunari Hara, CyberAgent Using CDN To Improve Web Performance
  2. CyberAgent Web Team is now on Performance Accessibility Security

  3. 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/
  4. CyberAgent Web Team is now

  5. None
  6. Posting a blog entry just by speaking

  7. こんにちは 今日はFastly 山小屋にきました Cloud Speech-to-Text

  8. こえのブログ is

  9. https://twitter.com/sisidovski/status/1158980641955823616 Lighthouse score with firework

  10. Performance: - Fast - Consistant

  11. “Spike” - a sharp increase in the magnitude or concentration

    of visitors Spike reasons: - Breaking news - Launch days - Disasters - Bots - System issues
  12. When your site is slow...

  13. When your site is down...

  14. Cache helps performance improvement by reusing resources and removing unnecessary

    calculation on servers Origin CDN
  15. こえのブログ is made with Fastly and GCP to maintain backends

    more easily
  16. Cache Stats of こえのブログ

  17. 98% cache hit ratio on CDN

  18. 98% cache hit ratio on CDN if you have cats

  19. 98% cache hit ratio on CDN if you have cats

  20. 99% cache coverage on CDN

  21. 99% cache coverage on CDN if you have cats

  22. 99% cache coverage on CDN if you have cats

  23. 98% cache coverage in browser on repeat view

  24. Fast and consistent even during spike as CDN returns cache

    CDN Origin Client 98%
  25. Less network access on repeat view CDN Origin Client 98%

  26. Cache Strategy

  27. 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
  28. Static: - Same for everyone - Cacheable Dynamic: - Variable

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

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

  30. 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
  31. User data is rendered asynchronousl y Displayed later than ranking

  32. 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
  33. Define consistent URLs using HTTP methods POST/PUT/DELETE - create/update/delete resources

    - Not cacheable
  34. Define resources as cacheable or not Cacheable: - Most GET

    endpoints Not cacheable: - Few GET endpoints with session - Few POST/PUT/DELETE endpoints
  35. 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
  36. 93% of endpoints are cacheable

  37. Set cache TTL as long as possible Response header for

    CDN: “Surrogate-Control: max-age=” - Default: 2592000 (30 days) - Fallback: 86400 (1 day)
  38. 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
  39. Divide endpoint if the resource changes frequently Entry { “id”:

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

    stale content: “Stale-While-Revalidate:” CDN Origin Update data in background
  41. Tagging cacheable resources with surrogate key “webapp” “blogger/ ameba” “entry/12345

    blogger/ameba”
  42. 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”
  43. 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
  44. These key ideas are also useful even when your app

    uses server side rendering (SSR) To be continued in the fastly meetup #3
  45. Backends

  46. Cloud Filestore Cloud Storage Cloud Functions Stable Serverless with Fastly

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

    As an origin server - HMAC Authentication https://docs.fastly.com/en/guides/google-cloud-storage
  48. Cloud Storage Cloud Filestore Event-driven functions Cloud Functions Some actions

    Event Cloud Storage
  49. onDatabaseUpdate Cloud Filestore Cloud Functions Update entryId: 12345 Purge Surrogate-Key:

    entry/12345
  50. onDatabaseDelete Cloud Filestore Cloud Functions Delete entryId: 12345 Purge Surrogate-Key:

    entry/12345 Cloud Storage Delete audio
  51. “deployability” is an important metric for app quality Over 1,000

    web app releasesin 10 month Over 200 VCL deploymentsin 10 month
  52. https://speakerdeck.com/herablog/web-app-checklist-2019-at-inside-frontend?slide=134

  53. Browser Cache

  54. Browser cache strategy No Service Worker: - Cache-Control header Service

    Worker: - Precaching - Runtime caching Service Worker is available in most modern browsers today
  55. 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" }, ... ]);
  56. Other resources are cached as runtime cache and can be

    used when offline CDN
  57. VCL Recipes

  58. Cache key to normalize method=${req.request}; origin=${req.http.host}; url=${req.url}; vary=${req.http.Accept-Encoding}; URL and

    vary can be variable
  59. 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/
  60. 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/
  61. 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
  62. 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
  63. 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
  64. 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; }... }
  65. 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
  66. 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
  67. (CDN) makes your app stable, predictable, efficient, deployable and enjoyable

  68. (CDN) makes your app

  69. @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