Share
Embed
Transcript
69 slides
-
CDNフル活用でつくる 高速Webアプリ ~ こえのブログ実装例 ~ Oct. 23 2019, Yamagoya 2019
Kazunari Hara, CyberAgent Using CDN To Improve Web Performance -
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/ -
“Spike” - a sharp increase in the magnitude or concentration
of visitors Spike reasons: - Breaking news - Launch days - Disasters - Bots - System issues -
Cache helps performance improvement by reusing resources and removing unnecessary
calculation on servers Origin CDN -
Fast and consistent even during spike as CDN returns cache
CDN Origin Client 98% -
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 -
Static: - Same for everyone - Cacheable Dynamic: - Variable
by user/session - Hard to cache efficiently Keep as many resources static as possible -
Keep as many resources static as possible Web App Assets:
Entry Assets: { “id”: “12345”, “title”: “voice” } -
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 -
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 -
Define resources as cacheable or not Cacheable: - Most GET
endpoints Not cacheable: - Few GET endpoints with session - Few POST/PUT/DELETE endpoints -
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 -
Set cache TTL as long as possible Response header for
CDN: “Surrogate-Control: max-age=” - Default: 2592000 (30 days) - Fallback: 86400 (1 day) -
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 -
Divide endpoint if the resource changes frequently Entry { “id”:
“12345”, “title”: “voice”, “view-count”: 2946 } Meta data is immutable View count changes frequently -
Divide endpoint if the resource changes frequently Response header for
stale content: “Stale-While-Revalidate:” CDN Origin Update data in background -
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” -
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 -
These key ideas are also useful even when your app
uses server side rendering (SSR) To be continued in the fastly meetup #3 -
Cloud Filestore Cloud Storage Cloud Functions Stable Serverless with Fastly
and GCP - High CDN cache coverage - Event-driven execution - Auto scalling - Deployability -
Cloud Storage Static files in Cloud Storage - Scalable -
As an origin server - HMAC Authentication https://docs.fastly.com/en/guides/google-cloud-storage -
onDatabaseDelete Cloud Filestore Cloud Functions Delete entryId: 12345 Purge Surrogate-Key:
entry/12345 Cloud Storage Delete audio -
“deployability” is an important metric for app quality Over 1,000
web app releasesin 10 month Over 200 VCL deploymentsin 10 month -
Browser cache strategy No Service Worker: - Cache-Control header Service
Worker: - Precaching - Runtime caching Service Worker is available in most modern browsers today -
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" }, ... ]); -
Other resources are cached as runtime cache and can be
used when offline CDN -
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/ -
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/ -
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 -
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 -
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 -
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; }... } -
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 -
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 -
@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