AWS CloudFront CDN — Simply put
AWS CloudFront CDN — Simply put
CDN is a powerful concept but not a simple one to wrap your mind around. Having CDNs usually offered in modern days as a service, one has to understand the concept, and how it maps into the terminology of the service in use in order to make sense of the terminology of that service provider’s Backoffice configuration application and reference pages.
This post explains the concept of CDN, and dives specifically into the terminology used by CloudFront — the CDN featured by AWS.
AWS aims to be a very generic service. As a result many times some over-architected implementation details leak out to the user, which looks confusing and bloated, until the moment you need that specific feature — which is often not the case.
This post does not aim to be a definite guide to AWS CloudFront, but to explain the system and the terminology, focusing on the more important and commonly used parts. Having this simplification understood and getting one familiar with the terms, one can search for further details in the reference more effectively.
“It’s by far easier to google something if you know how it’s called”
(someone I used to work with a long time ago)
Doesn’t count? Ok, ok. Here:
“If you do not know how to ask the right question, you discover nothing.”
(W. Edwards Deming)
The concept was coined in it’s time as web acceleration. It meant to help web pages load faster, shorten network roundtrip time, and let servers handle more traffic.
Since clients get their response from the closest edge server, the distance between a client and the server that responds it is shorter.
Ideally, the connection between the edge-servers and the source server is kept alive, uses powerful network and computing infra, and well maintained.
In the absolute majority of cases:
— This setup will beat a direct request to the source server.
when the edge-server has a cached version of the resource — it doesn’t only save a request to the origin, but:
— from client’s perspective it will be faster.
we employ more computing power and thus,
— can handle greater traffic.
While one can setup his own CDN, nowadays it’s mostly used as a service by big providers that can invest in relevant infrastructure and still make some profit margin, or on the other hand — big giants that had to invest in their own CDN for their own business, and chose to share it as a service in order to at least reduces their costs, if not turn it to a business model.
AWS CloudFront CDN — Glossary
Please note that this diagram obscures much of the distributed nature of the system. It focus on the objects and actors involved.
The objects and actors used and how they appear in AWS CloudFront, starting from the right.
Viewer — the client that makes the request. Usually it’s a browser or a mobile device, but it can be an API client that consumes HTTP based services (e.g. REST, SOAP, plain HTTP(s), etc.)
Viewer Request — the object that describes the properties of the original request the viewer makes. It’s important to distinct it from origin request (see below).
Distribution — configures the behavior of the entire distributed system from two aspects:
- The system as experienced from the viewer’s perspective. It determines what protocols it supports, how it identifies itself (e.g. ssl certificates),
- The system management — e.g logs, state and defaults (e.g default page).
On top of that, the main parts of a distribution is a collection of origins, and a collections of behaviors (see below).
Origin — the details of a backend source to pull resources from, specifically how to address them(e.g. protocol, address), and with what fault tolerance.
Typically — Origins may be an S3 bucket, an EC2 instance, an ECR service, an ELB, or any service external or internal to AWS that can serve content via HTTP/HTTPS — including another CloudFront distribution... :P
Behavior — the important parts are:
- The properties of the viewer request must display in order to qualify for this behavior — (specifically, method and path)
- The source from which the resource should be pulled- which is either an Origin as explained above — or an Origin Group (see below).
- What of all the properties of the viewer request should be preserved and used as part of the request against the origin (or each origin in the origin group) — specifically, HTTP headers and cookies.
- When cache is applied — what of all the properties of the viewer request should be used be be expressed in the unique cache-key.
There are two ways to provide all the information that makes a behavior: using the modern reusable policies mechanism, and using the legacy settings. The legacy settings required one to specify all these details for each behavior, even if they repeated themselves between different behaviors.
The modern (but more complicated) way — uses predefined reusable parts called policies, of two types: caching-policy and origin-request policies.
Caching Policy —What properties of the Viewer-Request should be part of the cache key, for how long to cache, and what compression to allow (gzip, brotli)— this latter is more related to the response policy, but so far it’s defined here….
Origin-Request Policy — What properties of the Viewer-Request must be preserved in requests to an origin (specifically — QS, headers, cookies).
Origin-Group — A sorted list of origins to try to fetch a resource from, until all the list is exhausted, or the resource is found.
The main difference between a single origin and an origin group, is that if a request is tried against a single origin and the origin replied with 40x or 50x, this reply will be passed to the viewer as is.
An Origin Group lets you create a sequence of origins a request can be tried against until it’s found. This is helpful to create a fallback logic.
AWS CloudFront — the backend interface
- general behavior of the distribution as a web server
- the more important ones are:
what protocols to answer, how to identify itself for incoming requests (ssl), default page and logging.
- origin = a server to pull resources from
- origin group = a sorted list of origins to fallback from one to another in search of a resource
a sorted list of rules that use properties in the incoming request to determine
- what origin or origin group to pull from
- if/how to cache it
supports two types of mechanisms to define rules:
policies — reuses between behavior rules a predefined policies for:
- request — what properties on the incoming request to pass to the origin
- caching — what properties on the incoming request to include in the cache key, for how long to store it and what response encodings to support
legacy — specify manually for each behavior how to cache it and how to pull it (supports only part of functionality in policies)
distribution page — the edge-cases tabs
error pages tab
lets one customize error responses — i.e catch error responses from the tried origin, and channel them to another request on an origin — pretty much like an origin-group, but on the entire distribution level.
This means that if you want to customize your own error pages — you have to:
- setup an origin that will provide them
- use a distinct path that will not collide with other behaviors
Advanced settings to control who gets your content.
At the time of this writing — it’s based only on GEO data of the incoming request.
What happens if the cache servers are loaded with content you don’t want them to serve any more? you invalidate it.
Mind that using this mechanism too much incur additional costs.
And — it does not update instantly, but is a process that could take between minutes to an hour and impact your application’s integrity if not handled correctly.
For this, a lot of the wits around CDN and of bundling tools (which modernly consider CDN a given) come to save you from the need to invalidate resources.
The main philosophy is to treat resources of static content as immutable. Once published — they never change. A new resource version? a new URL.
The main common edge-case is the default page, which I discuss a bit deeper in the best-practices section (see below).
Does not effect functionality. It’s mostly for automation, searching and management purposes.
In early days, we used to hold a domain or subdomain for the static content that points to CDN, and another domain for dynamic content, points to the front load-balancer. However, as web-security progressed this introduced problems with the resulted cross-origins.
Since then, the CDNs have grown and matured. Here’s a summary manifest of the modern and best practices.
- Distinct in your design between resources for long-term cache and resources that require dynamic processing, and express this distinction with a path.
e.g. either /apifor dynamic resources, or /static for static resources. You can get to thinner resolution if you need:
e.g /css /images /js
- Use one domain, point it to your CDN. All requests should pass through it, wither you apply caching to them or not.
Though that’s the base, old habits die hard, and I often come to customers which use a myriad of domains and subdomains. The pains with that are a bloated configuration that is distributed between bundler config, app-server config, deploy-tools config and CDN settings, where all have to agree in order to function well as a one cohesive product.
One domain and path-based rules reduces a lot of this noise.
- Use path-based behavior rules to tell your CDN how to handle requests for each of the resource types identified in stage 1.
If you have to — you can distinct between short-term caching and long term caching.
Mind that most SPA bundling tools mount cache-boosters on resources — and that lets you treat all resources as long-term cache, so — in most cases there’s nothing you have to do.
The edge case is, for example an app that manages resources dynamically, e.g. user-images, but not only.
The practice in this case is to come with your own on-path cache-booster logic, reflected on served URLs any time the users edits their resources.
- The default page —What’s dynamic about it? For the least of it, since it includes references to static resources with cache booster — it has to be updated every time these resources are updated.
The simplest way is to serve it from your dynamic server as a dynamic page. If it’s CPU heavy — apply a very short-term cache.
If that’s not enough — render it to a static page upon update, and serve it dynamically from there.
One step further is to make it as thin and skeletal as you can, serve it as a long-term static content, and use client-side logic to pull dynamic URLs from a truely dynamic service.
Note that just pushes the problem further — when anything in that skeletal seed changes you’ll have to perform cache-invalidation against it.
- CDN as a gateway for micro-services.
This setup is based on a set of behaviors with a zero-cache policy, and means to take advantage only on the networking benefit of the CDN.
In compare to just /api here, one has to get to a thinner resolution and use a prefix per service-group, e.g — for a classic shop -/api/users , /api/catalog , /api/cart , etc.
Then one can have the a behavior for each prefix that consumes resources from a different origin, which can be a different EC2/ECR, or even a different ELB of an EC2 / ECR scale group.
One can use origin-groups that are made of an origin pointing to the production cluster, backed by an origin pointing to the Disaster-Recovery cluster, and/or backed by once of those well designed static-content oupsy-pages.
- Low-Cache Static content
When a page that is very CPU intensive does not require real-time data, e.g. reports that suffer X min latency — one can render it to a static content using some background job, and have the CDN serve it with X minutes cache time.
Often you don’t even have to go that far, and configuring caching for X minutes is enough. The difference is that in the former option the resource is rendered once, where in the latter it will be rendered once per region-master and his edge-servers, plus — if your cached entry is dropped because the CDN’s LRU cache is exhausted — it will be rendered a second time for the regional master.
This post does not aim to be a definite guide to AWS CloudFront, but to explain the system and terminology, it’s usage, focusing on the more important and useful parts. Having this simplification understood and getting one familiar with the terms, one can search for further details in the reference more effectively.
“I see your schwartz is as big as mine, now lets see how well you handle it”
I hope it helps :)
We will contact you as soon as possible.