Autoscaling & Capacity Planning for Geospatial Workloads

Capacity for a geospatial portal is not one number, and autoscaling it on one signal produces a platform that adds renderers while the database is the bottleneck. The tiers saturate on different resources, respond to load on different timescales, and — in the case of the spatial database — cannot be scaled horizontally at all in the way the stateless tiers can. This topic sits inside the Infrastructure Orchestration & Configuration Management framework and builds directly on the statefulness model set out there.

The practical goal is modest and worth stating: a portal that absorbs a predictable seasonal peak without an emergency, that does not pay for idle capacity for the other eleven months, and whose scaling behaviour is understood well enough that an incident is not made worse by it.

CPU Is the Wrong Signal for a Renderer

The default autoscaling metric is CPU utilisation, and for a tile renderer it is a lagging indicator of the thing that actually matters. Rendering is bursty and heterogeneous: a request over open water finishes in milliseconds while one over a dense urban extent with several layers occupies a thread for a second. Average CPU across a scrape interval smooths precisely the behaviour that determines whether readers are waiting.

Four scaling signals, and how early each one moves CPU, concurrency, queue depth and cache-miss ratio, compared by how early they respond and what each one misses. SIGNAL MOVES MISSES CPU utilisation the default last, and smoothed by averaging a queue that is already forming in-flight requests per renderer as soon as work backs up little — the signal to scale on proxy queue depth requests waiting earliest of the reactive signals noisy — needs a longer window cache miss ratio before the load arrives — leading nothing, but needs a stable baseline

The last row is the one worth building. Renderer load is a function of cache misses, not of requests, and the miss ratio moves when an invalidation happens or a new area becomes popular — which is before the renderer feels it. Scaling on a combination of in-flight requests and miss ratio gives a fleet that grows slightly ahead of demand rather than a minute behind it, and a minute is the difference between a reader noticing and not.

Two practical notes on configuring it. Set the target for in-flight requests from a measured relationship between concurrency and latency on one renderer, not from a round number: the useful target is the concurrency at which p95 latency starts to climb, typically well below full CPU saturation. And give the scaler a longer window for scale-down than for scale-up, because tile traffic is spiky and a fleet that shrinks during a lull spends the next burst cold.

The Database Does Not Scale the Same Way

The stateless tiers respond to added replicas within a start-up time. The spatial database does not, and treating it as if it does is the most expensive mistake available here. Its ceiling is set by connections and by the memory each query may use, and adding read replicas helps only for the queries that can be routed to them — which excludes anything writing, anything in a transaction that writes, and anything that must see the effect of a write that just happened.

What each tier can do when asked for more capacity Renderers, workers, read replicas and the primary database compared by how they scale, how quickly, and what constrains them. TIER SCALES IN CONSTRAINED BY tile renderers horizontally, freely a start-up time whatever they read from workers horizontally a start-up time the database and object store read replicas horizontally, for reads minutes to hours replication lag, and routing primary database vertically a maintenance window everything else respects it

The consequence for autoscaling policy is concrete: bound the stateless tiers by what the database can serve, not by their own resource usage. A renderer fleet permitted to grow to fifty replicas in front of a pooler sized for forty connections will reach a state where every renderer is waiting for a connection, latency is high, CPU is low, and the autoscaler — reading CPU — decides to add more. Setting the fleet’s maximum from the pool’s capacity is the single most valuable constraint in the whole configuration.

Read replicas are worth adding for a portal whose load is dominated by feature queries and catalogue reads, and they are not free: something must decide which queries may go to a replica, and that decision has to account for replication lag. A user who edits a feature and immediately does not see it in the map has met that lag, and the fix is routing rather than capacity.

Traffic Shape Decides Cost, Not Traffic Volume

Two portals serving the same number of requests per second can need very different capacity, because the mix of what those requests are matters more than the count. Classifying traffic before sizing anything is what makes a capacity model predictive rather than a scaled-up guess.

Three classes cover most portals. Cacheable and shared — a public basemap, identical for everyone — is absorbed almost entirely by the cache and costs the renderer only on invalidation. Cacheable and per-caller — tenant-scoped tiles, styled per organisation — multiplies the cache footprint by the number of distinct audiences and reduces the hit ratio for each. Uncacheable — filtered feature queries, search, anything with a per-request parameter — reaches the database every time, and its cost scales linearly with request volume with no cache to absorb it.

