Module 8: Project — Design Enlace End to End

4. Step 3 — High-level design and the diagram

Description

With the contract (step 1) and the capacity table (step 2) in hand, the moment to finally draw has arrived: step 3 of the framework, the high-level design, and its star artifact, the diagram. In module 1 you drew Enlace in a single box —one server and one database— and measured, honestly, its four cracks: the tight read, the storage that fills up, the single point of failure. Now those cracks are plugged by what you learned in modules 4 to 7, and the capstone diagram incorporates all of them: the load balancer, the stateless servers, the cache, the primary with replicas, the sharding. The result is the complete distributed architecture of Enlace, in a single mermaid diagram, where —and this is the discipline of the lesson— each box points to the row of the capacity table that justifies it.

In this lesson you build that diagram incrementally: you start from the single box, and you add each component naming the number that requires it, so that you see the design is not copied from a pattern but derived from the estimation. You will understand why the load balancer is there (the compute scales), why the cache goes in front of the database (the 4,000 reads/s), why the database is a primary with sharded replicas (the 6 TB and the 100:1 ratio), and —crucially— why the diagram is still the simplest one that meets the requirements, without one extra box. It is Enlace's blueprint, the one lessons 5, 6 and 7 are going to deep-dive.

Connection to the module: this is the hinge of the journey, between the estimation (lesson 3) and the deep dive (lessons 5–7). It takes each signal of the capacity table and turns it into a box of the diagram; and the diagram, in turn, becomes the map of what has to be deep-dived: each component you draw here is one of the lessons that follow. It is step 3 applied to Enlace at full scale, the "grown-up" version of the single box of module 1.

The house blueprint, with all its installations

Think of it this way. An architect does not draw a single blueprint of the house; they draw several layers superimposed over the same floor plan. The first layer is the walls: where the rooms, the doors, the windows go —the structure—. But on top go others: the electrical installation (where the outlets, the panel, the wires), the plumbing (the water pipes that come in, the drains that go out), the HVAC. Each installation solves a different need —the walls give the shape, the electrical gives the light, the plumbing gives the water— and all share the same floor plan. The high-level blueprint shows them together, in one view, so that it is understood how everything collaborates: where the water comes in, where it goes out, where the panel that feeds each room is.

Notice two things about that blueprint. First: each installation is there because a need asks for it, not for decoration. There is no pipe that does not carry water anywhere; there is no wire that does not feed anything. If the architect drew a pipe "just in case", the plumber would ask "and where does this one go?", and there would be no answer. Second: the blueprint is legible —it shows the flow—. You can follow with your finger how the water comes in from the street, passes through the meter, goes up to the tank, and comes down to each tap. The blueprint is not a bunch of symbols; it is a story of how each thing flows through the house.

The high-level diagram of a system is that blueprint with all its installations. The servers, the cache, the database, the load balancer are the "installations" of Enlace, and each one is there because a number of the table asks for it —the load balancer because the compute scales, the cache because the reads are 4,000/s, the replicas because they have to be distributed—. And the diagram is legible: it shows how a write request flows (comes in through the load balancer, a server serves it, the primary writes it) and a read one (comes in, a server serves it, looks it up first in the cache, and only if it fails, in a replica). Just like the house blueprint, each box has its justification and the whole tells the story of how Enlace's two paths flow.

It is worth spelling it out:

The high-level design is the blueprint of the system: it shows all the components together and how the requests flow between them. Each component is there because a number of step 2 requires it —not one extra box—, and the whole is legible: you can follow with your finger the path of a write and that of a read. It is the simplest one that meets the requirements at the estimated scale.

From the simple design to the distributed one, one box at a time

The best way to understand the final diagram is to build it, adding each component with the number that justifies it —that way you see nothing is copied, everything is derived—. We start from the single box of module 1 and grow.

Starting point: the single box. One server and one database. It meets the functional requirements, but the capacity table marked its cracks: the tight read, the 6 TB that do not fit, the single point of failure.

graph LR
    C[Client / Browser] -->|HTTP| S[Enlace Server]
    S -->|SQL| DB[(Database)]

We add the cache (signal: ~4,000 reads/s + latency <100 ms). The read row of the table says "bottleneck". The cache absorbs 90% of the reads from RAM (5.90 ms average latency), and leaves the database only 386/s. It goes in front of the database, on the read path.

