TL;DR

1. The problem: why do we need these principles?

Instagram's requirements (see Requirements) say: serve 500M people a day, at ~350K feed requests per second at peak, with p99 latency under 300 ms, 99.99% availability, and never lose a photo. Now imagine the simplest possible implementation: one server running the app and a database, with photos on its disk. Every one of those requirements fails:

The principles in this lesson are the vocabulary for describing how a design fixes each of these — and what it costs. They come up in every system design interview, usually in step 6 ("what are the bottlenecks and failure modes?" — see How to Approach).

🧠 Analogy A restaurant. Scalability: when more customers come, you hire more cooks (horizontal) or buy a bigger oven (vertical), or open more branches. Availability: the restaurant is open even when one cook is sick — because no dish depends on a single person. Reliability: the food that comes out is what was ordered, every time. Durability: the secret recipes are written down in a safe and a copy is kept at the bank — a kitchen fire doesn't destroy them. Performance: how long you wait for your meal (latency) and how many meals the kitchen serves per hour (throughput). Maintainability: a new cook can learn the kitchen in a week. Cost: the restaurant still makes a profit.

2. Scalability

Scalability is a system's ability to handle increased load — more users, more data, more requests — by adding resources, while keeping performance acceptable. A scalable system's capacity grows roughly in proportion to the resources you add. "Load" must be described with load parameters: for Instagram, requests per second per endpoint, read:write ratio, follower-count distribution, data volume.

2.1 Vertical vs horizontal scaling

Vertical (scale up) 8 cores 128 cores2 TB RAM simple, but has a ceiling and is one box Horizontal (scale out) Load balancer 8 cores8 cores8 cores8 cores+ more near-unlimited and redundant, but needs statelessness
Scaling up buys a bigger machine; scaling out adds more machines behind a load balancer.
Vertical scaling (scale up)Horizontal scaling (scale out)
HowBigger machine: more CPU, RAM, faster disksMore machines sharing the load
Code changesNoneServices must be stateless or data must be partitioned
LimitHard ceiling (largest available machine); cost grows super-linearly near the topPractically unlimited; cost grows ~linearly
AvailabilityStill a single point of failureRedundant: losing one of N machines loses 1/N capacity
Operational complexityLowHigher: load balancing, distributed data, partial failures
UpgradesUsually downtime to resizeRolling: add/replace machines one at a time
Best forEarly stage; databases that are hard to distribute; quick reliefStateless tiers; anything at Instagram scale

Real systems do both: scale up a database primary to the largest sensible machine (it buys time and simplicity), and scale out everything else. Instagram's early story, as told in public talks, followed this path: a few bigger servers first, then many servers and sharded databases as growth demanded.

2.2 Stateless services: the key to scaling out

A service is stateless when it keeps no client-specific data in its own memory or disk between requests: any instance can handle any request. All state lives in shared external stores — databases, caches, object storage. Then:

# ❌ Stateful: session kept in the server's memory — breaks when the LB picks another server
sessions = {}                                   # lives in this process only
def login(user):  sessions[token] = user
def feed(token):  user = sessions[token]        # KeyError on any other instance

# ✅ Stateless: identity carried in a signed token, data in shared stores
def feed(request):
    user_id = verify_jwt(request.headers["Authorization"])   # no server memory needed
    post_ids = redis.zrevrange(f"timeline:{user_id}", 0, 19)
    return hydrate(post_ids)                                  # from cache / Cassandra

In the Instagram diagram every application service (Upload, Feed, Like & Comment, Follow, Search, User/Auth) is stateless. The one notable exception is the Direct Messaging WebSocket tier, which must hold long-lived connections; it keeps only the connection itself in memory and stores everything else (messages, presence) externally, with a routing table saying which gateway holds which user's connection. See Microservices & Stateless Services.

2.3 The scale cube: three ways to scale

The scale cube (from the book The Art of Scalability) names three independent dimensions of scaling:

