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
Before you Optimize
- Make sure you have a performance problem.
Before You Optimize
- Measure
- Concurrency or Latency or Volume or Bandwidth or Utilization
- Record that number!
- You need to compare it against future values.
- Record and track original settings and changes made!
- You need to compare performance with your changes!
- 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-Control: no-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
- Servers can use it as a replacement for
Cookie
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:
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.
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)
- 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
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