We add primary + replicas and sharding (signal: 6 TB + 100:1 ratio). The storage row says "does not fit on one machine": it is sharded by short_code (with consistent hashing) to distribute the 6 TB. And since the reads are 100× the writes, each shard is a primary (accepts the ~40 writes/s distributed) with read replicas (serve the reads the cache does not absorb).

We add the load balancer + stateless servers (signal: the compute also scales). A single application server is, itself too, a single point of failure and a bottleneck. Several stateless servers are placed (any one handles any request, because the state lives in the cache and the database, not in the server) behind a load balancer that distributes the traffic and takes out of rotation the one that fails (health checks). This plugs the last crack of the single box: the compute's single point of failure.

Put all together, this is Enlace's complete distributed architecture —the diagram that goes into the deliverable—:

graph TD
    C[Client / Browser]
    C -->|HTTP| LB{{Load balancer<br/>round-robin + health checks}}

    LB --> S1[Enlace Server 1<br/>stateless]
    LB --> S2[Enlace Server 2<br/>stateless]
    LB --> S3[Enlace Server 3<br/>stateless]

    S1 -->|read: 1st lookup| CACHE[(Redis cache<br/>~333 MB working set<br/>hit ratio 0.90)]
    S2 --> CACHE
    S3 --> CACHE

    CACHE -.miss 10%.-> ROUTER[Shard router<br/>consistent hashing<br/>by short_code]
    S1 -->|write| ROUTER
    S2 --> ROUTER
    S3 --> ROUTER

    ROUTER --> P0[(Shard 0<br/>PRIMARY)]
    ROUTER --> P1[(Shard 1<br/>PRIMARY)]
    P0 -->|replication log| R0a[(replica)]
    P0 --> R0b[(replica)]
    P1 --> R1a[(replica)]
    P1 --> R1b[(replica)]

What to expect. That diagram is all of Enlace, at full scale, in one view. Read it following the two paths with your finger, like the house blueprint:

  • Read path (resolve, ~4,000/s): the client reaches the load balancer, which sends it to one of the stateless servers (any one serves). The server looks it up first in the cache: 90% of the time (hit) the long_url is there and it responds in ~1 ms —end of the path, it does not even touch the database—. The remaining 10% (miss) goes to the shard router, which with consistent hashing over the short_code knows which shard it belongs to, and reads from a replica of that shard.
  • Write path (shorten, ~40/s): the client reaches the load balancer → a stateless server → the shard router → the primary of the shard the new short_code falls into. The primary copies the change to its replicas via the replication log. A single write, to the primary of the correct shard.

Notice the asymmetry the capacity table predicted: many read arrows that mostly die in the cache (90%), and few write arrows that go straight to the primary. The diagram draws the 100:1 ratio and the read-heaviness. And notice that each component has its number: the cache (~333 MB, hit 0.90), the shards (6 TB distributed), the servers (stateless, to scale the compute). There is not a single box without justification in the table —no search engine, no queue, no extra microservice—.

Why the state lives outside the server

There is a decision in that diagram worth stopping on, because it is the one that makes the load balancing possible: the servers are stateless. It means that an Enlace server does not store anything of its own between requests —no sessions, no user data, no last query—. All the state lives in two shared places: the cache (the hot working set) and the database (the 6 TB). The server is pure logic: it receives a request, looks up the cache or the database, responds, and forgets.

Why does it matter? Because if the state lived inside each server, the load balancer could not send the next request to any one —it would have to send it to the same server that handled the previous one (sticky sessions)—, and if that server goes down, the state is lost and the user notices. With stateless servers, on the other hand, any server handles any request: the load balancer distributes freely, adding capacity is just plugging in another identical server, and if one goes down, the health check takes it out of rotation and the others carry on —without any user losing anything—. The absence of state in the server is, exactly, what makes the compute scale horizontally and tolerate failures. It is a design decision with a number behind it: to serve ~4,000 reads/s with redundancy, you need several servers, and "several servers" only works well if they are interchangeable, that is, stateless.

Why this diagram, and not a more complex one (nor a simpler one)

Enlace's diagram is exactly as complex as the numbers require —no more, no less—, and it is worth defending that border in both directions.

Why not simpler. We could not deliver the single box of module 1, because the capacity table breaks it: 6 TB do not fit on one machine (you need sharding), 4,000 reads/s on a single DB saturate it (you need cache and replicas), and a single box does not meet 99.9% availability (you need redundancy and load balancing). Each box we added plugs a measured crack. Removing any reopens a crack the table already identified.