one monolith,one database X-axis: clone N identical copies behind a load balancer Y Y-axis: split by function Upload, Feed, Like, Follow, Search, DM … Z-axis: split by data each server/shard owns a subset of users e.g. shard = hash(user_id) mod N Z
The scale cube. Real systems combine all three axes: Instagram's Feed Service is split out by function (Y), runs as many clones (X), and reads data sharded by user (Z).
AxisWhat it meansSolvesCostsInstagram example
X — horizontal duplicationRun N identical copies of a component; load balancer spreads requestsThroughput and availability of stateless compute; read scaling via DB replicasDoesn't help with data size or write volume; every copy needs all the codeHundreds of Feed Service instances; PostgreSQL read replicas
Y — functional decompositionSplit by verb/noun into separate services, each with its own dataTeam autonomy, independent scaling and deploys, fault isolationNetwork calls, distributed transactions, operational overheadUpload, Feed, Like & Comment, Follow, Search, DM as separate services (Microservices)
Z — data partitioningEach instance handles a subset of the data, chosen by a keyData volume and write throughput beyond one machineCross-shard queries, hot shards, resharding complexityPosts in Cassandra partitioned by user_id; users in PostgreSQL shards (Sharding)

2.4 Scaling the data tier

Stateless compute is the easy part; data is where scaling gets hard, because it has state by definition.

💡 Elasticity vs scalability Scalability is the ability to grow capacity; elasticity is doing it automatically and quickly in response to load (autoscaling), and shrinking again to save money. Stateless tiers can be elastic in minutes; stateful tiers (databases, caches) usually scale in planned steps because data must be moved.

3. Availability

Availability is the proportion of time — or, better at scale, the proportion of requests — for which the system is working correctly:

availability (time-based)    = uptime / (uptime + downtime)
availability (request-based) = successful requests / total valid requests
availability (from failures) = MTBF / (MTBF + MTTR)
   MTBF = mean time between failures   MTTR = mean time to recovery (detect + fix)

The last formula reveals the two levers: fail less often (raise MTBF) or recover faster (cut MTTR). At scale, recovering faster is usually far cheaper. Example: a component fails once a month (MTBF = 720 h). If a human must notice and fix it (MTTR = 1 h), availability = 720/721 = 99.86%. If automatic failover recovers in 1 minute, availability = 720/(720 + 0.0167) = 99.998% — the same unreliable hardware, 60× less downtime.

3.1 The nines

AvailabilityDowntime / yearDowntime / monthDowntime / dayTypical for
90% ("one nine")36.5 days~73 hours2.4 hoursBatch jobs, experiments
99% ("two nines")3.65 days~7.3 hours14.4 minutesInternal tools
99.9% ("three nines")8.77 hours~43.8 minutes1.44 minutesMany SaaS products; Instagram search/Explore
99.95%4.38 hours~21.9 minutes43.2 secondsTypical single-region cloud service SLAs
99.99% ("four nines")52.6 minutes~4.38 minutes8.64 secondsInstagram feed, upload, media
99.999% ("five nines")5.26 minutes~26.3 seconds0.86 secondsTelecom, payment networks, core infra

Each extra nine is 10× less downtime, and typically costs far more than 10× the engineering effort: three nines can be reached with good practices in one region; five nines generally requires multiple regions, automated everything and extremely careful change management.

3.2 Availability in series vs in parallel

A request usually passes through several components. How do their availabilities combine? Assume failures are independent (an important caveat — see 3.3).

In series (the request needs all of them): multiply.

A_series = A1 × A2 × … × An

In parallel (redundant copies; the request needs at least one): the system fails only if all copies fail.

A_parallel = 1 − (1 − A1) × (1 − A2) × … × (1 − An)
two copies of 99%:   1 − 0.01 × 0.01   = 1 − 0.0001  = 99.99%
three copies of 99%: 1 − 0.01^3        = 99.9999%
Series: one instance of each — the chain is weaker than its weakest link DNS99.99% Load balancer99.99% API gateway99.99% Feed service99.5% Redis99.9% 0.9999 × 0.9999 × 0.9999 × 0.995 × 0.999 = 99.37% ≈ 55 hours of downtime per year Parallel: replicate the weak components Feed #1 99.5% Feed #2 99.5% Feed #3 99.5% Redis primary Redis replica Feed × 3: 1 − 0.005³ ≈ 99.99999% Redis × 2: 1 − 0.001² = 99.9999% Whole chain: 0.9999³ × 0.9999999 × 0.999999 ≈ 99.97% ≈ 2.6 hours of downtime per year Now the single DNS / LB / gateway dominate → replicate them too.
Series multiplies availabilities (down); redundancy in parallel multiplies unavailabilities (up). Always find the weakest serial link.

