1Show the Level 1 solution
2**A.**
31. **Scale up** (more cores), and fix slow queries. A database is hard to split, and 4 cores is small.
42. **Scale out.** It's stateless; add servers and autoscaling.
53. **Scale out.** Independent jobs split perfectly; add workers based on queue length.
64. **Move the sessions out first** (to Redis), then scale out. Short term, scaling up the single server is the quick fix, but it stays a single point of failure for the launch.
7
8**B.**
91. **No.** Nothing important is on it.
102. **Yes.** The photos on its disk are lost (and invisible to other servers anyway). Store them in object storage.
113. **No**, if the queue is set up properly: an unacknowledged job becomes visible again and another worker takes it (Chapter 22). The work is redone, so the job must be safe to run twice.
Show the Level 2 solution
1. Normal: 2,000 ÷ (500 × 0.6) = 6.7 → 7, plus 1 spare = **8**. Sale: 20,000 ÷ 300 = 66.7 → 67, plus 1 = **68**.
2. 1 ÷ (0.1 + 0.9 ÷ 10) ≈ **5.3×** with 10 servers; 1 ÷ (0.1 + 0.9 ÷ 100) ≈ **9.2×** with 100; the limit is 1 ÷ 0.1 = **10×**.
3. Always 68: 68 × 24 = **1,632 server-hours**. Autoscaled: 68 × 2 + 8 × 22 = **312**. About **81% less**.
4. For 3 minutes, 20,000 − 3,500 = 16,500 requests a second can't be served: 16,500 × 180 = **2,970,000 requests** dropped (or queued until they time out).
1Show the Level 3 solution
2Tested solution (Python 3):
3
4```python
5import math
6
7
8def servers_needed(rps, per_server_rps, target_util):
9 return max(1, math.ceil(rps / (per_server_rps * target_util)))
10
11
12def simulate(traffic, per_server_rps=500, target_util=0.6, min_servers=2, max_servers=100,
13 warmup=3, schedule=None, start=None):
14 ready = start if start is not None else min_servers
15 pending = [] # minutes when ordered servers become ready
16 history, dropped = [], 0
17 for minute, rps in enumerate(traffic):
18 ready += sum(1 for t in pending if t == minute)
19 pending = [t for t in pending if t > minute]
20 want = servers_needed(rps, per_server_rps, target_util)
21 if schedule:
22 floors = [n for m, n in schedule.items() if m <= minute]
23 if floors:
24 want = max(want, max(floors)) # keep the scheduled minimum
25 want = min(max(want, min_servers), max_servers)
26 coming = ready + len(pending)
27 if want > coming:
28 pending += [minute + warmup] * (want - coming) # scale out
29 elif want < ready and not pending:
30 ready = want # scale in
31 dropped += max(0, rps - ready * per_server_rps) * 60
32 history.append(ready)
33 return history, dropped
34```
35
36All three checks pass: steady traffic settles at 7 servers with nothing dropped; the sudden jump drops **2,970,000** requests during the 3-minute warm-up and settles at 67; scheduling ahead drops **0**. The full file (with Amdahl's law and the Level 4 code) is in `code/ch06_scaling.py`.
1Show the Level 4 solution
2**The bug:** the "add" and "remove" thresholds are **the same number**, and there's **no cooldown**, so the fleet **flaps**.
3
41. Minute 1: 4 servers, CPU ≈ 65% > 60% → **add** → 5.
52. Minute 2: 5 servers, CPU ≈ 52% < 60% → **remove** → 4.
63. Minute 3: back to 65% → add → 5.
74. Minute 4: 52% → remove → 4. And so on: **30 changes in 30 minutes** in the tested simulation, each one a server starting or being killed (and dropping its in-flight requests).
8
9**The fix:** a **gap** between thresholds (hysteresis) and a **cooldown**:
10
11```python
12def autoscale(servers, avg_cpu, minutes_since_change, cooldown=5):
13 if minutes_since_change < cooldown:
14 return servers
15 if avg_cpu > 0.70:
16 return servers + 1
17 if avg_cpu < 0.40:
18 return servers - 1
19 return servers
20```
21
22At 65% the fleet now stays at 4 (zero changes in the simulation). Also drain servers before removing them, so in-flight requests finish (Chapter 7).
1Show the Level 5 (sketch) solution
21. **Move the state out:** sessions → **Redis** (or signed tokens); book covers → **object storage** served through a CDN (Chapters 11 and 26); emails and image processing → a **queue** with workers; the database → its **own machine**.
32. **New layout:** DNS → load balancer → **N stateless app servers** (at least 2) → Redis, the database primary plus a read replica, object storage; workers reading from a queue.
43. **Database for the sale:** find and fix the top queries, add indexes; cache the book pages and catalogue in Redis; scale the database machine **up** before the sale; add **read replicas** for browsing traffic; put a **connection pooler** in front; keep checkout on the primary.
54. **Autoscaling:** **scheduled** minimum of about 68 app servers from 30 minutes before the sale; **target tracking** on requests per server (about 300) as a safety net; minimum 8, maximum about 100, capped by what the database can take; workers scale on queue length.
65. **Risks:** the database or connection limits saturate before the app tier does; caches start cold at the opening minute. **Test:** a load test at the sale peak in a production-like environment; warm the cache with the top books before opening; rehearse the scheduled scaling once.