Module 8: Project — Design Enlace End to End
8. Project: design Enlace end to end
Description
The end of the journey back has arrived, and with it the deliverable that crowns the entire guide: the complete design of Enlace, end to end. In this project no tool is added; you put together the seven of the previous modules into a single defensible artifact, going through the 4-step framework in one flow. It is exactly what you produce —and defend— in a system design interview or in a real architecture review: a design with requirements, an executed capacity table, a diagram, and a list of tradeoffs justified with numbers.
The lesson has three parts. First, the prompt of the project —what you have to produce, with your own hands, before looking at the solution—. Second, the rubric —the criteria a good design is evaluated with, so that you know what distinguishes a solid proposal from one that only looks complete—. And third, inside a <details>, the complete reference solution: Enlace's design done, with its mermaid diagram, its capacity table run in Python, and its list of tradeoffs, so that you compare your design with a defensible one. It is not the correct answer —another target hit ratio, another number of shards, another TTL policy are also defensible—, but it is a solid proposal with each decision tied to a number. At the end, the guide closes: the summary of the eight modules as an arc, and where to continue in the ecosystem.
Connection to the module: this is the capstone of the capstone. It uses step 1 (lesson 2), step 2 (lesson 3), step 3 (lesson 4) and step 4 (lessons 5, 6, 7) —all at once— to produce the final artifact. It is also the close of the guide: when you finish it, you will have walked the complete map that module 1 opened, and you will hold the skill the guide promises —take a vague prompt and produce a defensible distributed design with numbers—.
The craft's practical exam
Think of it this way. A pilot does not get their license by answering multiple-choice questions about aerodynamics. They get it in the practical exam: they climb into the cockpit, with the examiner beside them, and fly for real —take off, navigate, handle a simulated emergency, land—. The examiner does not ask them "what is lift?"; they observe whether they know how to fly, whether they go through the checklist in order, whether they make correct decisions under pressure, whether they can justify why they turned when they turned. Everything the pilot studied separately —meteorology, engines, navigation, procedures— is tested together, in a single session, producing a real flight.
Notice what the examiner evaluates. They do not look for the "perfect" maneuver —there is none—; they look for judgment: did the pilot make reasonable decisions for the conditions they had?, can they explain each one?, did they follow the method without skipping steps?, did they anticipate the problems instead of reacting late? Two pilots can fly the same route in different ways and both pass, because what is evaluated is not that they coincide with a single answer, but that each decision is justified and the method is respected.
This project is your practical exam of system design. We do not ask you "what is a cache?"; we ask you to design all of Enlace —requirements, estimation, diagram, deep dive, tradeoffs— producing a real design. And like the pilot's examiner, the rubric does not look for you to coincide with the reference solution word for word; it looks for judgment: that each box has its number, that you go through the framework in order, that you know how to defend each tradeoff, that you anticipate the bad day. A design different from the reference one can be just as good, if each decision is justified. That is what makes this an exam of the craft and not of memory.
It is worth spelling it out:
The final project evaluates judgment, not coincidence. A good Enlace design is not the one that copies the reference solution; it is the one that goes through the 4-step framework in order, justifies each component with a number, defends each tradeoff with its "I gain/I pay", and anticipates the day things fail. Like the pilot's exam: there is no perfect flight, there are justified decisions.
The project prompt
You are the engineer in charge of designing Enlace, a URL shortener, from scratch. The team gives you the vague prompt —"design a URL shortener that holds scale"— and the starting assumptions (the numbers that in a real problem you would extract from the client with questions):
- 100 million new URLs a month.
- Read:write ratio = 100:1 (people visit much more than they create).
- Retention 5 years; average long URL ~500 bytes; complete record ~1 KB.
resolvelatency < 100 ms; availability 99.9%.
Your deliverable is the four artifacts of the 4-step framework:
- Requirements (step 1): functional, non-functional with a number, and scope table.
- Capacity table (step 2): QPS, storage, bandwidth, memory —executed, not quoted—.
- High-level design (step 3): the diagram of the distributed architecture, with each box justified.
- Deep dive + tradeoffs (step 4): the write path and the read one, the data and compute scaling, and the list of tradeoffs with its numeric "I gain/I pay".
Do it yourself first, with your own hands, going through the framework in order. When you finish, open the reference solution and compare —not to see if you coincide, but to see if your judgment holds—.
The rubric
This is how an Enlace design is evaluated. It is not a list of components that must appear; it is a list of qualities a good design has. Use it to review yours.
| Criterion | What is looked for | Signal that it is missing |
|---|---|---|
| Explicit requirements | The three artifacts of step 1, with numeric non-functional ones and a scope table in both directions | "Make it fast and scalable" (no number); it is not said what is left out |
| Executed numbers | The capacity table run, reproducing the anchors (~40, ~4,000, 6 TB, 62⁷, 333 MB) | Numbers quoted from memory; the output cannot be reproduced |
| Each box justified | Every component of the diagram points to the row of the table that requires it | A box with no number behind it; a component from another guide (queues, microservices) |
| Disciplined simplicity | The simplest design that meets the requirements; neither superfluous nor missing | Bloated design (Kafka, Elasticsearch) or incomplete (a single DB for 6 TB) |
| Tradeoffs with "I gain/I pay" | Each important decision with its benefit, its cost and its number | "I chose X" without saying what is paid; consistency chosen by taste, not by the data |
| The bad day | Anticipates the mass miss, the failure of a node, the growth | Only describes the happy case; does not say what happens if the cache empties |
| Borders respected | Mentions and links what belongs to other guides (failover, events, styles) without invading it | Designs event sourcing or circuit breakers as if they were from this guide |
A design that meets the seven criteria is defensible against any question. Notice that none asks "use this technology"; all ask for justification, discipline and honesty. That is what is evaluated in the craft.
Reference solution
See the complete reference solution (Enlace design end to end)
Here is a complete and defensible design of Enlace, going through the 4-step framework. It is not the only correct answer, but each decision is tied to a number.
Step 1 — Requirements
FUNCTIONAL
F1 shorten(long_url) -> short_code (base62, 7 chars; enla.ce/<code>)
F2 resolve(short_code) -> 302 Location: long_url
F3 nonexistent short_code -> 404
NON-FUNCTIONAL (with number)
writes ~40/s reads ~4,000/s (100:1, READ-HEAVY)
storage ~6 TB (5 years) latency resolve < 100 ms
availability 99.9% (<=8.76 h/year) consistency eventual OK for resolve
SCOPE v1
IN: shorten, resolve, 404, 302 redirect
OUT: click analytics, custom URLs, expiration, auth, rate-limit
Key scope decision: defer analytics, because incrementing clicks on every resolve would turn each read into a write and shoot the writes from ~40/s to ~4,040/s (×100), changing the entire system.
Step 2 — Capacity table (executed)
# Reference solution: Enlace's capacity table.
writes_per_month = 100_000_000
seconds_per_month = 30 * 24 * 3600
qps_write = writes_per_month / seconds_per_month
qps_read = qps_write * 100
records = writes_per_month * 12 * 5
storage_tb = records * 1024 / 1e12
code_space = 62 ** 7
read_bw_mb = qps_read * 500 / 1e6
working_set = (writes_per_month / 30) * 0.20
cache_mb = working_set * 500 / 1e6
print(f"writes/s = {qps_write:6.1f} (~40)")
print(f"reads/s = {qps_read:6.0f} (~4000)")
print(f"records 5y = {records:,}")
print(f"storage = {storage_tb:5.2f} TB (~6 TB)")
print(f"62^7 = {code_space:,} ({records/code_space:.2%} used)")
print(f"bandwidth = {read_bw_mb:.2f} MB/s read (network is NOT a bottleneck)")
print(f"working set = {working_set:,.0f} entries = {cache_mb:.0f} MB")
Output (Python 3.14.0):
writes/s = 38.6 (~40)
reads/s = 3858 (~4000)
records 5y = 6,000,000,000
storage = 6.14 TB (~6 TB)
62^7 = 3,521,614,606,208 (0.17% used)
bandwidth = 1.93 MB/s read (network is NOT a bottleneck)
working set = 666,667 entries = 333 MB
Signals: ~40/s → calm write (one primary); ~4,000/s → bottleneck, cache + load balancing; 6 TB → sharding; 62⁷ with 0.17% → IDs plentiful (7 chars); 2 MB/s → network does not matter; 333 MB → cache dirt cheap.
Step 3 — High-level design
graph TD
C[Client / Browser] -->|HTTP| LB{{Load balancer<br/>round-robin + health checks}}
LB --> S1[Server 1<br/>stateless]
LB --> S2[Server 2<br/>stateless]
LB --> S3[Server 3<br/>stateless]
S1 --> CACHE[(Redis cache<br/>~333 MB, hit 0.90)]
S2 --> CACHE
S3 --> CACHE
CACHE -.miss 10%.-> ROUTER[Shard router<br/>consistent hashing + vnodes]
S1 -->|write| ROUTER
ROUTER --> P0[(Shard 0 PRIMARY)]
ROUTER --> P1[(Shard 1 PRIMARY)]
P0 -->|repl. log| R0[(replicas x2)]
P1 -->|repl. log| R1[(replicas x2)]
Each box, its number: load balancer + stateless servers (99.9% + 4,000 reads/s), cache (4,000 reads/s + <100 ms), sharding (6 TB), consistent hashing (grow without a storm), primaries (40 writes/s), replicas (386/s residual + redundancy).
Step 4 — Deep dive
Write path (shorten, ~40/s): stateless server → global counter → base62_encode (1000000 → '4c92') → router → shard primary. Uniqueness by construction (counter, without verifying collisions); 7 characters because 62⁷ = 3,521,614,606,208 gives 587× the demand (6 just barely reaches, 8 wastes). The calm path.
Read path (resolve, ~4,000/s): server → cache (hit 90% → 1 ms; miss 10% → router → replica → 50 ms → populate). Average latency 0.9·1 + 0.1·50 = 5.90 ms (8.5× vs without a cache). Working set 333 MB (80/20 rule, 0.005% of the 6 TB). Residual to the DB: 0.1 × 3,858 = 386/s (average), ~1,157/s (peak ×3). Eviction allkeys-lru; TTL 24 h + jitter.
Scaling data: replicas (1–2 minimum for 386–1,157/s, 2–3 in production for N−1 and for the mass miss of 3,858/s); sharding by short_code with consistent hashing + vnodes (growing remaps 130,623 / 13.1%, not 888,920 / 88.9%). Scaling compute: round-robin load balancing over stateless servers + health checks. Consistency: eventual/async for resolve (almost immutable data; only inconsistency = benign fleeting 404; CAP → availability, PACELC → latency); SLO 99.9%.
The list of tradeoffs
| Decision | I gain | I pay | Number |
|---|---|---|---|
| Cache in front of the DB | 5.90 ms; DB sees 386/s | old datum (mitigated) | hit 0.90; 333 MB |
| Read replicas | distribute reads; redundancy | replication lag (fleeting 404) | 386/s; N−1 |
Sharding by short_code | distributes 6 TB; no hotspots | cross-shard queries (Enlace has none) | 6 TB / N |
| Consistent hashing | growing moves 13.1% | ring table (vnodes) | 130,623 vs 888,920 |
| Stateless servers + LB | horizontal scale; tolerates failures | shared state is the bottleneck | 99.9% |
| Eventual consistency | low latency; read scale | reads see a datum from ms ago | almost immutable data |
| 302 redirect | door to future analytics | browser does not cache | scope v1 |
The bad day
If the cache empties (restart, deploy), the hit ratio drops to 0: latency → 50 ms, the DB gets 3,858/s at once (×10). Mitigations: warm up the cache in a staggered way, jitter in the TTLs, and size the replicas for the peak (2–3 per shard). The cache protects 99% of the time; the replicas are the net below the net.
The guide's summary: the eight modules as an arc
With Enlace's design finished, the entire guide closes. You walked eight modules, and each one solved a crack the previous one left exposed —the same four cracks of the single box of module 1—:
- Module 1 — How to approach. Understand before drawing; the 4-step framework; Enlace in a single box, with its cracks measured.
- Module 2 — Estimation. The napkin math: ~40 writes/s, ~4,000 reads/s, 6 TB, 62⁷ —the anchor numbers that governed every decision—.
- Module 3 — Data model and IDs. The
Linkrecord; theshort_codewith counter + base62; why 7 characters are enough. - Module 4 — Cache. Cache-aside; hit ratio 0.90 → 5.90 ms; working set ~333 MB (80/20 rule).
- Module 5 — Scale the DB. Replicas (primary/replica, lag); sharding; consistent hashing (13.1% vs 88.9%).
- Module 6 — Load balancing and stateless. Load balancer with health checks over stateless servers; the state lives outside.
- Module 7 — Reliability and consistency. Redundancy; CAP/PACELC; eventual vs strong; SLA/SLO as a number.
- Module 8 — Project. The seven pieces together in a defensible distributed design.
The skill that comes out of here is the one module 1 promised: take a vague prompt —"design a URL shortener"— and produce a defensible design with numbers, a diagram and explicit tradeoffs. You no longer freeze before the blank page; you go through the 4-step framework, and each step tells you your next move. And —the most transferable— the method is not Enlace's: it is any system's. Change the numbers of the prompt, and the same framework produces the design of a pastebin, a photo service, a "like" counter. Enlace was the case; the framework is what you take with you.
Where to continue: the ecosystem
This guide taught the fundamentals of design and scaling, and on purpose left borders with its sibling guides of the ecosystem. Now that you master the fundamentals, those guides are the next step —each one deep-dives into a topic we only touched here as a limit—:
architectural-styles-and-boundaries-guide— the debate we avoided: monolith vs. microservices, DDD, bounded contexts. Enlace was one service without entering into how to draw the borders between services. If you asked yourself "should I split Enlace into microservices?", the answer is there.event-driven-architecture-guide— what the scope deferred: message queues, event sourcing, CQRS, streaming. Enlace's click analytics —the one we took out of the v1 because it shot up the writes— is designed here, with queues and asynchronous processing.resilience-and-reliability-patterns-guide— what we mentioned without deep-diving: circuit breaker, bulkhead, retry with backoff, the failover in depth (how a downed primary is detected, a replica is promoted and "split brain" is avoided), idempotency. When we said "the replica takes over", this guide says how.api-design-and-integration-guide— the contract we left minimal: versioning, pagination, REST/gRPC contracts. Enlace'sshorten/resolvewas a two-operation contract; designing real APIs, with their evolution and their guarantees, is the topic of that guide.
The mechanical rule for choosing where to go: if the question is "monolith or microservices?", it is the styles one; if it is "how do I process asynchronous events?", the events one; if it is "how do I prevent a failure from taking everything down?", the resilience one; if it is "how do I design and version my API?", the APIs one. The fundamentals you learned here —estimate, cache, scale, reason about tradeoffs— are the foundation on which all of them build.
Common mistakes
Delivering a design without having gone through it in order. What happens: someone jumps straight to the diagram (step 3) and the tradeoffs (step 4) without writing the requirements (step 1) or executing the table (step 2), and presents a design that "looks complete" but whose justification does not hold —they cannot say why three replicas, nor why sharding by short_code, because they did not do step 2—. Why it happens: the diagram is the visible and fun thing; requirements and estimation feel like a formality. How to spot it: if your deliverable does not have the four artifacts, or if the diagram cannot point to the table, you skipped steps. How to fix it: go through the framework in order, even if you know Enlace by heart. Step 1 justifies step 2, which justifies 3, which justifies 4. A design without that chain is a pretty drawing without a foundation.
Optimizing to coincide with the reference solution. What happens: someone looks at the reference solution and adjusts their design to coincide —same hit ratio, same number of shards, same words— thinking that "coinciding" is "getting it right". They lose the opportunity to exercise their own judgment. Why it happens: the exam of the craft is confused with an exam of memory. How to spot it: if you changed your design only to look like the reference, without a numeric reason, you are copying. How to fix it: the rubric evaluates judgment, not coincidence. A hit ratio of 0.95 instead of 0.90, or four shards instead of two, is just as valid if you justify it with numbers. Like the pilot: two different flights pass if each decision is grounded. Defend your design with your numbers; do not bend it to imitate someone else's.
Closing the design at the happy case. What happens: someone delivers a design that works perfectly on the good day —hot cache, all nodes alive, flat traffic— and does not say a word about what happens when the cache empties, a node goes down, or a viral peak arrives. The design looks robust and is fragile. Why it happens: the happy case is the one thought about first, and anticipating failures is uncomfortable. How to spot it: if your deliverable does not have a "the bad day" section, it is incomplete. How to fix it: always include what happens at hit ratio 0 (the mass miss: 3,858/s to the DB), what happens if a primary goes down (failover, one affected shard), what happens at the peak (×3). A design that only describes the good day is half a proposal —the other half is proving it survives the bad day—.
Exercises
Exercise 1 — Self-evaluate your design with the rubric. Take the Enlace design you produced (or, if you did not do it, the reference solution) and run the seven rubric criteria through it, one by one. For each criterion, say whether your design meets it and with what concrete evidence (which number, which box, which section). Identify the weakest criterion of your design and how you would reinforce it.
See solution
There is no single answer —it depends on your design—, but this is what a rigorous self-evaluation of the reference solution looks like:
- Explicit requirements: ✅ The three artifacts of step 1 with numeric non-functional ones and scope in two directions.
- Executed numbers: ✅ The step 2 table runs and reproduces the anchors (38.6, 3858, 6.14 TB, 62⁷, 333 MB).
- Each box justified: ✅ Each component of the diagram points to its row of the table.
- Disciplined simplicity: ✅ No queues, microservices or search engine; without hiding the DB in a cylinder.
- Tradeoffs with "I gain/I pay": ✅ The tradeoffs table, each row with its number.
- The bad day: ✅ The mass-miss section with mitigations.
- Borders respected: ✅ Failover (resilience), analytics (events), etc. are linked.
The typically weakest criterion in one's own design is usually "the bad day" (it is the one most forgotten) or "each box justified" (it is easy to copy the canonical diagram without tying each box to a number). Reinforcement: for each box, write the line "this is here because step 2 gave X"; and add an explicit section on what happens at hit ratio 0, with a downed node, and at the peak ×3. The honest self-evaluation with the rubric is, in itself, a skill of the craft: knowing where your own design is weak before you are asked.
Exercise 2 — Adapt the design to a new requirement. The team changes a requirement: now click analytics enters the v1 (each visit has to be counted). Without redoing the whole design, describe: (a) which number of the capacity table changes and how; (b) which new component is needed and why; (c) which sibling guide that component belongs to.
See solution
- (a) The write number changes. Counting each visit turns each
resolveinto a write (incrementclicks). The writes go from ~40/s to40 + 4,000 = ~4,040/s—×100—. Enlace stops being read-heavy and becomes read-and-write-heavy; the write path, which was the calm one, becomes a bottleneck. - (b) A component is needed to absorb those writes without contention. Incrementing a counter 4,000 times/s on the same row creates lock contention. The solution is not to write each click straight to the DB, but to aggregate the clicks in memory and flush them periodically, or to queue the click events and process them asynchronously (a consumer that updates the counters in batches). That decouples the read path (which stays fast) from the counting (which is processed separately).
- (c) That component —event queue + asynchronous processing— belongs to
event-driven-architecture-guide. It is exactly the border Enlace marked when deferring analytics: counting at scale is a problem of event-driven architecture, not of the fundamentals of this guide.
The lesson: changing a scope requirement (putting in analytics) propagates through the whole capacity table and forces a new component —and that component lives in another guide—. That is why Enlace's v1 deferred it: not because it does not matter, but because its correct design crosses the ecosystem's border. A good designer sees that propagation before saying yes.
Exercise 3 — Defend your design against three interview questions. An interviewer questions your Enlace design. Answer each question in a couple of sentences, with numbers: (a) "Why a cache and not simply more database replicas?" (b) "Why 7 characters for the short_code?" (c) "Aren't you worried about consistency? What happens if two users read the same link and the replicas are out of sync?"
See solution
- (a) Cache vs. more replicas: a cache resolves a hit in ~1 ms from RAM, much faster than a replica that goes to disk (~50 ms), and a Redis instance (~1 GB, ~333 MB of working set) costs quite a bit less than a replica with a complete 6 TB copy. The cache absorbs 90% of the traffic cheap and fast (leaving 386/s to the DB); the replicas hold what the cache does not cover (the cold tail, the peak, the bad day). They complement each other, and the cache goes first because it is the cheapest and fastest layer. Removing the cache to "resolve everything with replicas" would be replacing the cheap layer with the expensive one.
- (b) 7 characters: because
62⁷ = 3,521,614,606,208(3.5 trillion), and the 5-year demand is 6,000 million, so I use 0.17% of the space —a factor of 587× slack—. With 6 characters (62⁶ ≈ 56,800 M) it would just barely reach, using >10% with no margin to grow; with 8 it would waste brevity. 7 is the minimum that gives comfortable slack: the answer is a calculation, not a magic number. - (c) Consistency: I am not worried, and because of the nature of the data: the
short_code → long_urlmapping is almost immutable —there are noUPDATEs that can diverge—, so two replicas will never show different destinations for the same code. The only possible inconsistency is a freshly createdshort_coderesolved from a replica that is behind: it gives a 404 of milliseconds (not the wrong datum, but "I do not have it yet"), it is very rare (the creator does not visit their link instantly from another machine) and it recovers on its own. I chose eventual consistency because the data does not change, and that tolerance is what buys me the read replicas and the scale. Strong consistency would force me to read everything from the primary and I would lose the scale, to protect against something that almost never happens and is benign.
The lesson: each answer ties the decision to a number and to the nature of the problem, not to a preference. That is defending a design —and it is exactly what the rubric and the pilot's examiner look for—. A design you cannot defend with numbers is not yours; it is copied.
Summary and next step
In this project —the capstone of the guide— you designed Enlace end to end, putting together the seven tools of the previous modules into a single defensible deliverable. You produced the four artifacts of the 4-step framework: the requirements (with a scope that defers analytics), the executed capacity table (~40, ~4,000, 6 TB, 62⁷, 333 MB), the diagram of the distributed architecture (load balancer → stateless servers → cache → sharded primaries with replicas), and the deep dive with the list of justified tradeoffs. And you saw, with the pilot's practical exam, that what is evaluated is not coinciding with the reference solution but exercising judgment: each box with its number, the framework in order, each tradeoff with its "I gain/I pay", and the bad day anticipated.
You closed the guide: the eight modules as a causal arc, the skill that comes out of here (take a vague prompt and produce a design with numbers, a diagram and tradeoffs), and the method transferable to any system. And you saw where to continue —the sibling guides of the ecosystem: architectural styles, event-driven architecture, resilience patterns, and API design—, each one deep-diving into a border we only touched here.
Before considering the guide finished you should be able to: go through the 4-step framework in one flow to design Enlace (or any system) from scratch; produce the four artifacts of the deliverable; self-evaluate a design with the rubric; and defend each decision against questions, with numbers and with the nature of the problem.
The next step is no longer in this guide: it is in the ecosystem. With the fundamentals of design and scaling mastered, choose the sibling guide that answers your next question —"monolith or microservices?", "how do I process events?", "how do I make this resilient?", "how do I design my API?"— and keep building on the foundation you just finished. You know how to approach an open problem, estimate it, model it, cache it, scale it, distribute it, reason about its tradeoffs, and design it whole. That is designing systems.
Resources
- System Design Primer — "Design a URL shortener" (complete exercise) — the same Enlace case solved end to end in the Primer (as
pastebin/shortener), with requirements, estimation, design and scaling. The ideal second voice to contrast your reference design with another defensible path. - Designing Data-Intensive Applications, by Martin Kleppmann — official site — the reference book of the craft, which will accompany you beyond this guide. Having finished the capstone, its Chapters 5 (Replication), 6 (Partitioning) and 9 (Consistency and Consensus) deep-dive into each decision you made in Enlace, and are the natural bridge toward the sibling guides.
- System Design Interview – An Insider's Guide, by Alex Xu — the canonical collection of complete system designs (shortener, feed, chat, notifications), with the same structure of requirements → estimation → design → deep dive as this guide. The resource to practice the 4-step framework on new cases, now that you master it with Enlace.