Why not more complex. Nor do we throw in message queues (analytics is out of scope —step 1—, and queues are from the events guide), nor microservices (two operations over ~40 writes/s do not ask to split the service; that debate is from the styles guide), nor a search engine (Enlace looks up by primary key, not by text), nor circuit breakers and bulkheads in depth (resilience patterns, sibling guide). Each of those pieces either has no number to justify it, or belongs to another guide of the ecosystem. Throwing them in would be the bloated design of exercise 3 of lesson 1.

The rule, again, is the one of the craft: the simplest design that meets the requirements at the estimated scale wins. Enlace's diagram is the embodiment of that rule —five types of component (load balancer, stateless servers, cache, primaries, replicas), each with its number, and not one extra—. That discipline is what separates an engineering design from a collection of impressive components.

Common mistakes

Drawing components without the number that justifies them. What happens: someone draws the complete diagram —load balancer, cache, replicas, shards— from memory, because "that is the shortener architecture", without being able to point to which row of the table requires each box. The design is correct by chance, not derived. It shows when the prompt changes: if Enlace were an intranet one (0.6 reads/s), the same diagram would be over-built by a factor of a thousand, and whoever copied it would not notice. Why it happens: it is easier to memorize the canonical diagram than to reconstruct why it is that one. How to spot it: for each box, ask yourself "what number requires it?". If you have no answer, the box is unjustified. How to fix it: build the diagram incrementally, adding each component with its signal —"cache because 4,000 reads/s", "sharding because 6 TB"—. A derived diagram changes with the numbers; a copied one does not.

Putting the state inside the servers (sticky sessions by default). What happens: someone draws several servers behind a load balancer but stores state in each one (user session, local cache, the last query), and for it to work they have to use sticky sessions —each user always to the same server—. When that server goes down, the user loses their state; and adding capacity does not help the users already "stuck" to a full server. The load balancing stops actually balancing. Why it happens: storing state in the server is the intuitive thing (it is where the code runs). How to spot it: if your load balancer needs to send each user always to the same server, your servers have state. How to fix it: take the state out of the server and put it in a shared place (cache + database). Stateless servers = any one serves anyone = free load balancing, elastic capacity and fault tolerance. It is the decision that makes the rest of the diagram possible.

Drawing the database as a single box "because it fits in the diagram". What happens: someone, for drawing convenience, puts the database as a single cylinder, even though the table said 6 TB (sharding) and 100:1 ratio (replicas). The diagram hides Enlace's two most important scale decisions under a single symbol, and the subsequent "deep dive" has nothing to lean on. Why it happens: drawing a cylinder is easier than drawing primary + replicas × shards. How to spot it: if your database is a single box but your table says 6 TB and read-heavy, the diagram contradicts the estimation. How to fix it: the high-level diagram must show the scale decisions the numbers require —the primary per shard, the read replicas, the consistent-hashing router—, even schematically (two shards represent "N shards"). The diagram is the blueprint; if it hides the plumbing, it is useless for building.

Exercises

Exercise 1 — Justify each box. For each component of Enlace's diagram, say which row of the capacity table (lesson 3) justifies it, in one sentence: (a) the cache; (b) the database sharding; (c) the read replicas; (d) the several stateless servers behind the load balancer; (e) the consistent-hashing router.

See solution
  • (a) The cache: the row ~4,000 reads/s + latency <100 ms. It is read-heavy; the cache absorbs 90% from RAM (5.90 ms) and offloads the database.
  • (b) The sharding: the row ~6 TB at 5 years. It does not fit comfortably on one machine; the data is distributed across shards.
  • (c) The read replicas: the row 100:1 ratio (read-heavy). The reads the cache does not absorb (386/s, more at the peak) are distributed among replicas; a single node per shard cannot keep up and gives no redundancy.
  • (d) The stateless servers + load balancer: implicitly, the row reads/s + availability 99.9%. A single application server is a bottleneck and single point of failure; several stateless ones behind a load balancer scale the compute and tolerate failures.
  • (e) The consistent-hashing router: derives from the sharding + the need to grow (M5): it distributes the keys among shards and allows adding one by moving only ~1/(N+1) of the data (13.1%, not 88.9%).

The lesson: each box of the diagram has, behind it, a row of the table. A diagram without this correspondence is decoration; with it, it is a derived design.