Let's verify the numbers. Series: 0.9999³ = 0.99970; × 0.995 = 0.99470; × 0.999 = 0.99370 → 99.37%, i.e. 0.63% × 8,766 h ≈ 55 hours/year. With three feed instances and a Redis replica: 0.99970 × 0.9999999 × 0.999999 ≈ 99.97% (~2.6 h/year). The remaining downtime now comes from the three single-instance edge components — so we make those redundant too (multiple DNS providers/anycast, LB pairs, many gateway instances). That is precisely why every layer in the Instagram diagram is drawn as a single box but deployed as many.

💡 Two quick rules (1) Adding a dependency in series can only lower availability — every synchronous call is a liability, which is one reason to move work to async queues. (2) A system can't be more available than its least available serial dependency — so a 99.99% target means every critical-path dependency must be (effectively) better than 99.99%.

3.3 The independence trap: correlated failures

The parallel formula assumes copies fail independently. In reality, failures are often correlated: all three replicas run the same buggy release; all sit in one data centre that loses power; all read the same bad config push; all depend on the same DNS or auth service. Three "independent" 99% replicas that share a deploy pipeline are not 99.9999% available. Defences: spread replicas across failure domains (racks, availability zones, regions), deploy gradually (canary one zone first), keep dependencies diverse where it matters, and design so that a bad change can be rolled back automatically.

4. Redundancy and single points of failure

A single point of failure (SPOF) is any component whose failure takes down the whole system (or a whole feature). Redundancy — having spare capacity or duplicate components — removes SPOFs.

4.1 Hunting SPOFs in a naive Instagram

SPOFWhat happens when it failsFix
Single load balancerNobody can reach anythingActive-active LB pair / anycast, DNS with multiple IPs (Load Balancing)
One database primaryNo writes (or nothing at all)Replicas + automatic failover (Replication)
One cache node holding all timelinesFeed latency explodes; DB overloaded by missesSharded Redis cluster with replicas
One availability zonePower/network outage takes all copies at onceSpread every tier across ≥3 zones
One regionRegional disaster = global outageMulti-region with failover (Multi-Region)
One Kafka brokerEvent pipeline stopsReplication factor 3, partitions spread across brokers
Hidden SPOFs: DNS provider, certificate expiry, a config service, a single on-call engineerTotal outage from something "boring"Redundant providers, automation, alerts on expiry, runbooks

4.2 How much redundancy?

5. Failover: active-passive vs active-active

