Architecture diagrams and trade-offs — what to draw and what to leave out
Explaining a design to people usually involves drawing. But the diagram itself isn't the design.
For the same requirement, multiple valid setups exist. The diagram shows one of them, and why you picked it isn't drawn. The explanation's substance is the comparison between chosen and discarded options. Draw diagrams as tools that support that comparison.
What you'll learn in this chapter
- Deciding what boxes and lines represent
- Steps to map requirements to boxes
- How to choose what to leave out
- mermaid syntax
- Multiple valid answers for the same requirement (distribute on write, or gather on read)
- Inspecting your diagram on the three axes of availability, scalability, consistency
Builds on Part 11 up to here (Capacity estimation, Scaling directions, Availability and redundancy, Data distribution). The elements in the diagrams come from there.
What this chapter does not cover
Comprehensive coverage of UML or other notation systems isn't included. Drawing-tool comparison isn't either. mermaid syntax itself is covered by the Syntax reference.
What boxes and lines represent
Most illegible architecture diagrams share a cause: things of different kinds drawn as the same shape. Decide first.
| Element | Represents | Decide up front |
|---|---|---|
| Box | Running thing (process, service) | One box = one machine, or a group of many? |
| Cylinder | Stateful thing (database, cache, object storage) | Different shape from box, because impact on failure differs |
| Solid line | Sync call (waits for response) | Direction: caller → callee |
| Dashed line | Async signal (doesn't wait) | Via queues, log ingestion, etc. |
| Enclosure | Boundary (network, account, data center) | What changes when crossing |
Reason for splitting box and cylinder: growth differs. Stateless boxes scale in count; stateful ones need replication or splitting (Scaling directions). Different shape shows readers "this can't be added easily".
Reason for splitting solid and dashed: failure propagation differs. Sync calls: slow partner slows self. Async: separable.
Map requirements to boxes
Every box in the architecture diagram has a reason to exist. Boxes whose reason you can't state are candidates for removal.
| Box | Placed for |
|---|---|
| Load balancer | Enable adding machines + exclude failing ones from routing |
| Cache | Keep reads from reaching the database |
| Queue | Flatten write peaks + return fast responses |
| Replica | Distribute reads + continue when primary fails |
Writing this out reveals unneeded boxes. A design with a queue when writes are 4 per second — from Capacity estimation numbers — reveals itself when the two meet.
Conversely, requirements without a box to serve them show up too. As "there's only one box, but they said 'must not stop'".
What to leave out
Drawing everything makes it unreadable. The judgment of leaving out is harder, so set criteria.
Draw only elements relevant to the question the diagram answers. A "how to handle load" diagram doesn't need monitoring or log-collection lines.
| Leavable | Reason |
|---|---|
| Monitoring, logs, secret management | Same across setups. Separate diagram |
| Internal class structure | Different layer. Architecture is at machine/process granularity |
| Concrete product names (in discussion phase) | "Cache" suffices. Product selection is a later decision |
| Details of paths that fire only on failure | Draw main flows; supplement exceptions in prose |
Say what you left out. Reading "absent from the diagram" as "doesn't exist" causes misunderstanding.
mermaid syntax
Diagrams in this repo use mermaid. For architecture, use flowchart and enclose boundaries with subgraph.
Lines from App 2 to the data layer are left out — they are the same as App 1's.
Three patterns to keep:
- Top-down or left-right, flow direction unified (
TB/LR). Mixed direction becomes untraceable - Stateful things use
[( )]as cylinders. Different growth is visible at a glance - Async lines use
-.->with labels for what flows
Actual examples close to real setups are at the top of the Practical AWS Guide. Nested subgraph expresses network boundaries.
Multiple correct answers for the same requirement
Design explanation becomes trade-off explanation because there isn't one answer. By example.
Requirement: "Show the posts of people you follow in a newest-first listing." Two setups hold.
Distribute on write
At post time, write into every follower's list.
- Fast reads. Just read your list in order
- Heavy writes. 100,000 followers means 100,000 writes per post
- Space heavy. Same post duplicated per follower
Gather on read
Posts saved in one place; on list open, collect posts of followees.
- Light writes. Save one item
- Heavy reads. On each open, pull followees, collect each's posts, and merge
- Space small
Which to choose
Read/write ratio decides (Capacity estimation). If opens vastly exceed posts, pushing heavy work to the write side reduces total.
But skewed follower-count distribution breaks the distribute-on-write approach. An account with 1M followers writes 1M items per post.
In practice, mix. Normal accounts distribute on write; accounts with disproportionately many followers gather on read and merge. This "mixing" choice itself proves you understand the trade-off.
Inspect on three axes
Once drawn, reinspect on three axes. Each has a "breaking question".
| Axis | Breaking question | What surfaces |
|---|---|---|
| Availability | What happens if this box goes down? Which is the only box? | SPOFs, degradation-unable dependencies (Availability and redundancy) |
| Scalability | If users grow 10×, where clogs first? | Stateful servers, non-scaling datastores (Scaling directions) |
| Consistency | When crossing replicas or splits, do users see old values? | Places you can't read what you just wrote (Data distribution) |
Configurations satisfying all three are usually expensive. So inspection's purpose isn't satisfying all — it's being able to say what you prioritized and what you gave up.
How to structure a written answer
For "design a system for this requirement" in prose:
- Turn requirements into numbers — users, rps, read/write ratio, data volume. State assumptions as assumptions (Capacity estimation)
- Separate functional and non-functional — what it does (functional) vs. how fast, reliably, correctly (non-functional). Design is separated by the latter
- Place a simple setup first — one-machine or minimum. Show what's missing — reasons for added parts follow
- Add parts to what's missing — for each, state "for what" in one line
- Cite one discarded alternative and why — this is the center of the explanation. Without comparison, the choice reason isn't shown
- State what you gave up — "listing reflection is a few seconds delayed", "large-follower accounts get special handling". Being able to state trade-offs proves understanding
Step 5 differentiates most. Lining up configurations you can memorize; explaining a discarded option requires understanding both.
If time or word count is tight, prioritize 1, 3, 5. Numbers, deltas from minimum, and discarded options. These three suffice as a design explanation.
Common misconceptions
"Drawing the architecture diagram is the design." — The diagram is a conclusion expression. Design is in the process of comparing candidates and choosing one. Showing just the diagram hides the alternatives.
"More complex is better rated." — Unneeded parts lower availability (Availability and redundancy's series product) and increase operational burden. Excessive setup for the numbers reads as lack of understanding.
"Trade-offs are admitting weakness." — A declaration of priority. "Prefer availability over consistency; view counts may return old values" isn't a defect admission — it's the explanation of choice against requirements.
"Drawing everything is kinder." — It becomes unreadable. Draw only relevant elements; state what you left out.
Exercises
Q1. Review this diagram: "User → LB → 3 app servers → 1 database. Apps hold sessions in memory". What do you flag?
Answer and explanation
Three points.
| Flag | Basis |
|---|---|
| Apps hold session in memory | With the default round-robin, each request hits a different machine. Login breaks (Scaling directions) |
| Database is a SPOF | Only one box. Falls → 3 apps or not, service stops (Availability and redundancy) |
| LB is also a SPOF | One in the diagram. Whether redundant isn't readable |
Also flag information not readable from the diagram. Whether "3 apps" are in the same building or split zones changes availability calculation. Whether one box = one machine or a group needs a diagram legend.
Q2. Design a notification listing. Distribute on write or gather on read? What information is needed to decide?
Answer and explanation
Three pieces of information:
- Read/write ratio — notification generation count vs. list-open count. If open count is high, distribute on write wins
- Distribution recipients' count distribution — how many people does one event notify? All-users notifications make distribute-on-write huge writes
- Delay tolerance — gather-on-read runs on each open, so display can slow
Notifications "know recipients up front", so they fit distribute-on-write well. But if there's "notify everyone" functionality, handle it separately.
Not deciding on one, but splitting by condition is the practical answer.
Q3. A design memo only says "prioritized availability". What do you add?
Answer and explanation
Three additions:
- A number — what availability level as the target. How many minutes per year of downtime is tolerable. Without a target, "prioritized" is unquantifiable
- What was given up — what was sacrificed for availability. Cost, write speed, or consistency. Only one side of the trade-off written is the problem
- Discarded options — other configurations considered and why rejected
Especially item 2 matters. Prioritizing something sacrifices something else. "Prioritized availability, accepting tens of milliseconds of extra write latency" reads as a choice.
Summary
- Decide what boxes and lines represent first. Stateful things get different shape; async lines get separated
- Boxes have reasons. Unstateable ones are removal candidates; unserved requirements mean not enough
- What to leave out is the harder judgment. Set criteria and state what you left out
- Multiple configurations for the same requirement. Read/write ratio and distribution decide
- Inspect on availability, scalability, consistency. Not satisfy all — be able to state priority and what was given up
- Prose answers: numbers → minimum setup → what's missing → discarded options → given up in order
- Design explanation is trade-off explanation. Listing configurations isn't explanation
- Syntax reference — mermaid syntax
- Practical AWS Guide — Example architecture diagram with network boundaries as nested
subgraph - Capacity estimation — Numbers that decide part necessity in the diagram
What to read next
- Joins and aggregation — On to Part 12. From setup talk to the queries flowing inside
- Retries and idempotency — How to handle failure in distributed setups
- Indexes — Before distributing, verify what one machine can do