Cloud CDN: Caching Right at the Edge, in Front of Your Load Balancer
Every request your backend doesnโt have to serve is a request that canโt be slow, canโt add load, and canโt fail because your backend happened to be under pressure at that moment. Thatโs the entire value proposition of a CDN, and Cloud CDNโs specific implementation detail worth understanding is that itโs not a bolted-on separate product โ itโs a caching layer built directly into GCPโs Global External Application Load Balancer, sitting at Googleโs edge points of presence around the world, caching responses close to where users actually are.
Because itโs this tightly integrated, enabling it is close to a checkbox โ but getting real value from it, and avoiding the genuinely bad failure mode (serving stale or, worse, wrong content to the wrong users), requires understanding cache keys, cache modes, and invalidation deliberately rather than flipping the switch and hoping for the best.
How Cloud CDN Sits in the Request Path
User request โ โผNearest Google edge point of presence โ โผCache lookup (based on cache key) โ โโโโดโโโ HIT MISS โ โ โ โผ โ Forward to origin (your backend service) โ โ โ โผ โ Origin responds, response cached per policy โ โ โผ โผServe response directly from edge โ no origin round tripA cache hit is served entirely from Googleโs edge infrastructure without your backend ever seeing the request โ this is the actual mechanism behind both the latency improvement (edge is physically closer to the user than your origin) and the load reduction (your origin only handles cache misses, not every single request).
Cache Modes: Who Decides What Gets Cached
# CACHE_ALL_STATIC โ cache static content automatically based on file extension/content-typegcloud compute backend-services update my-backend-service \ --global \ --cache-mode=CACHE_ALL_STATIC
# USE_ORIGIN_HEADERS โ respect your origin's Cache-Control headers exactlygcloud compute backend-services update my-backend-service \ --global \ --cache-mode=USE_ORIGIN_HEADERS
# FORCE_CACHE_ALL โ cache everything regardless of headers (use with real caution)gcloud compute backend-services update my-backend-service \ --global \ --cache-mode=FORCE_CACHE_ALLCACHE_ALL_STATIC is the sensible default for content thatโs obviously cacheable (images, CSS, JS, fonts) without requiring your application to set explicit headers. USE_ORIGIN_HEADERS puts your backend in full control, honoring Cache-Control, Expires, and validators your application already sets โ the right choice once your application has deliberate caching headers and you want the CDN to respect them exactly rather than guess. FORCE_CACHE_ALL overrides everything, including responses your application explicitly marked as private or no-cache, and is genuinely dangerous for any backend serving personalized or authenticated content โ this mode has caused real incidents where one userโs personalized response got cached and served to a different user entirely.
Cache Keys: The Setting That Determines What Counts as โThe Same Requestโ
The cache key is what the CDN uses to decide whether two requests should return the same cached response โ get this wrong and you either fragment your cache uselessly or, more dangerously, serve one userโs response to another.
gcloud compute backend-services update my-backend-service \ --global \ --cache-key-include-protocol \ --cache-key-include-host \ --cache-key-query-string-whitelist=category,pageBy default, the full query string is part of the cache key, which means /products?utm_source=twitter and /products?utm_source=email are cached as entirely separate entries despite returning identical content โ a common and entirely avoidable cache-fragmentation problem. Whitelisting only the query parameters that actually affect the response (category, page in this example) while excluding tracking parameters (utm_source, fbclid) dramatically improves cache hit rate for pages where marketing tracking parameters are common but irrelevant to the actual content served.
Cookies present a similar, often overlooked trap: if a response varies by a cookie value (a session ID, an A/B test bucket) but the cache key doesnโt account for it, one userโs cookie-personalized response can end up cached and served to a different user entirely โ precisely the kind of subtle misconfiguration that doesnโt show up in testing but becomes a real problem the first time itโs triggered by two users hitting the same edge cache in close succession.
Signed URLs: Caching Content That Still Needs Access Control
A common misconception is that caching and access control are incompatible โ Cloud CDN supports signed URLs specifically to cache content that still requires time-limited, per-user authorization:
from google.cloud.compute_v1 import UrlMapsClientimport datetime, base64, hashlib, hmac
def sign_url(url, key_name, key_value, expiration): expiration_ts = int((datetime.datetime.now() + expiration).timestamp()) url_to_sign = f"{url}?Expires={expiration_ts}&KeyName={key_name}" decoded_key = base64.urlsafe_b64decode(key_value) signature = hmac.new(decoded_key, url_to_sign.encode(), hashlib.sha1).digest() encoded_sig = base64.urlsafe_b64encode(signature).decode() return f"{url_to_sign}&Signature={encoded_sig}"This lets you cache a video file or a licensed asset at the edge while still requiring a valid, time-limited signature on every request โ the CDN caches the underlying content once, but each user still needs their own valid signed URL to actually retrieve it, and an expired or invalid signature is rejected regardless of whether the content is cached.
Cache Invalidation: The Hard Part Every CDN Shares
# Invalidate a specific cached pathgcloud compute url-maps invalidate-cdn-cache my-url-map \ --path="/images/logo.png"
# Invalidate everything under a path prefixgcloud compute url-maps invalidate-cdn-cache my-url-map \ --path="/api/*"Invalidation is the operation every caching system struggles with, and Cloud CDN is no exception โ a wildcard invalidation of a broad path is a blunt instrument that also evicts content that didnโt actually change, temporarily increasing origin load as everything under that path gets re-fetched. The better long-term pattern, once traffic and content volume justify the engineering effort, is versioned asset URLs (app.a3f92c.js rather than app.js) where a content change produces a genuinely new URL โ this sidesteps invalidation entirely for versioned assets, since the old URL simply stops being referenced rather than needing active cache eviction.
Cache Hit vs Cache Miss: The Latency Difference Made Concrete
Abstract descriptions of โfasterโ donโt stick the way a concrete timing comparison does.
The first request from any user near a given edge location pays the full origin round-trip cost โ thereโs no way around that for genuinely new content. Every subsequent request from any user served by that same edge location, though, gets the cached response with no origin involvement at all, which is both the latency win (tens of milliseconds instead of hundreds) and the load-reduction win (the origin literally never sees these requests). This is why cache hit ratio is the single most important CDN metric to track โ a low hit ratio means most requests are still paying the full origin round-trip cost, and the CDN isnโt delivering the benefit itโs theoretically capable of.
Real-World Use Case: E-Commerce Product Catalog
A retail site with a large product catalog is a strong Cloud CDN case, with a real nuance worth calling out: product images and static assets cache aggressively and benefit enormously, while product pricing and inventory data โ often rendered on the same page โ must never be served stale from cache. The correct architecture separates these concerns: static assets served with long cache TTLs and versioned URLs, while pricing/inventory data is either excluded from caching entirely (marked no-cache at the origin) or fetched via a separate, uncached API call from client-side JavaScript after the cached page shell loads. Conflating these โ caching a page that embeds both static and dynamic data as a single cached unit โ is exactly how a stale-price incident happens.
Best Practices
- Set explicit Cache-Control headers at the origin rather than relying purely on automatic static-content detection, especially once your application has content types that donโt fit the default heuristics cleanly.
- Whitelist only the query parameters that genuinely affect response content in the cache key โ this is consistently one of the highest-leverage tuning changes for cache hit rate.
- Never use FORCE_CACHE_ALL on any backend serving personalized or authenticated content. This isnโt a performance tuning decision โ itโs a data-exposure risk.
- Prefer versioned asset URLs over relying on invalidation for any content that changes on a predictable schedule (deploys, releases) โ itโs a more reliable pattern than remembering to invalidate manually every time.
Common Mistakes
Caching personalized content by accident. A page that includes a logged-in userโs name or account-specific data, cached without careful cache-key configuration, can serve one userโs personalized page to a completely different user โ this is a genuine security incident, not just a bug.
Not whitelisting query parameters, leading to cache fragmentation. Every unique combination of query parameters, including irrelevant tracking parameters, becomes a separate cache entry by default, silently tanking effective hit rate.
Treating invalidation as instantaneous and universally propagated. Invalidation takes a short but real amount of time to propagate across all edge locations โ assuming itโs instant everywhere can cause confusing โitโs still showing the old versionโ reports immediately after invalidating.
Frequently Asked Questions
Does Cloud CDN work with Cloud Storage-backed backends, not just Compute Engine or GKE? Yes โ a Cloud Storage bucket can be a backend behind the load balancer, and Cloud CDN caches its content the same way, which is a common pattern for serving static websites or large media files efficiently.
How do I know if Cloud CDN is actually improving performance? Cloud CDN provides cache hit ratio metrics in Cloud Monitoring โ tracking this over time, and specifically after cache-key tuning changes, is the concrete way to verify whether configuration changes are actually working rather than assuming they are.
Can Cloud CDN cache POST requests? By default, caching applies to GET (and sometimes HEAD) requests, since POST typically represents a state-changing operation that shouldnโt be cached. Some configurations allow caching specific POST responses, but this requires deliberate, careful configuration rather than being a default behavior.
Is Cloud CDN a separate cost from the load balancer? Yes, Cloud CDN has its own pricing dimensions (cache egress, cache fill, cache lookup requests) distinct from the load balancerโs own charges โ worth modeling both together when estimating total cost, particularly for very high-traffic sites.
Whatโs the maximum time content can stay cached at the edge? This is controlled entirely by your Cache-Control max-age (or the cache modeโs default behavior for CACHE_ALL_STATIC), not a platform-imposed ceiling โ content can be cached for seconds or for months depending entirely on what your headers or configuration specify.
Does enabling Cloud CDN change how my application needs to handle HTTPS? No additional application-side changes are needed โ Cloud CDN operates transparently behind the same load balancer already terminating TLS, so the caching layer doesnโt require your backend to handle encryption differently.
Summary
Cloud CDNโs integration directly into the load balancer makes it deceptively easy to enable and genuinely easy to misconfigure into either uselessness (fragmented cache keys tanking hit rate) or an actual security incident (personalized content cached and served across users). The real work is in cache-key design and cache-mode selection matched deliberately to what your application actually serves โ static assets and dynamic personalized content need fundamentally different caching treatment, and the biggest wins come from being explicit about that distinction rather than trusting default heuristics to sort it out correctly on their own โ and from actually watching the cache hit ratio metric afterward to confirm the configuration is doing what you intended.