Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Practical Serverless & Edge Computing

Practical Serverless & Edge Computing

Video: https://youtu.be/kKfW9LLL7Rc?t=196

While serverless is way beyond its hype point, it is still an underestimated technology that could open up new approaches in how we build our apps. In this talk I'm going to give an overview of practical applications of serverless JS. We're going to cover serverless frontend microservices, authorization methods, Smart CDNs, caching (including stale-while-revalidate) with Vercel and Cloudflare Workers. I'll illustrate these methods with practical examples from the tools we build internally at resume.io OG image generation, PDF/DOCX rendering, automatic Critical CSS injection and more.

01bf97df3784afa10fc2b561e251a41c?s=128

Alexey Taktarov

October 16, 2020
Tweet

Transcript

  1. practical serverless & edge computing. alexey taktarov · @mlfrg

  2. 2 alexey taktarov / @mlfrg λ. co-founded resume.io, 8.5M+ resumes

    online λ. I write code (JS / Ruby) occasionally these days… λ. I love designing backends!
  3. 3 wouter: a tiny 1.3KB React router github.com/molefrog/wouter react-colorful: a

    1.8KB color picker github.com/omgovich/react-colorful an amateur OSS contributor react-colorful — a micro color picker component for React
  4. 1. the state of serverless in 2020

  5. 5 fundamental problems in networking increasing throughput horizontal scaling, network

    configuration
  6. 5 fundamental problems in networking increasing throughput horizontal scaling, network

    configuration reducing latency caching, geo-distribution
  7. 6 serverless* aims to solve both * Functions-as-a-Service → https://martinfowler.com/articles/serverless.html

  8. 7 λ

  9. 7 λ function calls (http requests / events)

  10. λ http reqs λ. ∞-scalable λ. no ops needed λ.

    pay for the compute you need (down to 100ms) 8
  11. λ http reqs λ. ∞-scalable λ. no ops needed λ.

    pay for the compute you need (down to 100ms) λ λ λ λ λ λ λ λ λ λ λ λ λ λ λ λ λ λ λ λ 8
  12. 9 AWS Lambda

  13. 9 AWS Lambda

  14. 9 general-purpose FaaS providers AWS Lambda, Google Cloud, Azure, IBM,

    Yandex …
  15. 9 general-purpose FaaS providers AWS Lambda, Google Cloud, Azure, IBM,

    Yandex … → https://fauna.com/blog/comparison-faas-providers deployment platforms + SmartCDNs Vercel (formerly Zeit / Now), Netlify better DX, less configurable built on internally
  16. 9 general-purpose FaaS providers AWS Lambda, Google Cloud, Azure, IBM,

    Yandex … → https://fauna.com/blog/comparison-faas-providers deployment platforms + SmartCDNs Vercel (formerly Zeit / Now), Netlify better DX, less configurable built on internally edge computing Cloudflare Workers AWS Lambda@Edge EdgeEngine
  17. 10 Vercel and Cloudflare λ. CDN-based λ. great developer experience*

    * Serverless Framework is another great tech worth looking at
  18. 2. general-purpose lambdas with Vercel 11

  19. import fetch from 'node-fetch' export default async (req, res) "=>

    { const name = req.query.name "|| 'wouter' const response = await fetch(githubRepo(name)) const repo = await response.json() res.json({ stars: `${repo.stargazers_count} stars!` }) } api/stars.js 12
  20. 1. NPM deps are bundled import fetch from 'node-fetch' export

    default async (req, res) "=> { const name = req.query.name "|| 'wouter' const response = await fetch(githubRepo(name)) const repo = await response.json() res.json({ stars: `${repo.stargazers_count} stars!` }) } 1 api/stars.js 12
  21. 1. NPM deps are bundled import fetch from 'node-fetch' export

    default async (req, res) "=> { const name = req.query.name "|| 'wouter' const response = await fetch(githubRepo(name)) const repo = await response.json() res.json({ stars: `${repo.stargazers_count} stars!` }) } 1 2. async/await is supported 2 api/stars.js 12
  22. 1. NPM deps are bundled import fetch from 'node-fetch' export

    default async (req, res) "=> { const name = req.query.name "|| 'wouter' const response = await fetch(githubRepo(name)) const repo = await response.json() res.json({ stars: `${repo.stargazers_count} stars!` }) } 1 2. async/await is supported 3. req and res provide convenient helper methods 3 2 api/stars.js 12
  23. 13 > vercel zero-config deployment with just one command https:"//gh-stars.vercel.app/api/stars

  24. 14 a case study server-side PDF rendering microservice

  25. 15

  26. 15

  27. 16 λ. the preview must update in the real-time λ.

    react-pdf renders preview in a WebWorker λ. IE isn’t supported → need an SSR fallback
  28. 17 λ : (json) "-> pdf

  29. 18 challenge #1 how to authorise requests? backend browser lambda

    → →
  30. 19 authorisation decoupled JSON Web Tokens signed with asymmetric ciphers

    (RSA/ECD) → https://vercel.com/docs/solutions/authentication
  31. 20 eyJhbGciOiJIUzI1NiI sInR5cCI6IkpXVCJ9. eyJ1aWQiOiIxIiwicGx uIjoicHJlbWl1bSIsIm lhdCI6MTUxNjIzOTAyM n0. o1ayTFNg0eVZSQvd4T RUNRgSBa2MK2LCAUtj yIx1ydk

    ← header → https://jwt.io/
  32. 21 eyJhbGciOiJIUzI1NiI sInR5cCI6IkpXVCJ9. eyJ1aWQiOiIxIiwicGx uIjoicHJlbWl1bSIsIm lhdCI6MTUxNjIzOTAyM n0. o1ayTFNg0eVZSQvd4T RUNRgSBa2MK2LCAUtj yIx1ydk

    ← payload → https://jwt.io/ { "uid": "1", "pln": "premium", "iat": 1516239022 }
  33. 22 eyJhbGciOiJIUzI1NiI sInR5cCI6IkpXVCJ9. eyJ1aWQiOiIxIiwicGx uIjoicHJlbWl1bSIsIm lhdCI6MTUxNjIzOTAyM n0. o1ayTFNg0eVZSQvd4T RUNRgSBa2MK2LCAUtj yIx1ydk

    ← signature → https://jwt.io/ issue: private key verify: public key
  34. 23 backend issues JWT signs with the secret key browser

    passes JWT to the lambda Authorization header lambda user info token authenticity token lifetime (iat)
  35. 24 challenge #2 how to reuse already allocated resources?

  36. 25 export default async (req, res) "=> { const db

    = await connectToDB() const user = await db.getUserById(req.query.id) }
  37. 25 export default async (req, res) "=> { const db

    = await connectToDB() const user = await db.getUserById(req.query.id) } ← latency!
  38. 26 reusing resources between calls λ. one lambda can only

    process 1 request at a time λ. a lambda could be reused between requests λ λ λ λ λ λ lambda instance #1 lambda instance #2 λ
  39. 27 let dbPool = null const connectToDB = async ()

    "=> { if (!dbPool) { dbPool = createConnectionPool() } return await dbPool.acquire() } ← could be reused between requests
  40. 28 challenge #3 DRY-ing up the repetitive code

  41. 29 const useAuthMiddleware = fn "=> (req, res, ""...args) "=>

    { if (!jwtValid) { const errorMessage = "Not Authorized!" return res.status(402).json({ errorMessage }) } return fn(req, res, ""...args) } export default useAuthMiddleware(lambda) Remember: lambdas are just plain functions!
  42. 30 PDF rendering lambda performance λ. responds within 400ms–1.6s (depending

    on a document size) λ. no downtime since March, 2019 λ. processes ~300GB/m worth of requests λ. conversion rate increased by 0.2%
  43. 31 a case study instant resume image preview lambda

  44. 32 ← image preview

  45. 33 λ : (id) "-> jpeg

  46. 33 λ : (id) "-> jpeg could take up to

    2s to render the image…
  47. 3. Smart CDNs

  48. 35 Now Vercel is a CDN that is also capable

    of executing code. Guillermo Rauch, Vercel
  49. 36 response.setHeader('Cache-Control', 's-maxage=86400') Vercel will cache the image in 70+

    locations around the world for 24 hours → https://vercel.com/docs/edge-network/caching
  50. 37 → https://vercel.com/docs/edge-network/regions λ SFO1 760ms cache MISS: call lambda

  51. 37 → https://vercel.com/docs/edge-network/regions λ SFO1 760ms cache MISS: call lambda

    cache HIT! SFO1 20ms
  52. 37 → https://vercel.com/docs/edge-network/regions λ SFO1 760ms cache MISS: call lambda

    ARN1 800ms cache MISS: call lambda cache HIT! SFO1 20ms
  53. 38 https:"//ssr.resume.tools/to-image/ssid-vGP2z-1.jpeg?version=105324 page number jpeg / png / webp change

    to invalidate
  54. 39 another example that uses edge caching in Vercel random

    avatar generator
  55. 40 λ. a Vercel lambda builds SVGs from the set

    of pre-made parts: heads, eyes, hairs λ. the result is cached forever in SmartCDN λ. PRNG is initialized with the name as a seed https:"//headsome.resume.tools/alex.svg
  56. 40 λ. a Vercel lambda builds SVGs from the set

    of pre-made parts: heads, eyes, hairs λ. the result is cached forever in SmartCDN λ. PRNG is initialized with the name as a seed https:"//headsome.resume.tools/alex.svg
  57. 41 challenge #4 but the first request is still slow!

  58. A B B B B 42 → https://vercel.com/docs/edge-network/caching#stale-while-revalidate Cache-Control: ’s-maxage=60,

    stale-while-revalidate=600' serve cached version, but revalidate in the background A 1 min ← revalidates without blocking the request 10 min A A C D E 0 min
  59. λ. request time is critical λ. the content changes infrequently

    λ. does not have to be always fresh when to use stale-while-revalidate OG image preview rarely changes, but has to be fast!
  60. 4. Edge Computing w/ Cloudflare Workers

  61. origin SFO

  62. origin SFO client

  63. origin SFO edge FRA client

  64. origin SFO edge FRA client Cloudflare has 200+ edge locations

    around the globe → https://www.cloudflare.com/network/
  65. 46 running JS on the edge λ. V8 Isolate: cold

    starts in 5ms λ. can modify origin request / response λ. no filesystem, no Node modules λ. only pure JS packages λ. Web APIs: Crypto, fetch, URL etc. → https://developers.cloudflare.com/workers/
  66. 47 a case study Injecting «Above-the-Fold» CSS on the fly

  67. 48 λ. CSS files are render-blocking λ. «Above-the-Fold» CSS could

    be inlined to speed up the page load λ. higher PageSpeed → better SEO → https://web.dev/defer-non-critical-css/
  68. 49 <link rel="stylesheet" href="styles.css">

  69. 50 <style type="text/css"> .hero{background-color: #ADD8E6;color: #444;cursor: pointer;""... "</style> <link rel="preload"

    href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  70. 50 <style type="text/css"> .hero{background-color: #ADD8E6;color: #444;cursor: pointer;""... "</style> <link rel="preload"

    href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> «Critical» CSS of the above-the-fold content
  71. 51 challenge #1 modify origin’s HTML? → https://developers.cloudflare.com/workers/runtime-apis/html-rewriter

  72. 51 challenge #1 modify origin’s HTML? → https://developers.cloudflare.com/workers/runtime-apis/html-rewriter HTMLRewriter class!

    based on lol-html — a low output latency rewriter written in Rust
  73. 52 async function handleRequest(req) { const originResponse = await fetch(req)

    return new HTMLRewriter() .on("div", new MyDivHandler()) .transform(originResponse) } addEventListener("fetch", event "=> { event.respondWith(handleRequest(event.request)) })
  74. 1. inspired by ServiceWorkers 1 52 async function handleRequest(req) {

    const originResponse = await fetch(req) return new HTMLRewriter() .on("div", new MyDivHandler()) .transform(originResponse) } addEventListener("fetch", event "=> { event.respondWith(handleRequest(event.request)) })
  75. 1. inspired by ServiceWorkers 1 2. fetches the response from

    the origin 52 async function handleRequest(req) { const originResponse = await fetch(req) return new HTMLRewriter() .on("div", new MyDivHandler()) .transform(originResponse) } addEventListener("fetch", event "=> { event.respondWith(handleRequest(event.request)) }) 2
  76. 1. inspired by ServiceWorkers 1 2. fetches the response from

    the origin 52 async function handleRequest(req) { const originResponse = await fetch(req) return new HTMLRewriter() .on("div", new MyDivHandler()) .transform(originResponse) } addEventListener("fetch", event "=> { event.respondWith(handleRequest(event.request)) }) 2 3 2. rewriter allows to modify individual elements using a «visitor» pattern
  77. 53 request → https://github.com/addyosmani/critical

  78. 53 request fetch origin → → https://github.com/addyosmani/critical

  79. 53 request fetch origin → critical CSS → → https://github.com/addyosmani/critical

  80. 53 request fetch origin → critical CSS → → rewrite

    HTML → https://github.com/addyosmani/critical
  81. 53 request fetch origin → critical CSS → → rewrite

    HTML response → → https://github.com/addyosmani/critical
  82. 53 request fetch origin → critical CSS → → rewrite

    HTML response → → https://github.com/addyosmani/critical headless-chrome? not supported by workers… →
  83. 53 request fetch origin → critical CSS → → rewrite

    HTML response → → https://github.com/addyosmani/critical headless-chrome? not supported by workers… → Vercel lambda running headless-chrome! λ
  84. called with waitUntil() 54 → https://developers.cloudflare.com/workers/learning/fetch-event-lifecycle origin server Rails app

    CF worker fetch the origin KV → CSS client Vercel lambda extract CSS w/ Chrome
  85. «Critic» performance λ. CPU time ~5.3ms (99 pct) λ. processes

    3M+ reqs/m λ. SEO clicks doubled Core Web Vitals report, green shows the number of webpages that are considered “good” → https://search.google.com/ worker
  86. 5. Conclusion

  87. 57 Cloudflare Workers λ. SSR, APIs, websites λ. fine-grained app

    proxies Vercel, Netlify and alternatives λ. server-side rendering λ. APIs λ. dynamic websites with infrequent updates: blogs, SEO pages λ. things that use canvas, headless chrome, native packages etc. λ. low-latency / HA APIs λ. real-time apps w/ WebSockets* * https://blog.cloudflare.com/introducing-workers-durable-objects/
  88. 58 For the longest time, CDNs have been treating the

    "origin" as an opaque black box. It's now possible to push content directly to the network and design frameworks that optimize for this capability. Guillermo Rauch, Vercel
  89. 59 λ. deno.land website built w/ Vercel & CF Workers

    λ. Using node-canvas with Vercel λ. chrome-aws-lambda package additional resources appendix font Inter · icons Radix Icons Alexey Taktarov · molefrog.com · github.com/molefrog · @mlfrg