A portal whose traffic is ninety percent in the first class is a caching problem and needs very little renderer capacity. The same request rate in the third class is a database problem that no amount of tile caching will help. Both look identical on a requests-per-second graph, which is why that graph is a poor basis for a capacity decision.

The corollary is that the highest-value capacity work is often not capacity at all: moving requests from the third class to the first — by making a commonly-filtered view into a published layer, or by giving a frequently-requested export a scheduled snapshot — reduces the load rather than serving it faster.

Planning for Peaks You Can See Coming

Geospatial portals have unusually predictable peaks. A flood layer is consulted when it rains, agricultural parcels around a subsidy deadline, election boundaries in the weeks before a poll, and a public consultation drives traffic on the day it is announced. Unlike a general web service, most of a geospatial portal’s largest days are on somebody’s calendar months in advance.

That predictability is worth using rather than absorbing reactively. Autoscaling handles a gradual climb well and handles a step change badly: a portal that appears in a news broadcast goes from baseline to many multiples within a minute, and a scaler reacting on a metric window with a start-up time behind it is minutes late for an event that lasts fifteen.

Three preparations cover it. Pre-scale ahead of known events, from the calendar rather than from a metric — a scheduled minimum replica count for the day is cheap and removes the reaction time entirely. Pre-seed the tiles for the area and layers the event concerns, so that the surge lands on a cache rather than a renderer. And decide the shedding policy in advance, so that if the peak exceeds preparation the portal degrades in a way somebody chose, rather than by whichever tier collapses first.

Keep the previous year’s measurements to plan against, at a resolution that survives the retention policy. This is the practical reason for the downsampled long-retention series described in monitoring and observability for geospatial portals: when the seasonal peak comes round again, the useful question is what it looked like last time, and a two-week retention window cannot answer it.

Building a Capacity Model Somebody Can Argue With

A capacity model does not need to be sophisticated to be useful. It needs to be explicit enough that its assumptions can be challenged, and simple enough that it is updated when they change. Three inputs and one output are sufficient for most portals.

The inputs are the traffic mix by class, the cost per request within each class, and the peak multiplier over baseline. The mix comes from a week of access logs grouped by operation and cacheability. The cost per class comes from measurement — the median and p95 service time for a cached tile, an uncached tile, and a feature query, taken from the metrics rather than from a benchmark. The peak multiplier comes from the last few known peaks, and it is the input most often understated, because the memorable peaks are the ones that were survived rather than the ones that were not.

Three inputs, one capacity table Traffic mix, cost per class and peak multiplier feeding a per-tier capacity table at baseline and peak. traffic mix by class from a week of access logs cost per class measured service times peak multiplier from the last few peaks required capacity per tier renderers replicas, at baseline and at peak database connections, and the pool split workers concurrency per queue the tier closest to its ceiling is the bottleneck — and it is rarely the expected one

The output is a table of required capacity per tier at baseline and at peak, expressed in the unit each tier actually scales in: replicas for the renderer, connections for the database, workers for the queue. That table is what a budget conversation needs, and — more usefully — it is what makes the bottleneck visible before it is reached, because one tier is always closest to its ceiling and it is rarely the one people expect.

Revisit it when any input moves materially: a new large dataset, a change in cartography that alters render cost, a new integration that shifts the traffic mix towards uncacheable queries. The model going stale is not a failure of the model; failing to notice that it has is.

Two Costs That Grow Without Anybody Deciding

Capacity conversations focus on compute, and two other dimensions grow quietly and eventually dominate.

The first is tile storage. A cache grows with every seeded level and every distinct style, and because each zoom level roughly quadruples the tile count, adding one level to an archive can add more storage than every shallower level combined. Nobody decides to double the storage bill; somebody adds a zoom level, or a second style for a print variant, and the growth follows arithmetically. Forecasting it is a small piece of work with a large payoff, covered in forecasting tile storage growth.

The second is data transfer, which is invisible in most architecture discussions and is a real line on the bill for a portal serving tiles to the public. It scales with reader traffic rather than with anything the platform controls, and the levers are cartographic and technical in equal measure: tile format and compression, whether the client requests a retina variant, the cache lifetime that determines how often a returning reader re-fetches, and whether a popular embedded map is fetching tiles it does not display.

