Chapter 22 of 27 · Part 9: Deployment and scaling

Scaling microservices

Scale for load or for robustness: the four axes, databases, caching and autoscaling.

key idea / coordinatorservice / good pathin progress / watch outproblem / failureneutral

Why scale, and along which axis

We scale for two reasons: to perform better (more load, lower latency) and to be robust (survive a failure). Different techniques help with different goals, so name the goal before picking a tool.

Two goals, four axesPerformancemore load, lower latencyRobustnesssurvive failuresVertical scalingbigger machineHorizontal duplicationmore copies of the same thingData partitioningsplit work by a data keyFunctional decompositionsplit work by type: microservicesScale Cube (Abbott and Fisher) plus a fourth axis, vertical scaling.

Newman uses a running example, FoodCo: food delivery in many countries, with growing write contention on its main database. We follow it through each axis.

AxisHelps load?Helps robustness?Main cost
VerticalYes, quicklyHardlyMoney; a ceiling on machine size
Horizontal duplicationYes, if work spreads easilyYesMore infrastructure; needs a way to split work
Data partitioningYes, especially writesLittle on its ownHard key choice; cross-shard queries
Functional decompositionYes, per workloadOpens the doorBiggest code change; more services

From Building Microservices, ch. 13, "The Four Axes of Scaling".

Vertical scaling: a bigger box

Get a faster CPU, more memory, better I/O. On virtual machines or a public cloud this takes minutes and rarely needs code changes, so it is a cheap experiment to try first.

Vertical scaling and its limitsSmall VM2 cores, 8 GBBigger VM16 cores, 64 GBHardware limitno larger box existsLimits you will meet:more cores, not faster cores; software may not use them; still one point of failure; cost grows faster than capacityBigger machines also make room for databases of newly extracted services.
FoodCo: the primary database had already been upsized several times. It bought years, but growth projections said another resize would not be enough, so FoodCo looked at the other axes.

Horizontal duplication

Run several copies of the same thing and spread the work. The consumer sees one service: the load balancer or queue is part of its logical boundary. Instances come and go, so consumers need DNS or a registry to find them (service discovery, lesson 04).

Load balancing across instancesConsumersLoad balancerhealth checksCatalog #1Catalog #2Catalog #3DatabaseBalancer drops unhealthy nodes. Avoid sticky sessions if you can.
Competing consumersWork queuesongsTranscoder #1Transcoder #2Transcoder #3Transcoded filesCompeting consumers: add instances to raise throughput.

Duplication is simple because the application seldom changes. Its limits: it costs infrastructure, copies the whole monolith even when only one part is hot, and relies on your load-distribution mechanism (balancer, broker, replicas) working as designed. Systems that need a user always routed to the same replica (sticky sessions) are prone to other problems; Newman avoids building them.

From Building Microservices, ch. 13, "Horizontal Duplication".

Data partitioning and sharding

Apply a function to a key on each request; the result picks the partition (shard). Unlike duplication, each node holds only part of the data, which is why it scales writes so well.

Choosing a shard by keyRequestcustomer id 7281Partition fnhash(id) mod 3Partition 1Partition 2Partition 3own databaseown databaseown databaseCallers never see the partitioning: it is an internal detail of the service.

Partitioning can happen in the database (Cassandra, Kafka topics) or in front of dedicated service instances, useful when instances keep in-memory caches. The hard part is the key.

Getting the key rightPoor key: family name A-M / N-ZBetter key: unique customer idA-Mfew namesN-Zmost namesShard 1Shard 2Uneven load, and name changes move people.Even spread, stable over time.Surname distributions are very skewed in some countries.
BenefitLimit
Scales write-heavy workloads wellA bad key is painful to change; one client needed three days of downtime to re-partition
Smaller blast radius for maintenance; per-region rolloutsOne failed shard fails its share of requests (a quarter, with four shards)
Keeps data inside a jurisdiction (geographic shards)Queries across shards must visit each shard or use a separate read store
FoodCo rejected country shards: it plans to keep expanding, and a new partition for every country would make each launch expensive. Combine partitioning with duplication so a partition can survive a node failure.

From Building Microservices, ch. 13, "Data Partitioning".

Functional decomposition and combining axes

Extract a piece of functionality so it can scale on its own. This is the classic microservice split (lessons 02 and 23). Each workload can get right-sized hardware, be switched off when idle, or move to a better-suited language or database.

Extract a workload, then scale itMonolithorders, catalog, usersextractRemainingsystemOrder servicescales alonex3 copies,then shardExtraction unlocks the other axes for just the hot workload.
Three axes on one serviceRegion EURegion USOrder #1Order #2Orders databaseOrder #1Order #2Orders databaseOrder service: extracted, partitioned by region, duplicated inside each region.

