CMPUT 404

Web Applications and Architecture

Performance

Created by
Abram Hindle (abram.hindle@ualberta.ca)
and Hazel Campbell (hazel.campbell@ualberta.ca).
Copyright 2014-2023.

Performance

  • Performance is a non-functional requirement.
  • Measured as...
    • Concurrency (# of requests or users at once)
    • Latency (ms)
    • Volume (requests/second)
    • Bandwidth (bytes/second)
    • Utilization (percent)

Before you Optimize

Premature Optimization is the Root of All Evil - Donald Knuth

Before you Optimize

  1. Make sure you have a performance problem.

Before You Optimize

  1. Measure
    • Concurrency or Latency or Volume or Bandwidth or Utilization
  2. Record that number!
    • You need to compare it against future values.
  3. Record and track original settings and changes made!
    • You need to compare performance with your changes!
  4. Run tests more than once!
    • For basic stats you want more than 10 runs
    • For rigorous stats you want 40+

Techniques

  • Caching
  • Reduce # of round trips
  • Reduce download size
  • Asynchronous communication

Caching

  • Caching increases locality
    • Content/data is closer to where it is needed
  • Locality increases available bandwidth
  • Locality decreases latency

Levels of cache

  • CPU
  • Memory (RAM)
  • Disk
  • Network

Levels of HTTP cache

Typically:

  • Browser cache (memory/disk)
  • Near reverse proxy cache server or CDN (Network)
  • Far reverse proxy cache server (Network, but higher latency and lower bandwidth)
  • Original server

Browser Cache

  • Fastest
  • Force-refresh with control-shift-R
  • Browser manages balance between RAM and disk
  • Private windows typically won't use disk at all

HTTP Caching


HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
permissions-policy: interest-cohort=()
last-modified: Tue, 24 Oct 2023 22:48:57 GMT ← general caching
access-control-allow-origin: *
strict-transport-security: max-age=31556952
etag: "653849d9-2258" ← general caching
expires: Thu, 09 Nov 2023 17:32:01 GMT ← general caching
cache-control: max-age=600 ← general caching
x-proxy-cache: MISS ← varnish reverse proxy cache
x-github-request-id: 142A:6FA6:22706B2:302A4E3:654D1537
accept-ranges: bytes
date: Thu, 09 Nov 2023 17:22:07 GMT ← general caching
via: 1.1 varnish ← varnish reverse proxy cache
age: 6 ← cdn reverse proxy cache
x-served-by: cache-yyc1430027-YYC ← cdn reverse proxy cache
x-cache: HIT ← cdn reverse proxy cache
x-cache-hits: 1 ← cdn reverse proxy cache
x-timer: S1699550528.559036,VS0,VE1
vary: Accept-Encoding
x-fastly-request-id: e314eac57fc85817836f68792e20e026d05fd958
content-length: 8792
    

HTTP Caching

  • Hasn't been modified for 15 days 18 hours 33 minutes 4 seconds
  • Expires in 10 minutes
  • ETag = "653849d9-2258"
  • Browser (Edmonton)
    ⮀ Fastly CDN Reverse Proxy (Calgary)
    ⮀ varnish caching reverse proxy (Github)
    ⮀ Server (Github)

Curl Download 1


HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
permissions-policy: interest-cohort=()
last-modified: Tue, 24 Oct 2023 22:48:57 GMT
access-control-allow-origin: *
strict-transport-security: max-age=31556952
etag: "653849d9-2258"
expires: Thu, 09 Nov 2023 17:32:01 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: 142A:6FA6:22706B2:302A4E3:654D1537
accept-ranges: bytes
date: Thu, 09 Nov 2023 17:47:04 GMT
via: 1.1 varnish
age: 0
x-served-by: cache-yyc1430027-YYC
x-cache: HIT
x-cache-hits: 1
x-timer: S1699552025.741855,VS0,VE80
vary: Accept-Encoding
x-fastly-request-id: 9c31959e0bbdd1340c30b3964a0424d1056d75c3
content-length: 8792
    

Curl Download 2


HTTP/2 200
server: GitHub.com
content-type: text/html; charset=utf-8
permissions-policy: interest-cohort=()
last-modified: Tue, 24 Oct 2023 22:48:57 GMT
access-control-allow-origin: *
strict-transport-security: max-age=31556952
etag: "653849d9-2258"
expires: Thu, 09 Nov 2023 17:32:01 GMT
cache-control: max-age=600
x-proxy-cache: MISS
x-github-request-id: 142A:6FA6:22706B2:302A4E3:654D1537
accept-ranges: bytes
date: Thu, 09 Nov 2023 17:47:29 GMT
via: 1.1 varnish
age: 24
x-served-by: cache-yyc1430034-YYC
x-cache: HIT
x-cache-hits: 1
x-timer: S1699552049.047416,VS0,VE1
vary: Accept-Encoding
x-fastly-request-id: a93c5cf45589d939b9673207b5e90e84d8b34126
content-length: 8792
    

What changes?

  • ETag, Last-Modified & maybe Content-Length
    • Changed by server whenever content (file) changes.
  • Expires
    • Changed by server whenever reverse-proxy gets a new copy.
  • Age, Date, Via, etc...
    • Set by reverse-proxy on every request.

HTTP Headers For Caching

  • Last-Modified Response, tells client when the content was last modified.
    • Browser can use the rule of thumb: check back after 1/10th of the time since it was modified.

HTTP Headers For Caching

  • Etag in response & If-None-Match in next request
    • Rather than sending the content again, server can reply 304 Not Modified
    • Etag (entity tag) should be a unique identifier
      • Changes when content changes
    • Don't have to guess or estimate time content will be valid
      • Useful if content udpates are unpredictable

HTTP Headers For Caching

  • Etag in response & If-Match in next request
    • Used for PUT/PATCH/DELETE
      • Make sure that content doesn't change between read and write
      • Similar to a transaction but for only one URL
      • If the Etag has changed, respond with 412 Precondition Failed
    • Can be used with GET but not really useful

HTTP Headers For Caching

  • Last-Modified in response & If-Modified-Since in next request
    • Rather than sending 200 OK, server can reply 304 not modified
    • If the server would have responded with anything else than 200 OK, it should still proceed with that response
      • A 404 should still be a 404!
    • Can be used to compute the 1/10 rule

HTTP Headers For Caching

  • Expires in response
    • Tells user-agent exactly when to stop caching the content
    • Simple
    • Easy to use
    • Can cause problems if someone's clock is set wrong.

HTTP HEAD for Caching

  • Like GET, but the server only responds with headers
  • Useful for checking etag, Last-Modified, etc.
  • User-agent will request again if it sees a change.
  • Also useful for checking size before actually requesting something that could be huge.

Cache-Control

  • Cache-Control: max-age=3600, must-revalidate
    • Cache for 1 hour max
  • Cache-Control: no-cache
    • Do not cache
  • Cache-Control: no-store
    • Response should never be stored, only valid once
  • Cache-Control: private
    • Only browser's local cache should cache it

Cache-Control

  • Cache-Control: public
    • Allow caching even if responding to Authorization:
  • Cache-Control: no-transform
    • Proxies must not recompress images
  • Cache-Control: immutable
    • Promise that response won't change until max-age
  • Cache-Control: stale-if-error
    • Reuse cached copy on 500, 501, 502, 503 error even if old

HTTP ETag

  • How do you make it?
  • Strong: Hash funciton (e.g. SHA1)
  • Weak: etagvalue + some value
    • Use to avoid expensive hashing of huge files.
  • Keep it short!

Dangers of Etag

State vs Caching

  • Server tracking state and changing its response depending on state prevents effective caching
  • Authentication is a kind of state
    → Authentication prevents caching
  • Limit stateful and authenticated server content
    • Separate out content that needs to be stateful/authenticated from content that can be public and shared!

Avoid Cookies & Auth for Static Content

  • Static content = content that doesn't change or rarely changes
  • Examples:
    • Images
    • CSS
    • Fonts
    • Scripts

Avoid Cookies & Auth for Static Content

  • Use GET for static content
  • Use cache-busting: version number or hash in URL for static content

Avoid Cookies & Auth for Static Content

  • Use Cache-Control: public, immutable, max-age=604800 for static content
  • Put all authenticated content in a top level folder like server.com/x/,
    then use Set-Cookie: name=value; Path=/x/
  • Dynamic and static content can also be placed on two different servers, but it is less efficient

Round Trips

  • Increase latency
  • Happen whenever you have to wait for a response before making the next request
  • Example:
    • Have to wait for DNS response before starting TCP connection
    • Have to wait for TCP to connect before starting TLS connection
    • Have to wait for TLS to connect before making HTTP request
    • Have to wait for HTML before making request for CSS
    • Have to wait for CSS before making request for background image

Reducing Round Trips

  • Use HTTP/3: TCP is eliminated, TLS/HTTP requests can be done in fewer round trips
  • HTTP/2 and HTTP/3 can do multiple downloads at the same time with a single connection

Reducing Round Trips

  • Use fewer servers → look up fewer domain names, make fewer connections
  • Gmail example:
    • gmail.com
    • mail.google.com
    • fonts.gstatic.com
    • ssl.gstatic.com
    • www.gstatic.com
    • aa.google.com
    • lh3.googleusercontent.com
    • apis.google.com
    • chat.google.com
    • peoplestackwebexperiments-pa.clients6.google.com
    • peoplestack-pa.clients6.google.com
    • people-pa.clients6.google.com
    • play.google.com
    • addons-pa.clients6.google.com
    • signaler-pa.clients6.google.com
    • contacts.google.com
    • mail-ads.google.com
    • clients6.google.com
    • meet.google.com

Reducing DNS Round-Trips

  • Avoid CNAME
  • Allow DNS caching
    • Don't use super short TTLs
  • Provide multiple A/AAAA records in a single response

Reducing Round Trips

  • Use asynchronous loading!
  • Allow the browser to make multiple requests at once...
  • <script async src="whatever.js"></script>
  • Use ES6 modules rather than plain JS files
  • Include all CSS/JS/assets that are needed immediately directly in first HTML load
  • Use JS to async load CSS and other assets that aren't needed immediately

Async is better than Sync

  • Other things can be processed or loaded while something is loading
  • Fewer requests are blocked (waiting for something else to finish first)
  • Synchronous = +1 round trip for each request
  • Asyncronous = Parallelizable
  • Synchronous = Serialized

Reducing Round Trips

  • Avoid CSS imports
  • Use JS "compiler" like webpack
    • For deep requirement trees
    • Only if necessary: has some big disadvantages for debugging etc.

Reducing Round Trips

  • Avoid HTTP redirects!

Reduce Request Size

  • HTTP/1.1
    • Avoid too many headers
    • Avoid big cookies
    • Avoid long URIs
  • HTTP/2 & HTTP/3
    • Automatically applies header compression
    • If headers don't change, they won't be re-sent!
    • Lots of headers, big cookies and big URIs not a big deal!

Minimize Resource Size

  • HTTP Compression
    • Content-Encoding: gzip
    • Content-Encoding: deflate
    • Designed for compressing text (HTML, JS, CSS, JSON...)

Minimize Resource Size

  • What if it's not text?
  • Image Compression
    • Old:
      • Photos - JPEG
      • Drawings - GIF
      • Lossless - PNG
    • New: WebP
    • Newer: AVIF

Image Compression

  • AVIF and WebP offer better compression
  • But... they aren't universally supported.

Fallback example from MDN: use AVIF if supported, otherwise use WebP if supported, otherwise use JPG.

<picture>
    <source srcset="photo.avif" type="image/avif" />
    <source srcset="photo.webp" type="image/webp" />
    <img src="photo.jpg" alt="photo" />
  </picture>

Use SVG

  • SVG is an image format for vector images (made of shapes), instead of raster images (made of pixels)
  • HTML-like XML, can use text compression (gzip/deflate)
    • Even supports CSS!
  • Usually much smaller than equivalent PNG/GIF/JPEG!

Avoid POST

  • POST is not idempotent ("safe" in REST terms)
  • POST is not cacheable
  • POST is dynamic
  • POST->redirect->GET: at least a couple of round trips
  • POST smashes all the performance infrastructure into a fine powder and blows it into your face.

Check for Errors

  • 403s, 404s, 410s, etc. are all slow
    • Often the server works harder to serve an error than it does to serve real content
    • Errors are often not cached or cacheable
    • Errors cause logging overhead (I/O), reporting, and rarely used code to run
    • Errors are worthless! You don't even get anything!

Encoding

  • There are often more efficient formats than JSON
  • Don't send an audio file as ASCII text!
  • Do you need lossless or is lossy okay?
  • Do you need XML? JSON? CSV? Binary?

Library CDNs

  • Including JavaScript libraries from their home page or a CDN
  • Can also include other resources like fonts
  • Benefit: User may already have it cached if its popular
  • Disadvantage: What if they get hacked?
  • Disadvantage: What if they are slower than you?
  • Disadvantage: What if you lose locality?

Tools

  • Your browser's developer pane!
  • Google Lighthouse Chrome Plugin

Resources

  • MDN on Web performance
  • Google Lighthouse's documentation
  • TODO: find more guides that are actually up to date, and that aren't selling something. See the old slides.

License

Copyright 2014-2023 ⓒ Abram Hindle

Copyright 2019-2023 ⓒ Hazel Victoria Campbell and contributors

Creative Commons Licence
The textual components and original images of this slide deck are placed under the Creative Commons is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Other images used under fair use and copyright their copyright holders.

License


Copyright (C) 2019-2023 Hazel Victoria Campbell
Copyright (C) 2014-2023 Abram Hindle and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN.

01234567890123456789012345678901234567890123456789012345678901234567890123456789