Failover is switching traffic from a failed component to a healthy one. It needs three things: detection (health checks, heartbeats), a decision (who's the new primary — ideally via consensus to avoid two primaries) and redirection (update the LB, DNS or client routing).

 ACTIVE-PASSIVE                                ACTIVE-ACTIVE
 ──────────────                                ─────────────
   clients                                       clients
      │                                         ┌───┴───┐
      ▼                                         ▼       ▼
 ┌─────────┐  replicate  ┌─────────┐       ┌───────┐ ┌───────┐
 │ ACTIVE  │ ──────────> │ PASSIVE │       │ node A│ │ node B│   both serve traffic;
 │ serves  │             │ standby │       │ 50%   │ │ 50%   │   replicate to each other
 └─────────┘             └─────────┘       └───────┘ └───────┘
   ✗ fails → standby promoted (seconds–minutes)   ✗ A fails → B takes 100% immediately
Active-passive (primary/standby)Active-active
Normal operationOne node serves; standby only replicatesAll nodes serve traffic
Failover timeSeconds to minutes (detect + promote + redirect)Near-instant (LB stops sending to the dead node)
Resource efficiencyStandby capacity sits idle (can serve reads as a replica)All capacity used — but each node must have headroom to absorb the others' load
Data consistencySimple: one writer. Risk: losing un-replicated writes on failover (async replication)Hard for writes: concurrent updates on different nodes → conflicts (conflict resolution)
Main riskSplit brain: old primary comes back thinking it's still primary → needs fencingWrite conflicts; cascading overload if remaining nodes lack headroom
Typical useRelational DB primaries (PostgreSQL with a standby), many stateful systemsStateless services, load balancers, leaderless stores (Cassandra), multi-region reads

Standby flavours: hot (running and in sync, takes over in seconds), warm (running but behind or under-provisioned, minutes), cold (restored from backups when needed, hours). The choice trades cost against RTO (recovery time objective: how long you may be down) and RPO (recovery point objective: how much recent data you may lose).

⚠️ Split brain and fencing If the network between a primary and its standby breaks, the standby may conclude the primary is dead and promote itself — while the primary is actually alive and still accepting writes. Now two primaries diverge. Systems prevent this with a consensus-based leader election (ZooKeeper/etcd, Raft) and fencing: each new leader gets a higher "epoch" number, and storage rejects writes carrying an old epoch. This is CAP in practice — see CAP & Consistency.

6. Reliability vs availability vs durability vs fault tolerance

These words are often used interchangeably, but interviewers notice when you distinguish them precisely.

PropertyQuestion it answersMeasured asInstagram example of a failureMain techniques
ReliabilityDoes the system do the right thing, consistently, over time?Error rate, MTBF, correctnessA like is recorded twice; a post appears for the wrong audienceTesting, idempotency, validation, careful rollouts
AvailabilityCan users use it right now?% uptime or % successful requestsFeed returns 503 for 10 minutesRedundancy, failover, load balancing, degradation
DurabilityOnce stored, will the data still exist later?Probability of data loss per object per year (e.g. 11 nines)A photo is permanently lost after a disk failureReplication, erasure coding, backups, checksums
Fault toleranceDoes it keep working when parts fail?Which failures it survives (node, zone, region)Losing one Cassandra node causes errorsRedundancy, quorums, retries, isolation (bulkheads)
ResilienceHow well does it absorb and recover from stress?Recovery time, degradation behaviourA traffic spike causes a cascading failureTimeouts, circuit breakers, load shedding (Resilience)

Some illuminating contrasts:

A key design idea is graceful degradation: when part of the system fails, reduce functionality instead of failing entirely. If the ranking service is down, serve the timeline in chronological order; if like counts can't be fetched, hide them; if the CDN can't reach the origin, serve a slightly stale cached copy. Users experience "a bit worse" instead of "broken".

7. Performance

7.1 Latency vs throughput

Analogy: a motorway. Latency is how long one car takes to drive from A to B; throughput is how many cars pass per hour. Adding lanes raises throughput without making any car faster; raising the speed limit lowers latency. They interact through queueing: as throughput approaches capacity, queues form and latency rises sharply (section 7.5). Some techniques trade one for the other — batching (e.g. Kafka producers batching messages, or fan-out workers writing timelines in bulk) raises throughput but adds a little latency to each item.

7.2 Percentiles: p50, p95, p99

Latency is a distribution, not a number. The pN (N-th percentile) is the value below which N% of measurements fall.

20 feed requests, sorted latencies (ms):
 42 45 47 48 50 51 53 55 56 58 60 62 65 70 75 80 95 120 250 1,900

mean = 3,282 / 20 ≈ 164 ms     ← dragged up by one 1.9 s outlier; describes nobody
p50  (median) ≈ 59 ms          ← the typical experience
p95           ≈ 250 ms         ← 1 in 20 requests is at least this slow
p99           ≈ 1,900 ms       ← the tail (with only 20 samples, p99 ≈ the max)

7.3 Tail latency amplification under fan-out

Here is a subtle but crucial effect. When one user request fans out to many backend calls in parallel and must wait for all of them, the request is as slow as the slowest call. If each backend is "slow" (above its p99) 1% of the time, independently:

P(user request is slow) = 1 − P(all N calls are fast) = 1 − 0.99^N
Probability a request hits at least one slow (p99) backend call, vs fan-out N 0%50%100% 1%4.9%9.6%18.2%39.5%63.4%86.6% N = 15102050100200 Each backend is slow only 1% of the time — but at N = 100, most user requests are slow.
Tail latency amplification: 1 − 0.99N. The rare becomes the common as fan-out grows.

Instagram example: rendering one feed page hydrates ~20 posts, each requiring metadata, counters, author info and "did I like it?" lookups spread over many cache shards — easily 20–100 parallel calls. With N = 20, 18% of feed requests experience some backend's p99. Flip it around: to keep the page p99 at a target, each backend must meet that target at the p99.95 level, because 0.991/20 ≈ 0.9995.

Mitigations (from Google's well-known paper The Tail at Scale and common practice):

7.4 Little's law

Little's law is a simple, universally applicable relationship for any stable system (a server, a queue, a whole service):

L = λ × W
L = average number of requests in the system (concurrency / in-flight)
λ = average arrival rate (throughput, requests/s)
W = average time each request spends in the system (latency, s)

Uses:

7.5 Utilisation and queueing: why you need headroom

Queueing theory gives a sobering rule: as a server's utilisation ρ (busy fraction) approaches 100%, waiting time explodes. For the simplest model (M/M/1 — random arrivals, one server), average time in system is:

W = S / (1 − ρ)        S = service time when idle
ρ = 50%  → W = 2 S
ρ = 70%  → W ≈ 3.3 S
ρ = 80%  → W = 5 S
ρ = 90%  → W = 10 S
ρ = 95%  → W = 20 S
ρ = 99%  → W = 100 S

Real systems differ in detail, but the shape — a "hockey stick" — is universal. That's why capacity plans target ~60–70% peak utilisation, not 100%: the last 30% of "unused" capacity is what keeps the p99 sane and absorbs spikes and failovers (Estimation).

7.6 Bottleneck analysis

A system's throughput is limited by its bottleneck — the most constrained resource on the path. Speeding up anything else doesn't help. A systematic approach:

  1. Draw the request path (e.g. feed: LB → gateway → feed service → Redis → post cache → Cassandra on miss → ranking).
  2. For each stage, compare demand to capacity: requests/s vs per-node limit × nodes; bytes/s vs NIC/disk bandwidth; memory vs working set.
  3. Check resources with the USE method: for each resource (CPU, memory, disk, network, connection pools, locks) look at Utilisation, Saturation (queue length) and Errors.
  4. Find the latency budget hogs with distributed tracing: which span dominates the p99? (Observability)
  5. Fix the top bottleneck, then re-measure — the bottleneck moves. After caching the feed, ranking may become the slowest stage; after optimising ranking, the network to Redis may be.

Common bottlenecks in Instagram-like systems: a hot shard (a celebrity's posts or counters), the primary database for writes, cache-miss storms after a cache restart, fan-out workers lagging behind during a spike, and cross-region round trips. Amdahl's law is the reminder for parallelism: if 10% of the work is inherently serial, no amount of parallel workers can make it more than 10× faster.

8. Maintainability and operability

Most of a system's cost over its lifetime is not building it but running and changing it. Three facets (as framed in Martin Kleppmann's Designing Data-Intensive Applications):

🛠️ Operability

Easy for operators to keep healthy: good monitoring and alerts, automation, runbooks, safe deploys and rollbacks, predictable behaviour, no manual steps in failover.

🧩 Simplicity

Easy for new engineers to understand: clear service boundaries, good abstractions, no accidental complexity. Choose boring, proven technology unless there's a strong reason not to.

🔄 Evolvability

Easy to change: loosely coupled services, versioned APIs, schema evolution, feature flags, events that let new consumers be added without touching producers.

In the Instagram diagram, Kafka is a big maintainability win: when the company wants a new feature that reacts to posts (say, a "creator insights" dashboard), a team adds a new consumer — the Upload Service doesn't change. Splitting services by function (Y-axis) lets hundreds of engineers deploy independently. Public Instagram talks have long emphasised operational simplicity — "do the simple thing first" and use proven technology — which let a tiny early team run a fast-growing service.

9. Cost

Every property above costs money: redundancy doubles or triples hardware, extra nines need extra regions, low latency needs RAM and edge locations. Good designers think in unit economics — cost per DAU, per upload, per GB served — and know the main levers:

Cost driver (Instagram)LeverTrade-off
Media storage (hundreds of PB/yr)Erasure coding instead of 3× replication; cold tiers for old media; efficient formatsSlower access to cold data; CPU for encoding
Egress bandwidthCDN with high hit ratio; right-sized renditions; modern codecsEncoding compute; device compatibility
Cache RAMCap timeline length; skip inactive users; compact encodingsOlder items fetched slower
ComputeEfficient runtimes; autoscaling down off-peak; fewer calls per requestEngineering effort
RedundancyMatch redundancy to each component's SLO (search needs less than feed)Lower availability for less critical features

10. How Instagram uses these principles: why each box exists

Read the Instagram diagram as a set of answers to the questions in this lesson. Every box buys at least one property:

Box in the diagramPrimary property it providesHow
Mobile/Web clientsPerformance (perceived), availability (perceived)Prefetching, local caches, optimistic UI, retries with backoff (Clients)
DNSAvailability, performanceRoutes users to a nearby healthy region/PoP; removes failed ones (DNS)
CDNPerformance, scalability, cost, availabilityServes media near users; offloads ~95%+ of egress; can serve stale on origin failure (CDN)
Load BalancerScalability (X-axis), availabilitySpreads load; health checks eject dead instances; deployed as a redundant pair/fleet (Load Balancing)
API GatewaySecurity, operability, availabilityCentral auth, TLS, rate limiting protects backends from overload (API Gateway)
Stateless application servicesScalability (X + Y axes), availability, maintainabilityAny instance serves any request; split by function for independent scaling/deploys (Microservices)
Kafka event busReliability, scalability, performance, evolvabilityDurable, replicated buffer; async work off the request path; absorbs spikes; new consumers without changing producers (Message Queues)
Feed Fan-out WorkersPerformancePre-compute timelines so reads are a single cache lookup (News Feed)
Media Processing WorkersPerformance, costRight-sized renditions and adaptive video reduce bytes and latency (Media Processing)
Feed Cache (Redis)Performance, scalabilitySub-ms reads; absorbs the bulk of read QPS so databases don't have to; replicated for availability (Caching)
Cassandra (posts, comments, stories, messages)Scalability (Z-axis), availability, durabilitySharded by key, leaderless replication (RF 3) survives node and zone loss (Sharding, Replication)
PostgreSQL (users), sharded + replicasReliability/consistency, availability, read scalingUnique constraints and transactions; replicas for reads and failover (Databases)
Object Storage (S3)Durability, scalability, cost11-nines design via replication/erasure coding across zones; tiers (Object Storage)
ElasticsearchPerformanceInverted index makes search fast; updated async so writes aren't slowed (Search)
Notification Service → APNs/FCMReliability, availability isolationRetries, dedupe; push-provider outages don't affect posting (Notifications)
Analytics & MLOperability, product performanceMetrics for capacity planning; models for relevance (Analytics & ML)
Multiple zones/regions (implicit)Availability, durabilitySurvive data-centre loss; serve users nearby (Multi-Region)
📸 In Instagram Instagram's public engineering history illustrates these principles in sequence. Early talks ("Scaling Instagram") describe stateless Django app servers behind load balancers (X-axis), PostgreSQL scaled first with replicas and then by sharding into thousands of logical shards (Z-axis), and Redis/Memcached absorbing reads (performance). Later, Instagram moved into Facebook's data centres and described running in multiple data centres so that losing one would not take the service down (availability), and adopting Cassandra for workloads needing high write throughput and fault tolerance. Their engineers have also written publicly about performance work on the Python/Django stack — e.g. tuning garbage collection to improve memory efficiency — a reminder that per-server efficiency directly translates into fewer machines (cost).
More usersload ↑
→X-axis
Clone stateless services
→reads ↑
Cache + replicas
→data/writes ↑
Shard (Z-axis)
→teams ↑
Split services (Y) + events
→regions
Multi-region

11. Trade-offs & common pitfalls

✅ What these techniques buy

  • Horizontal scaling → near-unlimited capacity and redundancy
  • Redundancy + automatic failover → four nines and beyond
  • Replication/erasure coding → durability measured in many nines
  • Caches and pre-computation → low, predictable latency
  • Async queues → spike absorption, failure isolation

⚠️ What they cost

  • Distributed systems complexity: partial failures, consistency trade-offs
  • 2–3× hardware for redundancy and headroom
  • Replication lag → stale reads; failover → possible data loss (RPO)
  • Caches → invalidation bugs and miss storms
  • More moving parts to monitor and operate

Common pitfalls

12. Real-world technologies

TechnologyPrinciple it servesWhen to use it
Kubernetes, cloud autoscaling groupsHorizontal scaling, elasticity, self-healingRunning many stateless instances; replacing failed ones automatically
HAProxy, NGINX, Envoy, cloud load balancersDistribution, health checks, failoverIn front of every replicated tier
ZooKeeper, etcd, ConsulLeader election, fencing, service discoverySafe active-passive failover; avoiding split brain
Patroni (PostgreSQL), MySQL Group Replication / OrchestratorAutomated database failoverRelational primaries that must fail over in seconds
Cassandra, DynamoDB, ScyllaDBPartitioned, replicated, highly available storageWrite-heavy data needing to survive node/zone loss
Redis Cluster / Sentinel, MemcachedPerformance via in-memory caching; replicas for availabilityHot reads and low-latency structures
Prometheus, Grafana, OpenTelemetry, JaegerMeasuring percentiles, saturation, traces for bottleneck analysisAlways — you can't improve what you don't measure
Chaos Monkey, Gremlin, LitmusChaosVerifying fault toleranceSystems with ambitious availability targets
k6, Locust, GatlingFinding throughput limits and latency curvesCapacity planning, before launches

13. Interview corner

What's the difference between vertical and horizontal scaling, and which would you use for Instagram?

Vertical scaling buys a bigger machine — simple, no code changes, but it has a hard ceiling, gets expensive near the top, and remains a single point of failure. Horizontal scaling adds more machines behind a load balancer — near-unlimited and redundant, but requires stateless services or partitioned data. For Instagram: horizontal everywhere — stateless services cloned behind LBs, Redis and Cassandra clusters, sharded PostgreSQL. Vertical scaling is still useful tactically, e.g. giving database primaries large machines to delay resharding.

Explain the scale cube with Instagram examples.

X-axis: clone — hundreds of identical Feed Service instances behind a load balancer; DB read replicas. Y-axis: split by function — separate Upload, Feed, Like & Comment, Follow, Search and DM services, each scaled and deployed independently. Z-axis: split by data — posts partitioned by user_id across Cassandra nodes; users across PostgreSQL shards. Each solves a different limit: X for stateless throughput, Y for organisational and functional scaling, Z for data size and write throughput.

A request goes through 5 components each with 99.9% availability. What's the end-to-end availability, and how do you improve it?

In series: 0.9995 ≈ 99.5% — about 44 hours of downtime a year, worse than any single component. Improve by (1) making each component redundant: two independent 99.9% copies in parallel give 1 − 0.001² = 99.9999%, so the chain becomes ≈ 99.9995%; (2) removing components from the synchronous path (move work to async queues); (3) graceful degradation so a failing non-critical dependency doesn't fail the request; (4) spreading replicas across failure domains so failures really are independent.

Active-passive or active-active for Instagram's user database?

Active-passive per shard: each PostgreSQL shard has one primary that accepts writes (so unique constraints like usernames remain simple and strongly consistent) and replicas that serve reads and act as hot standbys. Failover is automated with consensus-based leader election and fencing to avoid split brain; with synchronous or semi-synchronous replication to at least one standby, RPO can be ~0. Active-active writes would require conflict resolution for usernames — unacceptable. Stateless services, LBs and Cassandra, by contrast, run active-active.

Define reliability, availability, durability and fault tolerance, and give an Instagram failure example for each.

Reliability: doing the right thing consistently — failure: a like counted twice. Availability: being usable now — failure: the feed returns errors for 10 minutes. Durability: stored data survives — failure: a photo lost forever after disk failures. Fault tolerance: continuing to work despite component failures — failure: losing one Cassandra node causes write errors. Fault tolerance is the means; availability and durability are the goals.

Why do you care about p99 more than average latency, and what is tail latency amplification?

The average is skewed by outliers and hides what the slowest users experience; heavy users hit the tail daily. Amplification: when a request fans out to N backends in parallel and waits for all, it's as slow as the slowest. If each is slow 1% of the time, the request is slow with probability 1 − 0.99N: 18% at N = 20, 63% at N = 100. A feed page hydrating many posts is exactly this case. Mitigate with batching (fewer calls), hedged requests, timeouts with partial results, and pre-computed timelines.

Use Little's law to size something in Instagram.

Feed tier: λ = 350K req/s at peak, W = 100 ms → L = 35,000 concurrent requests. If a server handles 64 concurrent requests, we need ≥ ~550 servers for concurrency alone, before headroom. Or a DB pool: 20K queries/s × 5 ms = 100 connections. It also explains cascading failures: if a dependency's latency rises 10×, in-flight requests rise 10× at the same throughput, exhausting threads and pools — hence timeouts and circuit breakers.

How do you find the bottleneck in a slow feed endpoint?

Trace the path (LB → gateway → feed service → Redis timeline → post hydration → ranking) with distributed tracing and look at which spans dominate the p99. For each resource apply USE (utilisation, saturation, errors): CPU on feed hosts, Redis ops/s per shard (a hot shard?), cache hit ratio (miss storm?), connection-pool waits, GC pauses. Fix the top one, re-measure — the bottleneck moves. Typical culprits: N+1 calls during hydration (fix with multi-get), a hot Redis shard (replicate/split hot keys), or ranking-model latency (cache scores, cap candidates).

14. Check your understanding

Numeric: two independent load balancers, each 99.9% available, in active-active. Combined availability?

1 − (0.001 × 0.001) = 1 − 0.000001 = 99.9999% — assuming truly independent failures (different racks/zones, staggered deploys).

Numeric: how much downtime per month does 99.95% allow?

0.05% of ~43,830 minutes (average month) ≈ 21.9 minutes.

Numeric: MTBF = 500 hours, MTTR = 30 minutes. Availability? What if MTTR drops to 30 seconds?

500 / 500.5 ≈ 99.90%. With MTTR = 30 s (0.00833 h): 500 / 500.00833 ≈ 99.998%. Faster recovery is often the cheapest path to more nines.

Numeric: a page calls 50 backends in parallel; each has a 1% chance of being slow. What fraction of pages is slow?

1 − 0.9950 ≈ 1 − 0.605 = ~39.5%.

Scenario: your three Redis replicas are in the same availability zone. Is the cache highly available?

Only against single-node failures. A zone outage (power, network) takes out all three at once — a correlated failure. Spread replicas across zones (and ideally have the feed degrade gracefully if the cache tier is lost).

Scenario: a service runs at 92% CPU at peak and its p99 is erratic. Why, and what do you do?

Queueing: near full utilisation, waiting time grows like 1/(1 − ρ) — at 92% it's ~12× the service time and very sensitive to bursts. Add capacity (or cut per-request work) to bring peak utilisation to ~60–70%, and add load shedding/rate limiting to protect it from spikes.

Which property is violated: "Users' photos were all still on disk after the outage, but nobody could view them for 2 hours"?

Availability was violated; durability was preserved. The fix lies in redundancy and failover (e.g. serving from another region), not in more data copies.

Why must Instagram's app servers be stateless, and what's the one tier in the diagram that can't be fully stateless?

Statelessness lets the load balancer send any request anywhere, enables autoscaling, zero-downtime rolling deploys, and loss of an instance without data loss. The Direct Messaging WebSocket tier holds long-lived connections, so it's connection-stateful; it keeps all durable state (messages, presence) in external stores and uses a routing registry so any instance failure only drops connections, which clients re-establish.

🎯 Key takeaways

Related lessons