Exercise 2 — Draw the intranet Enlace diagram. A client wants Enlace for their intranet: 1,000 new URLs a month, same 100:1 ratio. Estimate quickly the writes/s and reads/s, and draw (in ASCII or by describing) the appropriate high-level diagram. How many of the boxes of the big diagram survive, and why?

See solution

Quick estimate: 1,000 / (30·24·3600) ≈ 0.0004 writes/s; reads = × 100 ≈ 0.04 reads/s. That is, one read every ~25 seconds. Storage at 5 years: 1,000 × 12 × 5 × 1 KB ≈ 60 MB.

The appropriate diagram is the single box of module 1:

Client --HTTP--> Enlace Server --SQL--> Database

Almost none of the boxes of the big diagram survive, and for one reason: the numbers do not require them. 0.04 reads/s do not need a cache (a single DB serves them asleep), nor replicas (there is no load to distribute), nor sharding (60 MB fit a thousand times on one machine), nor load balancing (one server is more than enough for 0.04 requests/s). Throwing in any of those components would be over-engineering by a factor of thousands. Maybe one replica survives —not for load, but for redundancy if the intranet requires 99.9%—, but not even that is mandatory.

The lesson, which is the one of exercise 3 of lesson 1 seen right-side up: the same problem, with other numbers, is another design. Enlace's diagram is not "the shortener's diagram"; it is the diagram of this shortener, at this scale. Change the numbers and the design changes. That is why step 2 goes before step 3: the table decides how many boxes.

Exercise 3 — Spot the superfluous box and the missing one. A colleague draws this Enlace diagram: client → load balancer → three stateless servers → a Kafka message queue → a single database server (no replicas, no shards). (a) Which component is superfluous and why (what number does not justify it and which guide does it belong to)? (b) What is missing and why (what number requires it)? (c) Correct the design in one sentence.

See solution
  • (a) The Kafka queue is superfluous. No number of Enlace justifies it: the writes are ~40/s (a primary absorbs them directly, with no need to queue them) and analytics —which could use a queue— is out of scope (step 1). Moreover, message queues are territory of event-driven-architecture-guide, not this guide. It is an extra box, from the bloated design.
  • (b) The cache is missing and the database scaling (replicas + sharding) is missing. The cache is required by the row ~4,000 reads/s + latency <100 ms (without a cache, the 4,000 reads all fall on the DB). The sharding is required by the row 6 TB (they do not fit on a single node), and the replicas, by the row 100:1 ratio (the reads have to be distributed). Their "single database server" reopens three cracks the numbers already closed.
  • (c) Correction: remove the Kafka queue, put a cache in front of the database, and turn the "single DB server" into sharded primaries (by short_code, consistent hashing) with read replicas.

The lesson: a diagram is evaluated in both directions —what is superfluous (no number to justify it, or from another guide) and what is missing (a number requires it and it is not there)—. The colleague threw in complexity where it was not needed (Kafka) and skimped on it where it was (cache, replicas, sharding). The correct Enlace design is humble where the numbers allow it and robust where the numbers require it.

Summary and next step

In this lesson you executed step 3 of the framework: Enlace's high-level design and its mermaid diagram, the third artifact of the deliverable. You built it incrementally, starting from the single box of module 1 and adding each component with the number that justifies it: the cache (for the ~4,000 reads/s), the sharding by short_code (for the 6 TB), the read replicas (for the 100:1 ratio), and the stateless servers behind the load balancer (to scale the compute and meet 99.9%). The final diagram draws the asymmetry the table predicted —many reads that die in the cache, few writes to the primary— and is legible as a blueprint: you follow the two paths with your finger.

You saw why the state lives outside the server (stateless servers = free load balancing, elastic capacity, fault tolerance), and why the diagram is exactly as complex as the numbers require —no extra queues or microservices or search engine (border with the sibling guides), without hiding the database in a single cylinder—.

Before moving on you should be able to: build Enlace's diagram adding each box with its numeric justification; explain why the servers are stateless; follow with your finger the read path and the write one; and detect in a diagram the superfluous box and the missing one.

What comes next is step 4: deep dive. With the blueprint drawn, we go down to the detail of each critical component, starting with the path the diagram shows short and calm. In lesson 5 you deep-dive into the write path: how shorten generates the short_code (counter + base62, and why 62⁷ is enough for 6,000 M), which shard the write goes to, and why the ~40 writes/s make this Enlace's easy path. It is the first "deep dive" of the capstone.

Resources