The Azure Well-Architected Performance Efficiency Pillar: Designing for Load You Don't Have Yet
Autoscale switched off, no performance baseline, Premium tiers where Standard would carry it. What the Azure Well-Architected performance efficiency pillar keeps turning up in real estates.
The Azure Well-Architected Performance Efficiency Pillar: Designing for Load You Don’t Have Yet
Nearly every assessment I run turns up the same contradiction. Capacity is fixed. Autoscale is off. And there’s no performance baseline to scale against in the first place.
The pillar asks one question. Can a workload meet demand as it changes, scaling up under load and back down when the load passes, without either falling over or paying for headroom it never uses? Most environments answer it by accident. Someone guessed a size once, shipped it, and never connected the guess to anything measured.
It’s the mirror of cost optimization. That pillar finds the over-provisioning. This one finds the under-provisioning, and the static provisioning: workloads pinned to a fixed size and left there. Same root cause, opposite direction. Capacity decided without data.
Four principles, and where they break
Capacity should track demand through measurement and elasticity, not through a fixed allocation someone picked at design time. The Microsoft Azure Well-Architected Framework treats performance as something you negotiate against data, continuously. That last word is where most environments fall down.
Four principles carry the pillar. Negotiate realistic performance targets, so “fast enough” means something. Meet capacity through elasticity instead of static over-provisioning. Baseline, test, and monitor against the targets so performance holds over time. Make the trade-offs on evidence: the right tier, the right scaling model, the right place to spend.
Then you look at the running environment and the gap writes the findings for you. A workload sitting at a fixed three instances because three felt safe. No load test ever run, so nobody knows what three instances can actually serve. No baseline, so nobody can say whether response times have slipped since launch.
A guess that calcified into a fact.
Why “no autoscale” is the finding I see most
Static capacity is the default. Autoscale is the thing you have to sit down and configure, write the rules, set the bounds, and nobody got around to it. A fixed instance count works fine the day it ships. It only shows its inadequacy under a spike or during a quiet spell, and by then nobody’s looking at capacity.
Azure hands you the scaling mechanisms. The assessment finds them switched off.
Take App Service. A plan on the Standard or Premium tier supports autoscale rules: scale out when CPU or memory crosses a threshold, scale in when it drops back. What I find is a fixed instance count and no rules at all. Sized for the peak, it over-pays at the trough. Sized for the average, it degrades at the peak. Either way the elasticity is sitting there unused.
Virtual Machine Scale Sets are worse in a way, because the whole point of a scale set is to move instance count automatically. I find them pinned to a static count with the autoscale profiles never defined. The construct gets used for management convenience and the capability that justifies it gets ignored.
AKS has two scaling layers. The Horizontal Pod Autoscaler scales pods against CPU or custom metrics, and the cluster autoscaler scales the node count. Often neither is configured. A fixed node pool running a fixed pod count, sized by hand.
None of this is hard to fix. It’s defining the rules and the bounds. What’s missing isn’t capability, it’s the configuration, plus the baseline that would tell you where to set the thresholds.
And before you assume this is a story about other people’s estates: I have absolutely sized a thing by feel on a Friday, shipped it, and never gone back to check. The instinct is not rare. It’s just usually undocumented.
No baseline means no answer
Ask “how fast is this system when it’s healthy?” and there’s no recorded answer.
That’s the finding. You can’t prove the workload is meeting its target and you can’t catch it drifting away from one. Nothing visibly breaks, which is exactly why it goes unnoticed. The system is just un-measured.
A baseline is a captured record of behaviour under known load: response time at the median and the tail percentiles, throughput, utilisation at each tier. It’s the reference everything else gets judged against. Without it, “the application feels slow” is unfalsifiable. Autoscale thresholds become guesses. Capacity planning becomes superstition.
So the assessment looks for two things. Evidence that load testing happened, meaning Azure Load Testing runs, results captured, targets defined. And Application Insights configured to track the performance signals continuously. Usually it finds neither, with the app instrumented at defaults.
That’s the same observability gap the operational excellence pillar surfaces, seen through the performance lens. You can’t tune what you never measured. And you can’t prove a regulator-relevant performance commitment you never baselined.
“But nothing has ever fallen over, so we’re fine, right?”
Not quite. Nothing falling over tells you the system survived the load it happened to get. It says nothing about the load it hasn’t got yet, and nothing about whether it’s slower today than it was at launch. Absence of an incident is not a measurement. It’s a sample size of however many quiet months you’ve had.
Premium where Standard would do
Premium feels like the responsible choice. Nobody checked whether the workload needed it. That’s the whole mechanism, and it’s the performance pillar surfacing cost waste, arriving at the same place as the cost optimization pillar from a different question. Does this workload actually use the tier it’s paying for?
The pattern repeats across services.
| Service | Premium chosen | What the workload actually needs |
|---|---|---|
| Azure SQL Database | Business Critical tier | General Purpose, where the IOPS and latency suffice |
| Azure Cache for Redis | Premium tier | Standard, where the throughput and size fit |
| App Service | Premium v3 plan | Standard, where the autoscale ceiling is never reached |
| Storage | Premium SSD on a low-IOPS workload | Standard SSD, matched to actual throughput |
| Application Gateway | WAF_v2 at high capacity units | Right-sized capacity against real request volume |
Premium isn’t wrong. These tiers exist for workloads that need what they guarantee: the latency floor of Business Critical, the clustering and persistence of Premium Redis. The finding is the tier picked without checking whether the workload ever reaches for any of it. The pillar asks you to make that choice on evidence.
Most get made on instinct, and instinct rounds up.
The tier everyone forgets to scale
Compute usually scales. The data tier usually doesn’t. So the database becomes the ceiling the moment the application scales into it, and that’s why data and caching moves the needle more than compute scaling ever will. It’s the most overlooked lever here and the one with the biggest payoff when it’s missing.
The failure mode is structural, not a config typo. The application tier autoscales beautifully. Under load it spins up more instances, and every one of them hammers the same Azure SQL Database, which can’t scale horizontally the way a stateless tier can. Throughput collapses at the data tier while compute sits comfortable.
Adding application instances makes it worse.
Three levers, and the assessment checks for each.
Caching first. Azure Cache for Redis in front of the database absorbs read load that would otherwise hit the data tier on every request. What I usually find is no cache at all. Every read goes to the database, including reads for data that only changes hourly. It’s often the single highest-impact change available, and it’s absent.
Read replicas next. Azure SQL Database read scale-out and read replicas move read-heavy queries off the primary. Read-dominated workloads still route everything to the primary, because the replica was never set up.
Then static assets. Content served straight from the application or from storage, with no Azure Front Door or CDN in front, putting load on origins that should never see it.
Scale compute against an un-cached, un-replicated database and you’re scaling the wrong tier. No autoscale rule fixes a structural bottleneck.
What a performant configuration leaves behind
Defined performance targets. Captured baselines. Autoscale rules with sensible bounds. Service tiers matched to measured demand. A data tier with caching and read scale-out where the load justifies it. That’s the evidence, and it’s what turns “it performs well” from an assertion into something demonstrated and repeatable.
It’s also a tell for operational maturity. A baseline implies load testing ran. Autoscale rules imply someone understood the demand pattern. Tier choices backed by utilisation data imply the trade-offs were deliberate. A performance-efficient environment is a measured one, almost by definition.
The part that eventually wore me down was doing this correlation by hand. Open the portal, read the provisioned tier, open Azure Monitor, read the actual utilisation, decide whether the two have anything to do with each other, then repeat that for every workload in the subscription and write it up before anyone changes it again. I got tired of being a human join between two blades, so I built Platform Architecture Authority to do the join and hand back the remediation as infrastructure-as-code. It’s read-only. Which workloads genuinely warrant Premium guarantees is still an architect’s call, and it should be.
Go and look at one workload in your own estate. Its provisioned tier, and its actual utilisation over the last month. If those two numbers have never been in the same sentence, you already know what the assessment would say.
The short version
- No autoscale configured is the finding I see most. Workloads run at a fixed instance count, sized for an average that’s wrong at both the peak and the trough.
- No performance baseline exists in most environments. Without one you can’t say whether the system is fast enough, and you can’t tell when it degrades.
- Premium tiers get used where Standard would carry the throughput. The inverse waste: paying for headroom the workload never touches.
- Data and caching is the most overlooked lever. The application scales, the database it leans on does not, and nothing sits between them.
- Performance efficiency and cost optimization are the same conversation. Right-size to actual demand and both resolve.
Designing for load you don’t have yet is cheap insurance, not over-engineering. Wire up the elasticity and capture the baseline now, and when the load arrives the system scales to meet it. Skip it, and the load teaches you what you should have measured.
FAQ
What is the difference between scaling up and scaling out? Scaling up (vertical) means a bigger instance: more vCPUs or memory on the same resource. Scaling out (horizontal) means more instances sharing the load. The pillar favours scaling out with autoscale for stateless tiers: it adds elasticity and removes single points of failure, where scaling up hits a hard ceiling.
Why does the data tier limit performance more than compute? The application tier is usually stateless and scales horizontally: add instances, add capacity. A relational database holds state and can’t scale horizontally the same way, so it becomes the bottleneck when the application scales into it. Caching and read replicas relieve the data tier. Adding application instances does not.
How is performance efficiency different from cost optimization? Two views of one question: does capacity match demand? Cost optimization finds capacity exceeding demand (over-provisioning). Performance efficiency finds capacity failing to track demand (no autoscale, no baseline) and, in the tier findings, the same over-provisioning. Right-size to measured demand and you satisfy both.