Both belong in the same review as compute capacity, quarterly, with the same treatment: a measured number, a trend, and a named owner. A portal that reviews replica counts monthly and storage growth never will eventually be surprised by the one it was not watching.

Scaling Down Is the Harder Half

Almost every autoscaling discussion is about growth, and almost every incident caused by autoscaling happens on the way down. Removing a renderer takes its in-flight work with it unless the drain is correct, and a fleet that shrinks aggressively during a quiet period meets the next burst with cold pods, empty page caches, and a scaler that has to react from a lower base.

Three settings decide whether scale-down helps or hurts. The stabilisation window should be several times longer for removal than for addition, so a brief lull does not trigger a shrink that the next minute reverses. The minimum floor should be sized from the traffic that arrives fastest rather than from average demand, because the floor is what absorbs the first thirty seconds of any surge. And the drain behaviour must give in-flight renders time to complete, with a termination grace period longer than the p99 render time — the mismatch described in configuring HAProxy for WMS load balancing applies identically here.

There is also a cost argument for a generous floor that is often missed. The saving from running two replicas instead of four overnight is small in absolute terms for most agency portals, and the cost of the next morning’s slow start — in reader experience, in support time, and in the temptation to disable autoscaling entirely after one bad morning — is easily larger. Scale down, but not to the edge.

One further habit is worth adopting: record every manual scaling change with a reason and an expiry, in the same place the automated policy lives. Emergency scale-ups are legitimate and they are also the most common source of unexplained baseline cost a year later, because nobody remembers whether the floor of twelve replicas was a considered decision or a Tuesday afternoon.

Operational Troubleshooting

Symptom Likely cause Fix
The fleet scales up but latency stays high The bottleneck is downstream — pool, database, or object store Cap the fleet at what the downstream can serve; scale that instead
Renderers scale down during a lull and the next burst is slow Scale-down window as short as scale-up Lengthen the scale-down window; keep a warm floor
Autoscaling reacts a minute after readers notice Scaling on CPU, which lags Scale on in-flight requests, with cache-miss ratio as a leading signal
A news-driven surge overwhelms the portal before scaling helps Step change faster than start-up time plus metric window Pre-scale from the calendar; pre-seed the relevant tiles
Cost rises without a traffic increase A minimum replica count raised during an incident and never lowered Put an expiry on emergency changes; review scaling floors monthly
Adding read replicas did not help The queries are writes, or read-after-write Classify traffic before adding capacity; route explicitly
Scaling oscillates between two sizes Target too close to the steady-state value, with no stabilisation Widen the stabilisation window and set the target from measured latency

FAQ

What is a sensible starting point for a renderer’s concurrency target?

Measure it rather than guess: run one renderer at increasing concurrency and record p95 latency, and take the target as the concurrency just below where latency starts climbing. For a typical raster renderer that lands in the low tens per replica, and it is usually well below the point at which CPU looks busy — which is exactly why CPU makes a poor target.

Should the tile cache be autoscaled?

The cache’s capacity is storage rather than compute, so it does not autoscale in the same sense; what matters is that it is large enough to hold the working set. A cache too small for the popular area produces a permanently elevated miss ratio, which shows up as renderer load and is often misdiagnosed as insufficient renderer capacity.

How should scheduled jobs interact with autoscaling?

Give them their own capacity and their own limits, rather than letting them compete with reader traffic through the same autoscaler. A seeding run or a bulk ingestion that triggers a scale-up is paying for capacity to serve work that could have waited, and it displaces the signal the scaler needs to see about real readers.

Is scaling to zero ever appropriate?

For a genuinely idle environment — a staging portal outside working hours — yes, and it saves real money. For anything a person might reach, no: the first request after a scale-to-zero pays the full start-up cost, which for a renderer with assets to load is measured in tens of seconds, and the reader who paid it will report the portal as broken.

How much headroom should a portal keep?

Enough to absorb the time it takes to scale, which means the headroom and the start-up time are the same decision. A fleet that takes ninety seconds to add a replica needs enough spare capacity to serve ninety seconds of growth at the fastest rate traffic actually climbs — measured from the last few peaks, not assumed.

Up one level: Infrastructure Orchestration & Configuration Management.