The goal is not to use every axis, only to know them. Newman tries the cheap, low-impact ones first (vertical, duplication) and leaves decomposition for later unless it also solves organisational scaling. That is FoodCo: splitting out deliveries and menus also lets a growing team own services.

From Building Microservices, ch. 13, "Functional Decomposition" and "Combining Models".

Scaling databases: reads, writes and CQRS

Reads and writes scale differently. Reads are easy to copy; writes are the hard part.

Read replicas offload the primaryServiceroutes internallyPrimaryall writesRead replica 1Read replica 2writesreads (dashed)Replicas are eventually consistent: a read can be slightly stale.
ProblemTry firstThen
Read-heavyRead replicas behind a balancerCaching, or CQRS if reads need a different shape
Write-heavyBigger database machineShard by key; consider a database built for your write pattern
One hot workloadMove it to its own service and databaseScale that service on its own

When a database cannot keep up with writes, teams often change database technology. A bigger box is the fastest relief while you evaluate options.

CQRS: separate models for reads and writesClientCommand modelwritesQuery modelreadsWrite storeRead storeprojectionseparate processescan scale apartCQRS is an internal choice of the service; callers do not see it.
CQRS and event sourcing are hard. Newman has seen smart teams struggle and suggests a read replica first if you are only read-constrained. Both raise cognitive load. See lesson 11 for CQRS views and lesson 10 for event sourcing.

From Building Microservices, ch. 13, "Horizontal Duplication", "Data Partitioning" and the CQRS box in "Start Small".

Caching: where, how, and how fresh

Lesson 07 covers caching reads. Here is the scaling view: a cache cuts latency, shields a contended origin, and can keep a service answering when the origin is down (if you keep stale entries). It always trades freshness for speed.

Where to cacheFour places to cache the best-seller listSalestop-ten requestCatalogorigin of namesClient-sideShared (Redis)Server-sideRequest cacheinside Salesone copy, one hopinside Catalogwhole answerno network callconsistent for allhelps all callersfastest, narrowestClient-side saves the network call and helps robustness; server-side helps every consumer.
InvalidationHow it worksWatch out
TTLEntry valid for a set time (Cache-Control, Expires)Blunt: stale until expiry. Shorter TTL means more origin calls
Conditional GETSend If-None-Match with the ETag; 304 means unchangedStill a round trip; saves rebuilding the response
NotificationOrigin publishes a change event (Stock Change)Needs a broker; send heartbeats so silence is not mistaken for no change
Write-throughCache and origin updated togetherPractical mainly on the server side
Write-behindUpdate the cache first, origin laterData loss risk; unclear source of truth. Rare in microservices
The golden rule: cache in as few places as possibleOriginstock changesServer cacheTTL 1 min: 1 min oldClient cacheTTL 1 min: 2 min oldNested caches add their staleness. The client thinks it is at most one minute behind.
Cache poisoning: a bug let a legacy response keep an Expires: Never header, and those pages stuck in browser caches the team could not clear; the only fix was changing URLs. Know the full path from source to screen, including CDNs and browsers.

From Building Microservices, ch. 13, "Caching" to "Cache Poisoning: A Cautionary Tale".

Autoscaling

If provisioning and deployment are fully automated, instances can be added and removed automatically. Two triggers: predictive (known daily pattern) and reactive (observed load or failure).

Predictive and reactive scalingLoad through the dayPredictive: 8:45 up, 17:15 downSpike: reactNews site: a daily curve to predict, plus unpredictable story spikes to react to.

From Building Microservices, ch. 13, "Autoscaling".

Start small, and start again

Scaling adds complexity: every new service or cache is another thing to run and secure. Treat each change as an experiment: find the bottleneck, build a small test, change one thing, measure.

Scale by experimentObservereal bottleneck?Hypothesisone changeLoad testbaseline, changeMeasurekeep or revertRepeat. Optimise for problems you have, not ones you might have.

The architecture that gets you started may not carry you to the next level. Gilt ran a Rails monolith for two years before load forced a redesign. That is no reason to build for huge scale on day one: you would spend months on load that may never come and learn too late whether anyone wants the product. Needing to re-architect for scale is a sign of success, not failure.

From Building Microservices, ch. 13, "Start Small" and "Starting Again".

Recap

Quick check

Your service is read-heavy and its database is at 90% CPU. What do you try first, and why not CQRS?
Show answer

A read replica (or a cache): low risk and quick, with no change to the write model. CQRS is one of the harder options and raises cognitive load.

Why is partitioning customers by first letter of the family name a poor key?
Show answer

Names are unevenly distributed (and vary by culture), so one shard gets far more load. Names can also change. Use a unique customer id.

Both the server and client cache an item with a one-minute TTL. How stale can the client copy be?
Show answer

Up to two minutes: the client may refresh from a server cache that is already one minute old. Hence cache in as few places as possible.