<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>System Design Notes</title><description>Curated notes on distributed systems, architecture patterns, and system design fundamentals.</description><link>https://system-design-notes.vercel.app/</link><language>en-us</language><item><title>Monolith vs Microservices</title><link>https://system-design-notes.vercel.app/notes/architecture-patterns-monolith-vs-microservices/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/architecture-patterns-monolith-vs-microservices/</guid><description>A practical comparison of monolithic and microservice architectures — coupling, deployment, scaling, team context — and how to choose between them.</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;monolith&lt;/strong&gt; is a single application (running on one or many machines) where all computation lives in one deployable unit. A &lt;strong&gt;microservice&lt;/strong&gt; architecture divides the whole workload into &lt;strong&gt;business units&lt;/strong&gt;, each becoming an independent service with its own data and deployment.&lt;/p&gt;
&lt;p&gt;Neither is &quot;better&quot; — they optimize for different failure modes of software teams.&lt;/p&gt;
&lt;h2&gt;Side-by-Side&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Monolith&lt;/th&gt;
&lt;th&gt;Microservices&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Moving parts&lt;/td&gt;
&lt;td&gt;Few&lt;/td&gt;
&lt;td&gt;Many&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fit&lt;/td&gt;
&lt;td&gt;Small teams, early stage&lt;/td&gt;
&lt;td&gt;Larger orgs, independent domains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplication (tests, tooling)&lt;/td&gt;
&lt;td&gt;Less&lt;/td&gt;
&lt;td&gt;More per service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;Faster — in-process procedure calls&lt;/td&gt;
&lt;td&gt;Network calls (RPC) per interaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Complex — everything touches everything&lt;/td&gt;
&lt;td&gt;Independent per service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blast radius&lt;/td&gt;
&lt;td&gt;One bug can break everything&lt;/td&gt;
&lt;td&gt;Isolated to one service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coupling&lt;/td&gt;
&lt;td&gt;Tight&lt;/td&gt;
&lt;td&gt;Loose (by contract)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Scale the whole thing&lt;/td&gt;
&lt;td&gt;Scale exactly what&apos;s hot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context needed to work&lt;/td&gt;
&lt;td&gt;Whole codebase&lt;/td&gt;
&lt;td&gt;One service at a time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Design effort&lt;/td&gt;
&lt;td&gt;Lower upfront&lt;/td&gt;
&lt;td&gt;Tougher — boundaries must be right&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Monolith&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt; fewer moving parts; great for small teams; less duplicated test/tooling infrastructure; fast because everything runs in the same box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt; working on anything requires more context; deployments are complex and risky; too much responsibility concentrated in one server — one bad change can break everything; tight coupling makes evolution slow.&lt;/p&gt;
&lt;h2&gt;Microservices&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt; easy to scale individual services; less context needed per developer; parallel development across teams; resources can be allocated where load actually is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt; tougher to design — service boundaries, data ownership, inter-service contracts, and distributed-systems failure modes all become your problem.&lt;/p&gt;
&lt;h2&gt;How to Choose&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Start monolith until domain boundaries are proven. Splitting later is far easier than un-splitting wrong boundaries.&lt;/li&gt;
&lt;li&gt;Interview framing: most interview questions assume high scale, so microservices are the expected answer — but say &lt;em&gt;why&lt;/em&gt; (independent scaling, team autonomy) and acknowledge the operational price.&lt;/li&gt;
&lt;li&gt;The migration path is usually: monolith → extract the hot/critical path as a service → repeat. See &lt;a href=&quot;/notes/architecture-patterns-microservices&quot;&gt;Microservices Architecture Patterns&lt;/a&gt; for decomposition strategies once you do split.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Part of the &lt;strong&gt;Architecture Patterns&lt;/strong&gt; series.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>architecture-patterns</category><category>monolith</category><category>microservices</category><category>architecture</category><category>decoupling</category></item><item><title>Database Sharding</title><link>https://system-design-notes.vercel.app/notes/databases-sharding/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/databases-sharding/</guid><description>Horizontal partitioning of a database into independent shards — shard key selection, routing, replication with failover, and the operational costs.</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Sharding&lt;/strong&gt; is horizontal partitioning of a database into smaller, independent pieces (&lt;strong&gt;shards&lt;/strong&gt;), each holding a disjoint subset of rows and running on its own hardware. You shard when a single database genuinely cannot handle the write volume or dataset size — not before.&lt;/p&gt;
&lt;h2&gt;The Shard Key&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;shard key&lt;/strong&gt; decides how data is split and where each row lives. Choosing it is the most consequential decision in sharding:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A good key spreads load evenly.&lt;/li&gt;
&lt;li&gt;A bad key creates &lt;strong&gt;hot shards&lt;/strong&gt; — a few shards take most of the traffic while others idle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example: sharding by &lt;code&gt;user_id&lt;/code&gt; spreads users evenly; sharding by &lt;code&gt;created_at&lt;/code&gt; puts all current traffic on one shard (everyone writes to &quot;today&quot;).&lt;/p&gt;
&lt;h2&gt;Common Strategies&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;Trade-off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Range-based&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shard 1: IDs 1–1M, Shard 2: 1M–2M…&lt;/td&gt;
&lt;td&gt;Simple range queries; uneven load risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hash-based&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hash(key) % n&lt;/code&gt; picks the shard&lt;/td&gt;
&lt;td&gt;Even distribution; range scans hit all shards&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For dynamic shard counts, consistent hashing avoids mass reassignment when shards are added (see &lt;a href=&quot;/notes/system-design-fundamentals-load-balancing-and-consistent-hashing&quot;&gt;Consistent Hashing&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;Routing&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;routing layer&lt;/strong&gt; — application logic or a proxy — maps each request to the correct shard. This layer must know the partitioning scheme and adds one more component that can fail or become a bottleneck if not made highly available.&lt;/p&gt;
&lt;h2&gt;Replication Per Shard&lt;/h2&gt;
&lt;p&gt;Each shard is itself replicated for availability:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One &lt;strong&gt;master&lt;/strong&gt; handles all writes; &lt;strong&gt;slaves&lt;/strong&gt; serve reads.&lt;/li&gt;
&lt;li&gt;If the master fails, a slave is &lt;strong&gt;promoted&lt;/strong&gt; (failover).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives you both scalability (data spread across machines) and availability (no single machine&apos;s loss loses data).&lt;/p&gt;
&lt;h2&gt;Strengths&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Improves scalability (write capacity grows with shards) and availability (blast radius shrinks per shard).&lt;/li&gt;
&lt;li&gt;Shards can be split further only when they grow too large.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Costs &amp;amp; Complexity&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cross-shard queries are expensive&lt;/strong&gt; — joins and transactions spanning shards need application-level coordination.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rebalancing is difficult&lt;/strong&gt; — moving data between shards while serving traffic requires careful tooling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System complexity&lt;/strong&gt; — routing, monitoring per shard, and debugging all get harder. Only pay this price when you truly need it.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Part of the &lt;strong&gt;Databases&lt;/strong&gt; series.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>databases</category><category>sharding</category><category>partitioning</category><category>scalability</category><category>replication</category></item><item><title>Event-Driven Architecture</title><link>https://system-design-notes.vercel.app/notes/messaging-event-driven-architecture/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/messaging-event-driven-architecture/</guid><description>Communicating through immutable, persisted events — replayability, idempotency, event sourcing, CQRS, and the trade-offs of eventual consistency.</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Event-driven systems communicate through &lt;strong&gt;events&lt;/strong&gt; — records of state changes (&quot;order placed&quot;, &quot;payment failed&quot;) rather than direct commands.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flow:&lt;/strong&gt; Producer → Broker (queue/log) → Consumer(s)&lt;/p&gt;
&lt;p&gt;Two properties make events powerful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Immutable&lt;/strong&gt; — never updated, only appended.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persisted&lt;/strong&gt; — stored durably, enabling replay and recovery.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Replayability&lt;/h2&gt;
&lt;p&gt;Because the event log is a complete history, system state can be &lt;strong&gt;rebuilt by reprocessing events from the beginning&lt;/strong&gt;. This enables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Self-healing&lt;/strong&gt; — after a bug corrupts state, fix the handler and replay the log.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Debugging and auditing&lt;/strong&gt; — every change has a traceable cause.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;New consumers&lt;/strong&gt; — subscribe and replay history to build a current view.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Brokers like Kafka and RabbitMQ provide the persistence, retry, and delivery machinery.&lt;/p&gt;
&lt;h2&gt;Reliability Requirements&lt;/h2&gt;
&lt;h3&gt;Idempotency&lt;/h3&gt;
&lt;p&gt;At-least-once delivery means the same event can arrive multiple times. Handlers must be &lt;strong&gt;idempotent&lt;/strong&gt; — processing an event twice must be safe. Common technique: track processed event IDs and dedupe.&lt;/p&gt;
&lt;h3&gt;Retries + ACKs&lt;/h3&gt;
&lt;p&gt;Consumers acknowledge messages only after successful processing. Unacked messages are redelivered — this is what makes delivery reliable, and also why idempotency is non-negotiable.&lt;/p&gt;
&lt;h2&gt;Event Sourcing &amp;amp; CQRS&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Event Sourcing&lt;/strong&gt;: store all changes &lt;em&gt;as events&lt;/em&gt;, not final state. The database &lt;em&gt;is&lt;/em&gt; the event log; current state is derived.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CQRS&lt;/strong&gt; (Command Query Responsibility Segregation): separate the write model (commands producing events) from the read model (projections built from those events). Each side scales and evolves independently.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Strengths&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Loose coupling&lt;/strong&gt; — services evolve independently; add new consumers without touching producers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt; — asynchronous processing absorbs spikes naturally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Availability &amp;amp; fault tolerance&lt;/strong&gt; — consumers can be down while events accumulate safely in the broker.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Weaknesses&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No strict ordering guarantee by default&lt;/strong&gt; — depends on partitioning/design.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eventual consistency&lt;/strong&gt; — reads may lag writes; wrong fit for strong transactional requirements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distributed debugging&lt;/strong&gt; — tracing one request across many async consumers is hard without correlation IDs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Careful handling required&lt;/strong&gt; for retries, duplicates, ordering, and failure paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Real-World Uses&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Payments, notifications, analytics pipelines&lt;/li&gt;
&lt;li&gt;Git (the commit history is an event log)&lt;/li&gt;
&lt;li&gt;Gaming (rebuilding game state from event history)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not ideal when you need strong transactional consistency across a write — use a relational design there instead.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Part of the &lt;strong&gt;Messaging&lt;/strong&gt; series.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>messaging</category><category>eda</category><category>events</category><category>event-sourcing</category><category>cqrs</category><category>kafka</category></item><item><title>Message Queues: Async Processing at Scale</title><link>https://system-design-notes.vercel.app/notes/messaging-message-queues/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/messaging-message-queues/</guid><description>How message queues decouple producers from consumers, survive worker crashes with retries and reassignment, and form the basis of pub-sub and event-driven systems.</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With multiple servers processing jobs in parallel, a server can &lt;strong&gt;crash mid-job&lt;/strong&gt;. The jobs it was holding are now unfinished — they must not be lost, and they must be reassigned.&lt;/p&gt;
&lt;h2&gt;The Reassignment Pattern&lt;/h2&gt;
&lt;p&gt;A monitor/notifier watches server health. On crash detection:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetch the crashed server&apos;s &lt;strong&gt;unfinished jobs&lt;/strong&gt; from shared storage.&lt;/li&gt;
&lt;li&gt;Redistribute them to alive servers.&lt;/li&gt;
&lt;li&gt;Distribution uses &lt;strong&gt;consistent hashing via the load balancer&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because consistent hashing maps the same job ID to the same server, jobs already running on healthy servers &lt;strong&gt;don&apos;t get duplicated&lt;/strong&gt; — only the dead server&apos;s slice moves.&lt;/p&gt;
&lt;p&gt;This pattern — durable job list, health checks, retry, redistribution — is essentially how a &lt;strong&gt;message queue&lt;/strong&gt; works.&lt;/p&gt;
&lt;h2&gt;What a Message Queue Gives You&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Producers&lt;/strong&gt; enqueue work; &lt;strong&gt;consumers&lt;/strong&gt; process it. Neither knows about the other directly.&lt;/li&gt;
&lt;li&gt;Messages are &lt;strong&gt;persisted&lt;/strong&gt; until acknowledged, so crashes don&apos;t lose work.&lt;/li&gt;
&lt;li&gt;Consumers scale independently of producers (add workers at peak, drain backlog off-hours).&lt;/li&gt;
&lt;li&gt;Failed messages can be retried or routed to a dead-letter queue.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Pub-Sub Model&lt;/h2&gt;
&lt;p&gt;Pub-sub generalizes the queue from &lt;em&gt;one consumer per message&lt;/em&gt; to &lt;em&gt;many&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Publishers&lt;/strong&gt; emit events (&quot;order placed&quot;).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;broker&lt;/strong&gt; (Kafka, RabbitMQ) routes them to topic subscribers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subscribers&lt;/strong&gt; consume independently — notifications, analytics, fraud checks all react to the same event without touching each other.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Key properties:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Services never call each other directly&lt;/td&gt;
&lt;td&gt;Loose coupling; add consumers without touching producers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asynchronous delivery&lt;/td&gt;
&lt;td&gt;Producers aren&apos;t blocked by slow consumers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Broker owns persistence/retries&lt;/td&gt;
&lt;td&gt;Reliable delivery even when consumers are down&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;The Trade-offs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Slower than direct calls&lt;/strong&gt; — an extra network hop and serialization layer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eventual consistency&lt;/strong&gt; — consumers lag producers; wrong fit when you need strong transactional guarantees.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operational complexity&lt;/strong&gt; — brokers need setup, monitoring, capacity planning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failure handling is your job&lt;/strong&gt; — retries, duplicates, and ordering must be designed for (see &lt;a href=&quot;/notes/messaging-event-driven-architecture&quot;&gt;Event-Driven Architecture&lt;/a&gt; for idempotency).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;When to Reach For It&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Work that doesn&apos;t need an immediate response (emails, thumbnails, analytics).&lt;/li&gt;
&lt;li&gt;Spike absorption (enqueue now, process later).&lt;/li&gt;
&lt;li&gt;Fan-out to multiple independent consumers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avoid it for request/response paths that need a synchronous answer.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Part of the &lt;strong&gt;Messaging&lt;/strong&gt; series.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>messaging</category><category>message-queue</category><category>async</category><category>reliability</category><category>pub-sub</category></item><item><title>CDNs: Content Delivery Networks</title><link>https://system-design-notes.vercel.app/notes/system-design-fundamentals-cdn/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/system-design-fundamentals-cdn/</guid><description>How globally distributed edge servers cut latency for static content, the cache hit/miss flow, and invalidation strategies.</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;CDN&lt;/strong&gt; is a network of servers distributed across the globe that serves content from the location nearest to each user. Physics is the win: shorter distance means lower latency, and no single origin carries the world&apos;s traffic.&lt;/p&gt;
&lt;h2&gt;What Belongs on a CDN&lt;/h2&gt;
&lt;p&gt;Primarily &lt;strong&gt;static content&lt;/strong&gt;: images, CSS, JS bundles, videos, fonts — anything identical for every user. Dynamic/personalized responses need more care (and often shouldn&apos;t be cached at all).&lt;/p&gt;
&lt;h2&gt;Hit vs Miss Flow&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cache hit&lt;/strong&gt; — an edge server near the user already holds the asset → served immediately.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache miss&lt;/strong&gt; — edge fetches from the &lt;strong&gt;origin&lt;/strong&gt;, stores a copy, serves it. Subsequent nearby users hit the now-warm cache.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is caching with geographic distribution bolted on — all the usual concepts apply: TTLs, hit ratio as the key metric, and stale-data trade-offs.&lt;/p&gt;
&lt;h2&gt;Benefits&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lower latency&lt;/td&gt;
&lt;td&gt;Content travels meters, not continents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Origin offloading&lt;/td&gt;
&lt;td&gt;Most requests never reach your servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability &amp;amp; availability&lt;/td&gt;
&lt;td&gt;Traffic spikes and even origin outages are absorbed by edges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost-effectiveness&lt;/td&gt;
&lt;td&gt;Edge bandwidth is cheaper than scaling origin capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Invalidation&lt;/h2&gt;
&lt;p&gt;Cached content must sometimes change &lt;em&gt;before&lt;/em&gt; its TTL expires (deploy day). CDNs support &lt;strong&gt;cache invalidation strategies&lt;/strong&gt; — purging specific URLs or tags at the edges — plus pluggable fetch/caching behaviors to control what gets cached and revalidated.&lt;/p&gt;
&lt;h2&gt;Who Runs Them&lt;/h2&gt;
&lt;p&gt;CloudFront, Akamai, Cloudflare, Fastly — the pattern is commodity; you rarely build your own.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Part of the &lt;strong&gt;System Design Fundamentals&lt;/strong&gt; series.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>system-design-fundamentals</category><category>cdn</category><category>caching</category><category>latency</category><category>static-content</category></item><item><title>HLD vs LLD</title><link>https://system-design-notes.vercel.app/notes/system-design-fundamentals-hld-vs-lld/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/system-design-fundamentals-hld-vs-lld/</guid><description>The difference between high-level design (system architecture, modules, interactions) and low-level design (classes, APIs, database schemas, implementation logic).</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;System design interviews and real projects both operate at two altitudes.&lt;/p&gt;
&lt;h2&gt;HLD — High-Level Design&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;big-picture architecture&lt;/strong&gt; of a system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Main modules and their responsibilities&lt;/li&gt;
&lt;li&gt;Overall request/data flow&lt;/li&gt;
&lt;li&gt;How components interact (sync vs async, which protocols)&lt;/li&gt;
&lt;li&gt;Technology choices at the boundaries (DB type, queue, cache, CDN)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deliverable: an architecture diagram you can defend — boxes, arrows, and the reasoning for each. Questions like &quot;design Twitter&quot; or &quot;design a URL shortener&quot; are HLD exercises. The scaling toolkit lives here: load balancers, sharding, caching, queues (see the other fundamentals notes).&lt;/p&gt;
&lt;h2&gt;LLD — Low-Level Design&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;detailed design of each module&lt;/strong&gt; from the HLD:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Classes, interfaces, and their relationships&lt;/li&gt;
&lt;li&gt;Function signatures and API contracts&lt;/li&gt;
&lt;li&gt;Database tables/schemas and indexes&lt;/li&gt;
&lt;li&gt;Implementation logic and edge cases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Deliverable: code-shaped artifacts — class diagrams, schemas, API specs. Questions like &quot;design a parking lot&quot; or &quot;design a BookMyShow seat-booking flow&quot; are LLD exercises; they test object-oriented design and data modeling rather than scale.&lt;/p&gt;
&lt;h2&gt;How They Relate&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;HLD&lt;/th&gt;
&lt;th&gt;LLD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Question answered&lt;/td&gt;
&lt;td&gt;&lt;em&gt;What&lt;/em&gt; are the pieces and how do they talk?&lt;/td&gt;
&lt;td&gt;&lt;em&gt;How&lt;/em&gt; is each piece built inside?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audience&lt;/td&gt;
&lt;td&gt;Architects, senior engineers, interviewers&lt;/td&gt;
&lt;td&gt;Implementing engineers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;Architecture diagram + component choices&lt;/td&gt;
&lt;td&gt;Class/API/schema design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure mode if skipped&lt;/td&gt;
&lt;td&gt;Building detailed the wrong thing&lt;/td&gt;
&lt;td&gt;Vague hand-waving that collapses in code review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Rule of thumb: HLD first to make the big bets cheap to change, LLD second to make each bet buildable. A strong answer names the HLD components, then drills into LLD for the one or two most interesting components rather than shallowly covering everything.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Part of the &lt;strong&gt;System Design Fundamentals&lt;/strong&gt; series.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>system-design-fundamentals</category><category>hld</category><category>lld</category><category>interviews</category><category>design</category></item><item><title>Load Balancing &amp; Consistent Hashing</title><link>https://system-design-notes.vercel.app/notes/system-design-fundamentals-load-balancing-and-consistent-hashing/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/system-design-fundamentals-load-balancing-and-consistent-hashing/</guid><description>How load balancers distribute traffic across servers, why naive hashing breaks on scale events, and how consistent hashing with virtual nodes solves it.</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;load balancer&lt;/strong&gt; sits between clients and your server pool, distributing requests so no single machine is overwhelmed. It is mandatory equipment the moment you horizontally scale — multiple servers are useless if all traffic still lands on one of them.&lt;/p&gt;
&lt;h2&gt;The Naive Approach: Simple Hashing&lt;/h2&gt;
&lt;p&gt;The simplest distribution scheme is hashing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server = hash(request_id) % n_servers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same request ID always maps to the same server. Cheap, deterministic, and gives you cache/session locality for free.&lt;/p&gt;
&lt;h3&gt;The Drawback&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;n_servers&lt;/code&gt; changes whenever you add or remove a server — which is exactly what horizontal scaling is for. When &lt;code&gt;n = 10&lt;/code&gt; becomes &lt;code&gt;n = 11&lt;/code&gt;, almost every key remaps to a different server:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sessions stored in-memory are lost.&lt;/li&gt;
&lt;li&gt;Caches go cold (most requests now miss).&lt;/li&gt;
&lt;li&gt;Upstream connections churn.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At scale this disruption is unacceptable.&lt;/p&gt;
&lt;h2&gt;Consistent Hashing&lt;/h2&gt;
&lt;p&gt;Consistent hashing uses a &lt;strong&gt;fixed hash space&lt;/strong&gt; (conceptually a ring, e.g. &lt;code&gt;hash(key) % M&lt;/code&gt; where M is a huge constant like 2³²):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Servers&lt;/strong&gt; are hashed onto the ring at specific positions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keys&lt;/strong&gt; are hashed onto the same ring.&lt;/li&gt;
&lt;li&gt;A key belongs to the &lt;strong&gt;next clockwise server&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because the hash space never changes, adding or removing a server only reassigns the keys sitting between the old and new position — roughly &lt;code&gt;1/n&lt;/code&gt; of keys move instead of nearly all of them.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Naive hashing&lt;/th&gt;
&lt;th&gt;Consistent hashing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Keys moved when scaling&lt;/td&gt;
&lt;td&gt;~all keys&lt;/td&gt;
&lt;td&gt;~&lt;code&gt;1/n&lt;/code&gt; of keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache/session locality&lt;/td&gt;
&lt;td&gt;Broken on every change&lt;/td&gt;
&lt;td&gt;Mostly preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;Trivial&lt;/td&gt;
&lt;td&gt;Ring + node lookup logic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Virtual Nodes&lt;/h2&gt;
&lt;p&gt;Physical servers don&apos;t sit evenly on the ring by chance — some get huge ranges, others tiny ones (&lt;strong&gt;hot spots&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Virtual nodes&lt;/strong&gt; fix this: each physical server is placed on the ring many times (e.g. 100–200 replicas under different hash offsets). With enough virtual nodes, ranges even out statistically, and a failing server sheds its small slices to many neighbors instead of dumping one giant range onto one unlucky peer.&lt;/p&gt;
&lt;h2&gt;Where You&apos;ll See It&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Distributed caches and stores: &lt;strong&gt;Redis Cluster, Cassandra, DynamoDB&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Load balancers with sticky routing&lt;/li&gt;
&lt;li&gt;Database sharding layers (see &lt;a href=&quot;/notes/databases-sharding&quot;&gt;Sharding&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Interview Framing&lt;/h2&gt;
&lt;p&gt;When asked &quot;how would you route jobs to workers?&quot;, walk through the progression: round-robin → hashing for stickiness → why &lt;code&gt;% n&lt;/code&gt; breaks → hash ring → virtual nodes for balance. The story of &lt;em&gt;why each step exists&lt;/em&gt; matters more than naming them.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Part of the &lt;strong&gt;System Design Fundamentals&lt;/strong&gt; series.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>system-design-fundamentals</category><category>load-balancing</category><category>consistent-hashing</category><category>scalability</category></item><item><title>Single Points of Failure &amp; Resilience</title><link>https://system-design-notes.vercel.app/notes/system-design-fundamentals-resilience-and-spof/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/system-design-fundamentals-resilience-and-spof/</guid><description>Identifying SPOFs in centralized components and the standard toolkit for removing them: redundancy, replication, failover, partitioning, and backups.</description><pubDate>Sun, 23 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A &lt;strong&gt;Single Point of Failure (SPOF)&lt;/strong&gt; is any component whose failure takes down the entire system. If one box dying means your service dies, that box is an SPOF.&lt;/p&gt;
&lt;h2&gt;Where SPOFs Hide&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Centralized databases&lt;/strong&gt; — one DB instance serves everything.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coordinators and proxies&lt;/strong&gt; — components handling routing, service discovery, or job assignment are especially prone: everything flows &lt;em&gt;through&lt;/em&gt; them by design.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single load balancer&lt;/strong&gt; — ironic but common; the component meant to add resilience becomes the choke point.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Removal Toolkit&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redundancy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Run multiple instances of every service; health-check them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Replication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Duplicate data across independent machines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failover&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automatically switch to a standby on failure (e.g. promote a DB slave to master)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Horizontal scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;More nodes = no single node is load-bearing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Partitioning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Spread load/data across systems so failures are isolated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Backups&lt;/h2&gt;
&lt;p&gt;Backups don&apos;t prevent failure — they bound its damage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep regular backups of all stateful stores.&lt;/li&gt;
&lt;li&gt;A backup turns &quot;total data loss&quot; into &quot;restore from last night&quot;.&lt;/li&gt;
&lt;li&gt;Pair with redundancy so recovery is fast, not just possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The Dependency Graph&lt;/h2&gt;
&lt;p&gt;Map what depends on what. With enough redundancy the dependency graph becomes flexible — traffic reroutes around failed nodes instead of through them. Audit it regularly: new features quietly introduce new SPOFs.&lt;/p&gt;
&lt;h2&gt;Limits: CAP&lt;/h2&gt;
&lt;p&gt;You can&apos;t remove every failure mode:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CAP theorem&lt;/strong&gt; constrains what&apos;s achievable during partitions — you trade consistency against availability.&lt;/li&gt;
&lt;li&gt;Strong consistency often requires coordination (quorums, leader election), which itself can reduce availability.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Resilience engineering is about choosing &lt;em&gt;which&lt;/em&gt; failures you can tolerate, then making those failures boring.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Part of the &lt;strong&gt;System Design Fundamentals&lt;/strong&gt; series.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>system-design-fundamentals</category><category>spof</category><category>resilience</category><category>availability</category><category>failover</category><category>backups</category></item><item><title>Microservices Architecture Patterns</title><link>https://system-design-notes.vercel.app/notes/architecture-patterns-microservices/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/architecture-patterns-microservices/</guid><description>Essential patterns for designing, deploying, and operating microservices: decomposition, communication, data management, and operational patterns.</description><pubDate>Mon, 05 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Microservices architecture structures an application as a collection of loosely coupled, independently deployable services. This guide covers the essential patterns.&lt;/p&gt;
&lt;h2&gt;Decomposition Patterns&lt;/h2&gt;
&lt;h3&gt;1. Decompose by Business Capability&lt;/h3&gt;
&lt;p&gt;Organize around business functions, not technical layers.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❌ Technical layers:
  ├─ UI Service
  ├─ Business Logic Service
  └─ Data Access Service

✅ Business capabilities:
  ├─ Order Management
  ├─ Customer Management
  ├─ Inventory Management
  ├─ Payment Processing
  └─ Shipping &amp;amp; Fulfillment
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Decompose by Subdomain (DDD)&lt;/h3&gt;
&lt;p&gt;Use Domain-Driven Design to identify bounded contexts.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Each bounded context = potential microservice
public class OrderContext {      // Order Management
    // Entities: Order, OrderItem, Shipment
    // Aggregates: Order (root)
}

public class CustomerContext {   // Customer Management
    // Entities: Customer, Address, PaymentMethod
    // Aggregates: Customer (root)
}

public class InventoryContext {  // Inventory Management
    // Entities: Product, Warehouse, StockLevel
    // Aggregates: Product (root)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Strangler Fig Pattern&lt;/h3&gt;
&lt;p&gt;Incrementally migrate from monolith.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Phase 1:                    Phase 2:                    Phase 3:
┌─────────────┐             ┌─────────────┐             ┌─────────────┐
│  Monolith   │             │  Monolith   │             │  Monolith   │
│             │             │  ┌───────┐  │             │  ┌───────┐  │
│  [Orders]   │──────────▶  │  [Orders]   │──────────▶  │  [Orders]   │
│  [Customer] │  Extract    │  [Customer] │  Extract    │  [Customer] │
│  [Inventory]│             │  [Inventory]│             │  [Inventory]│
└─────────────┘             │  ┌───────┐  │             │  ┌───────┐  │
                            │  │Order Svc│  │             │  │Order Svc│  │
                            │  └───────┘  │             │  └───────┘  │
                            └─────────────┘             └─────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Communication Patterns&lt;/h2&gt;
&lt;h3&gt;1. Synchronous (Request-Response)&lt;/h3&gt;
&lt;h4&gt;REST/HTTP&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /api/v1/orders/{orderId}
Authorization: Bearer &amp;lt;token&amp;gt;
Accept: application/json

Response: 200 OK
{
  &quot;orderId&quot;: &quot;ord_123&quot;,
  &quot;customerId&quot;: &quot;cust_456&quot;,
  &quot;status&quot;: &quot;SHIPPED&quot;,
  &quot;items&quot;: [...]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;gRPC (Recommended for internal)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-protobuf&quot;&gt;// order.proto
service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc CreateOrder(CreateOrderRequest) returns (Order);
  rpc StreamOrders(StreamOrdersRequest) returns (stream Order);
}

message Order {
  string order_id = 1;
  string customer_id = 2;
  OrderStatus status = 3;
  repeated OrderItem items = 4;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Asynchronous (Event-Driven)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Event publication
public class OrderService {
    private final EventPublisher publisher;
    
    public Order createOrder(CreateOrderCommand cmd) {
        Order order = Order.create(cmd);
        orderRepository.save(order);
        
        // Publish domain event
        publisher.publish(new OrderCreatedEvent(
            order.getId(),
            order.getCustomerId(),
            order.getItems(),
            order.getTotal()
        ));
        
        return order;
    }
}

// Event consumption (in Payment Service)
@EventListener
public class PaymentEventHandler {
    private final PaymentService paymentService;
    
    @EventHandler
    public void handle(OrderCreatedEvent event) {
        paymentService.processPayment(
            event.getOrderId(),
            event.getCustomerId(),
            event.getTotal()
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. API Gateway Pattern&lt;/h3&gt;
&lt;p&gt;Single entry point for all clients.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Kong API Gateway config
services:
  - name: order-service
    url: http://order-service:8080
    routes:
      - name: orders
        paths: [&quot;/api/orders&quot;]
        methods: [&quot;GET&quot;, &quot;POST&quot;]
    plugins:
      - name: rate-limiting
        config:
          minute: 1000
          policy: redis
      - name: jwt
      - name: correlation-id

  - name: customer-service
    url: http://customer-service:8080
    routes:
      - name: customers
        paths: [&quot;/api/customers&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Backend for Frontend (BFF)&lt;/h3&gt;
&lt;p&gt;Tailored APIs per client type.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;                    ┌─────────────┐
                    │   Web BFF   │──▶ Web App
                    └──────┬──────┘
                           │
              ┌────────────┼────────────┐
              ▼            ▼            ▼
        ┌─────────┐  ┌─────────┐  ┌─────────┐
        │ Orders  │  │Customer │  │Inventory│
        │ Service │  │ Service │  │ Service │
        └─────────┘  └─────────┘  └─────────┘
              │            │            │
              └────────────┼────────────┘
                           ▼
                    ┌─────────────┐
                    │  Mobile BFF │──▶ Mobile App
                    └─────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Data Management Patterns&lt;/h2&gt;
&lt;h3&gt;1. Database per Service&lt;/h3&gt;
&lt;p&gt;Each service owns its data.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Order     │     │  Customer   │     │ Inventory   │
│  Service    │     │  Service    │     │  Service    │
│  ┌───────┐  │     │  ┌───────┐  │     │  ┌───────┐  │
│  │OrderDB│  │     │  │CustDB │  │     │  │InvDB  │  │
│  └───────┘  │     │  └───────┘  │     │  └───────┘  │
└─────────────┘     └─────────────┘     └─────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Saga Pattern (Distributed Transactions)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Choreography-based saga
public class OrderSaga {
    private final EventPublisher publisher;
    
    public void createOrder(CreateOrderCommand cmd) {
        // Step 1: Create pending order
        Order order = Order.createPending(cmd);
        orderRepository.save(order);
        
        // Step 2: Reserve inventory (async)
        publisher.publish(new ReserveInventoryCommand(
            order.getId(), order.getItems()
        ));
    }
    
    @EventHandler
    public void handle(InventoryReservedEvent event) {
        Order order = orderRepository.find(event.getOrderId());
        order.confirmInventoryReserved();
        orderRepository.save(order);
        
        // Step 3: Process payment
        publisher.publish(new ProcessPaymentCommand(
            order.getId(), order.getCustomerId(), order.getTotal()
        ));
    }
    
    @EventHandler
    public void handle(PaymentProcessedEvent event) {
        Order order = orderRepository.find(event.getOrderId());
        order.confirmPayment();
        orderRepository.save(order);
        
        // Step 4: Create shipment
        publisher.publish(new CreateShipmentCommand(order));
    }
    
    // Compensating transactions
    @EventHandler
    public void handle(InventoryReservationFailedEvent event) {
        Order order = orderRepository.find(event.getOrderId());
        order.cancel(&quot;Inventory unavailable&quot;);
        orderRepository.save(order);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Event Sourcing&lt;/h3&gt;
&lt;p&gt;Store state changes as events.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Event-sourced aggregate
public class Order {
    private String orderId;
    private OrderStatus status;
    private List&amp;lt;OrderItem&amp;gt; items;
    private List&amp;lt;DomainEvent&amp;gt; uncommittedEvents = new ArrayList&amp;lt;&amp;gt;();
    
    public static Order create(CreateOrderCommand cmd) {
        Order order = new Order();
        order.apply(new OrderCreatedEvent(
            cmd.getOrderId(), cmd.getCustomerId(), cmd.getItems()
        ));
        return order;
    }
    
    public void addItem(AddItemCommand cmd) {
        if (status != OrderStatus.PENDING) {
            throw new IllegalStateException(&quot;Cannot modify confirmed order&quot;);
        }
        apply(new ItemAddedEvent(orderId, cmd.getItem()));
    }
    
    private void apply(DomainEvent event) {
        when(event);
        uncommittedEvents.add(event);
    }
    
    private void when(OrderCreatedEvent e) {
        this.orderId = e.getOrderId();
        this.status = OrderStatus.PENDING;
        this.items = new ArrayList&amp;lt;&amp;gt;();
    }
    
    private void when(ItemAddedEvent e) {
        this.items.add(e.getItem());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. CQRS (Command Query Responsibility Segregation)&lt;/h3&gt;
&lt;p&gt;Separate read and write models.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Write model (commands)
public class OrderCommandHandler {
    private final EventStore eventStore;
    
    @CommandHandler
    public void handle(CreateOrderCommand cmd) {
        Order order = Order.create(cmd);
        eventStore.save(order.getUncommittedEvents());
    }
}

// Read model (queries) - optimized for reading
@Entity
@Table(name = &quot;order_summary&quot;)
public class OrderSummary {
    @Id String orderId;
    String customerName;
    OrderStatus status;
    BigDecimal total;
    LocalDateTime createdAt;
    int itemCount;
}

// Projector updates read model
@EventHandler
public class OrderProjector {
    private final OrderSummaryRepository readRepo;
    
    public void on(OrderCreatedEvent event) {
        readRepo.save(new OrderSummary(
            event.getOrderId(),
            event.getCustomerName(),
            OrderStatus.PENDING,
            event.getTotal(),
            event.getTimestamp(),
            event.getItems().size()
        ));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Operational Patterns&lt;/h2&gt;
&lt;h3&gt;1. Service Discovery&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Consul service registration
service:
  name: order-service
  port: 8080
  tags: [&quot;v1&quot;, &quot;api&quot;]
  check:
    http: http://localhost:8080/health
    interval: 10s
    timeout: 5s
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Configuration Management&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Spring Cloud Config / Consul Config
order-service:
  database:
    url: ${DATABASE_URL}
    pool-size: 20
  inventory:
    service-url: http://inventory-service
    timeout: 5s
    retry-attempts: 3
  circuit-breaker:
    failure-threshold: 50%
    timeout: 30s
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Distributed Tracing&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// OpenTelemetry instrumentation
@SpanAttribute
public Order getOrder(String orderId) {
    return tracer.spanBuilder(&quot;getOrder&quot;)
        .setAttribute(&quot;order.id&quot;, orderId)
        .startScopedSpan(() -&amp;gt; {
            return orderRepository.findById(orderId);
        });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Health Checks&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Component
public class OrderHealthIndicator implements HealthIndicator {
    private final OrderRepository repository;
    private final InventoryClient inventoryClient;
    
    @Override
    public Health health() {
        // Check database
        if (!repository.isHealthy()) {
            return Health.down()
                .withDetail(&quot;database&quot;, &quot;unavailable&quot;)
                .build();
        }
        
        // Check dependencies
        if (!inventoryClient.isHealthy()) {
            return Health.up()
                .withDetail(&quot;inventory&quot;, &quot;degraded&quot;)
                .withDetail(&quot;database&quot;, &quot;healthy&quot;)
                .build();
        }
        
        return Health.up()
            .withDetail(&quot;database&quot;, &quot;healthy&quot;)
            .withDetail(&quot;inventory&quot;, &quot;healthy&quot;)
            .build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Anti-Patterns to Avoid&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Anti-Pattern&lt;/th&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distributed Monolith&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Services must deploy together&lt;/td&gt;
&lt;td&gt;Ensure independent deployability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shared Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tight coupling, schema conflicts&lt;/td&gt;
&lt;td&gt;Database per service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chatty Communication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Latency, cascade failures&lt;/td&gt;
&lt;td&gt;Async events, batch APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Synchronous Chains&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A→B→C→D blocks&lt;/td&gt;
&lt;td&gt;Event-driven, saga&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Duplication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inconsistency&lt;/td&gt;
&lt;td&gt;Single source of truth + events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nanoservices&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Operational overhead&lt;/td&gt;
&lt;td&gt;Right-size services&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Technology Choices&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Options&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Mesh&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Istio, Linkerd, Consul Connect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;API Gateway&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kong, AWS API Gateway, Traefik, Zuul&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Discovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Consul, etcd, Eureka, Kubernetes DNS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Message Broker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kafka, RabbitMQ, NATS, Pulsar&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distributed Tracing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jaeger, Zipkin, Tempo, AWS X-Ray&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metrics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prometheus + Grafana, Datadog, CloudWatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Logging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ELK/EFK, Loki, Splunk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes, Nomad, ECS, Cloud Run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://microservices.io/patterns/&quot;&gt;Microservices Patterns&lt;/a&gt; - Chris Richardson&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://samnewman.io/books/building_microservices/&quot;&gt;Building Microservices&lt;/a&gt; - Sam Newman&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.enterpriseintegrationpatterns.com/&quot;&gt;Enterprise Integration Patterns&lt;/a&gt; - Hohpe &amp;amp; Woolf&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://domainlanguage.com/ddd/&quot;&gt;Domain-Driven Design&lt;/a&gt; - Eric Evans&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pragprog.com/titles/mnee2/release-it-second-edition/&quot;&gt;Release It!&lt;/a&gt; - Michael Nygard&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>architecture-patterns</category><category>microservices</category><category>architecture</category><category>decomposition</category><category>api-gateway</category><category>saga</category><category>event-driven</category></item><item><title>Netflix Architecture: Microservices at Scale</title><link>https://system-design-notes.vercel.app/notes/case-studies-netflix-architecture/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/case-studies-netflix-architecture/</guid><description>How Netflix built a resilient, scalable streaming platform serving 230M+ subscribers across 190 countries.</description><pubDate>Thu, 01 Feb 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Netflix is the canonical example of microservices architecture at massive scale. This case study explores their architecture, key decisions, and lessons learned.&lt;/p&gt;
&lt;h2&gt;Scale Numbers (2024)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;230M+&lt;/strong&gt; subscribers across &lt;strong&gt;190 countries&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;15,000+&lt;/strong&gt; titles in catalog&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1M+&lt;/strong&gt; requests/second at peak&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;15%&lt;/strong&gt; of global internet traffic (peak hours)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4,000+&lt;/strong&gt; microservices&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1,000+&lt;/strong&gt; deployments/day&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;High-Level Architecture&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                        CDN (Open Connect)                    │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐        │
│  │  ISP 1  │  │  ISP 2  │  │  ISP 3  │  │  ...    │        │
│  └────┬────┘  └────┬────┘  └────┬────┘  └────┬────┘        │
└───────┼────────────┼────────────┼────────────┼──────────────┘
        │            │            │            │
        ▼            ▼            ▼            ▼
┌─────────────────────────────────────────────────────────────┐
│                      AWS Cloud                               │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │   API GW     │  │   Zuul GW    │  │   Edge       │       │
│  │   (GraphQL)  │  │   (Routing)  │  │   Services   │       │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘       │
└─────────┼─────────────────┼─────────────────┼────────────────┘
          │                 │                 │
    ┌─────▼─────┐    ┌──────▼──────┐    ┌─────▼─────┐
    │  User     │    │  Content    │    │  Playback │
    │  Service  │    │  Catalog    │    │  Service  │
    └─────┬─────┘    └──────┬──────┘    └─────┬─────┘
          │                 │                 │
    ┌─────▼─────────────────▼─────────────────▼─────┐
    │           Data Layer (Cassandra, Redis, S3)   │
    └────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Key Architectural Decisions&lt;/h2&gt;
&lt;h3&gt;1. Microservices with Bounded Contexts&lt;/h3&gt;
&lt;p&gt;Each service owns a single business capability:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Service examples
services:
  user-service:
    owns: [profiles, preferences, accounts, billing]
    data: Cassandra (user profiles), PostgreSQL (billing)
  
  catalog-service:
    owns: [titles, metadata, search, recommendations]
    data: Elasticsearch, Cassandra
  
  playback-service:
    owns: [streaming, licensing, DRM, quality selection]
    data: Redis (session), S3 (manifests)
  
  discovery-service:
    owns: [search, browse, personalization]
    data: Elasticsearch, Cassandra
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. API Gateway Pattern&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Zuul&lt;/strong&gt; (Netflix&apos;s gateway) handles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Request routing&lt;/li&gt;
&lt;li&gt;Authentication/authorization&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Circuit breaking&lt;/li&gt;
&lt;li&gt;Request/response transformation&lt;/li&gt;
&lt;li&gt;A/B testing routing&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Zuul filter example
public class RateLimitFilter extends ZuulFilter {
    @Override
    public String filterType() { return &quot;pre&quot;; }
    
    @Override
    public int filterOrder() { return 1; }
    
    @Override
    public boolean shouldFilter() { return true; }
    
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        String userId = ctx.getRequest().getHeader(&quot;X-User-Id&quot;);
        
        if (!rateLimiter.tryAcquire(userId)) {
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(429);
            ctx.setResponseBody(&quot;Rate limit exceeded&quot;);
        }
        return null;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Circuit Breaker Pattern (Hystrix)&lt;/h3&gt;
&lt;p&gt;Prevent cascade failures:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@HystrixCommand(
    fallbackMethod = &quot;getFallbackCatalog&quot;,
    commandProperties = {
        @HystrixProperty(name = &quot;execution.isolation.thread.timeoutInMilliseconds&quot;, value = &quot;1000&quot;),
        @HystrixProperty(name = &quot;circuitBreaker.requestVolumeThreshold&quot;, value = &quot;20&quot;),
        @HystrixProperty(name = &quot;circuitBreaker.errorThresholdPercentage&quot;, value = &quot;50&quot;),
        @HystrixProperty(name = &quot;circuitBreaker.sleepWindowInMilliseconds&quot;, value = &quot;5000&quot;)
    }
)
public Catalog getCatalog(String userId) {
    return catalogClient.getRecommendations(userId);
}

public Catalog getFallbackCatalog(String userId) {
    // Return cached/popular content
    return catalogCache.getPopular();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Chaos Engineering (Simian Army)&lt;/h3&gt;
&lt;p&gt;Proactively inject failures:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chaos Monkey&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Randomly terminates instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency Monkey&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Adds artificial latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conformity Monkey&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Finds non-compliant instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Doctor Monkey&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Removes unhealthy instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Janitor Monkey&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cleans up unused resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chaos Gorilla&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simulates AZ outage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chaos Kong&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simulates region outage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;5. Data Architecture&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Cassandra&lt;/strong&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User viewing history (time-series)&lt;/li&gt;
&lt;li&gt;Playback positions&lt;/li&gt;
&lt;li&gt;Device registry&lt;/li&gt;
&lt;li&gt;A/B test assignments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Session management&lt;/li&gt;
&lt;li&gt;Rate limiting counters&lt;/li&gt;
&lt;li&gt;Real-time recommendations cache&lt;/li&gt;
&lt;li&gt;Feature flags&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;S3&lt;/strong&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Video manifests (HLS/DASH)&lt;/li&gt;
&lt;li&gt;Thumbnails, artwork&lt;/li&gt;
&lt;li&gt;Backup/restore&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Elasticsearch&lt;/strong&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search indexing&lt;/li&gt;
&lt;li&gt;Log aggregation&lt;/li&gt;
&lt;li&gt;Metrics&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Resilience Patterns&lt;/h2&gt;
&lt;h3&gt;1. Bulkhead Pattern&lt;/h3&gt;
&lt;p&gt;Isolate failures to prevent cascade:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Thread pool isolation per dependency
hystrix:
  threadpool:
    catalogService:
      coreSize: 20
      maxQueueSize: 100
    userService:
      coreSize: 10
      maxQueueSize: 50
    playbackService:
      coreSize: 50
      maxQueueSize: 200
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Timeout and Retry Budgets&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Retry with exponential backoff + jitter
@Retryable(
    maxAttempts = 3,
    backoff = @Backoff(delay = 100, multiplier = 2, random = true),
    value = {TimeoutException.class, ConnectException.class}
)
public Catalog fetchCatalog(String userId) {
    return catalogClient.getCatalog(userId);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Graceful Degradation&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public PlaybackManifest getManifest(String titleId, String deviceId) {
    try {
        return manifestService.getPersonalizedManifest(titleId, deviceId);
    } catch (Exception e) {
        log.warn(&quot;Personalized manifest failed, using generic&quot;, e);
        return manifestService.getGenericManifest(titleId);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Request Collapsing&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Collapse duplicate concurrent requests
@RequestCollapser(
    scope = RequestCollapser.Scope.REQUEST,
    collapserKey = &quot;getUserProfile&quot;
)
public UserProfile getUserProfile(String userId) {
    return userClient.getProfile(userId);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Deployment Pipeline&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────┐
│  Code   │──▶│  Build  │──▶│  Test   │──▶│  Canary │──▶│  Prod   │
│  Commit │   │  &amp;amp; Unit │   │  Int.   │   │  Deploy │   │  Rollout│
└─────────┘   └─────────┘   └─────────┘   └─────────┘   └─────────┘
                 │             │             │             │
          ┌──────▼──────┐ ┌───▼────┐ ┌──────▼──────┐ ┌────▼────┐
          │  Spinnaker  │ │ Bakery │ │  Kayenta    │ │  Atlas  │
          │  Pipeline   │ │  (AMI) │ │  (Canary    │ │  (Metrics)│
          └─────────────┘ └────────┘ │   Analysis) │ └─────────┘
                                    └─────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Spinnaker Pipeline Stages:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bake&lt;/strong&gt;: Create immutable AMI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy to Dev&lt;/strong&gt;: Automated&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration Tests&lt;/strong&gt;: Automated&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Canary Deploy&lt;/strong&gt;: 1% → 10% → 100%&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kayenta Analysis&lt;/strong&gt;: Automated metric comparison&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production Rollout&lt;/strong&gt;: Gradual with pause points&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Key Lessons Learned&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Invest in observability early&lt;/strong&gt;: Metrics, logs, traces, dashboards&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automate everything&lt;/strong&gt;: Deployments, rollbacks, capacity, failover&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Design for failure&lt;/strong&gt;: Assume everything will fail&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Culture of ownership&lt;/strong&gt;: You build it, you run it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Incremental migration&lt;/strong&gt;: Strangler fig pattern from monolith&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standardize but allow innovation&lt;/strong&gt;: Paved road + experimentation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chaos engineering is not optional&lt;/strong&gt;: Test failure scenarios regularly&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Modern Evolution (2020+)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GraphQL Federation&lt;/strong&gt;: Unified API layer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gRPC&lt;/strong&gt;: Service-to-service communication&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;: Container orchestration (migrating from Titus)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serverless&lt;/strong&gt;: AWS Lambda for sporadic workloads&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ML Platform&lt;/strong&gt;: Metaflow for ML workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://netflixtechblog.com/&quot;&gt;Netflix Tech Blog&lt;/a&gt; - Primary source&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7b80TAPnuTU&quot;&gt;Microservices at Netflix Scale&lt;/a&gt; - Josh Evans&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.oreilly.com/library/view/chaos-engineering/9781492043867/&quot;&gt;Chaos Engineering&lt;/a&gt; - Book&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://netflix.github.io/&quot;&gt;Netflix OSS&lt;/a&gt; - Open source tools&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://samnewman.io/books/building_microservices/&quot;&gt;Building Microservices&lt;/a&gt; - Sam Newman&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>case-studies</category><category>netflix</category><category>microservices</category><category>streaming</category><category>chaos-engineering</category><category>aws</category><category>resilience</category></item><item><title>Caching Strategies: Patterns and Best Practices</title><link>https://system-design-notes.vercel.app/notes/caching-caching-strategies/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/caching-caching-strategies/</guid><description>Comprehensive guide to caching patterns including write-through, write-back, read-through, and cache-aside. When to use each and common pitfalls.</description><pubDate>Thu, 25 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Caching is one of the highest-leverage optimizations in system design. A well-designed cache can reduce latency by 10-100x and reduce database load by 90%+.&lt;/p&gt;
&lt;h2&gt;Why Cache?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Latency&lt;/strong&gt;: Memory (ns-μs) vs Disk (ms) vs Network (ms)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Throughput&lt;/strong&gt;: Reduce load on primary data store&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost&lt;/strong&gt;: Serve more requests with same infrastructure&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Availability&lt;/strong&gt;: Cache can serve stale data during outages&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cache Patterns&lt;/h2&gt;
&lt;h3&gt;1. Cache-Aside (Lazy Loading)&lt;/h3&gt;
&lt;p&gt;Most common pattern. Application manages cache.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Read:
  1. Check cache
  2. If hit → return data
  3. If miss → query DB → store in cache → return data

Write:
  1. Update DB
  2. Invalidate/Update cache
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Cache-aside implementation
class CacheAside:
    def __init__(self, cache, db):
        self.cache = cache
        self.db = db
    
    def get(self, key: str):
        # Try cache first
        value = self.cache.get(key)
        if value is not None:
            return value
        
        # Cache miss - fetch from DB
        value = self.db.get(key)
        if value is not None:
            self.cache.set(key, value, ttl=300)
        return value
    
    def set(self, key: str, value: any):
        # Write to DB first
        self.db.set(key, value)
        # Then invalidate cache (or update)
        self.cache.delete(key)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Simple, cache only stores hot data, resilient to cache failures
&lt;strong&gt;Cons&lt;/strong&gt;: Cache miss penalty, potential for stale data on write&lt;/p&gt;
&lt;h3&gt;2. Read-Through&lt;/h3&gt;
&lt;p&gt;Cache sits between app and DB. Cache handles misses.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Read:
  1. App requests from cache
  2. Cache checks → if miss, loads from DB → returns to app
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Read-through cache (transparent to app)
class ReadThroughCache:
    def __init__(self, cache, db):
        self.cache = cache
        self.db = db
    
    def get(self, key: str):
        value = self.cache.get(key)
        if value is None:
            # Cache loads from DB automatically
            value = self.db.get(key)
            if value is not None:
                self.cache.set(key, value, ttl=300)
        return value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Application logic simpler, cache manages population
&lt;strong&gt;Cons&lt;/strong&gt;: More complex cache implementation, first request always slow&lt;/p&gt;
&lt;h3&gt;3. Write-Through&lt;/h3&gt;
&lt;p&gt;Writes go to cache and DB synchronously.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Write:
  1. App writes to cache
  2. Cache writes to DB (synchronously)
  3. Return success
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class WriteThroughCache:
    def set(self, key: str, value: any):
        # Write to both cache and DB
        self.cache.set(key, value, ttl=300)
        self.db.set(key, value)  # Synchronous
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Cache always consistent with DB, no stale reads
&lt;strong&gt;Cons&lt;/strong&gt;: Higher write latency, DB must be available&lt;/p&gt;
&lt;h3&gt;4. Write-Back (Write-Behind)&lt;/h3&gt;
&lt;p&gt;Writes go to cache first, DB updated asynchronously.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Write:
  1. App writes to cache
  2. Return success immediately
  2. Cache asynchronously writes to DB (batched)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import asyncio
from collections import deque

class WriteBackCache:
    def __init__(self, cache, db, flush_interval=5):
        self.cache = cache
        self.db = db
        self.write_queue = deque()
        self.flush_interval = flush_interval
        asyncio.create_task(self._flush_loop())
    
    def set(self, key: str, value: any):
        self.cache.set(key, value, ttl=300)
        self.write_queue.append((key, value))
    
    async def _flush_loop(self):
        while True:
            await asyncio.sleep(self.flush_interval)
            await self._flush()
    
    async def _flush(self):
        if not self.write_queue:
            return
        # Batch write to DB
        batch = []
        while self.write_queue:
            batch.append(self.write_queue.popleft())
        await self.db.batch_set(batch)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;: Lowest write latency, batches DB writes
&lt;strong&gt;Cons&lt;/strong&gt;: Data loss risk on cache failure, complex implementation&lt;/p&gt;
&lt;h3&gt;5. Refresh-Ahead&lt;/h3&gt;
&lt;p&gt;Proactively refresh cache before expiration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class RefreshAheadCache:
    def __init__(self, cache, db, refresh_threshold=0.8):
        self.cache = cache
        self.db = db
        self.refresh_threshold = refresh_threshold  # 80% of TTL
    
    async def get(self, key: str):
        value, ttl = await self.cache.get_with_ttl(key)
        
        # Proactively refresh if near expiration
        if value is not None and ttl &amp;lt; self.cache.default_ttl * self.refresh_threshold:
            asyncio.create_task(self._refresh(key))
        
        if value is None:
            value = await self.db.get(key)
            if value:
                await self.cache.set(key, value)
        return value
    
    async def _refresh(self, key: str):
        value = await self.db.get(key)
        if value:
            await self.cache.set(key, value)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Cache Invalidation Strategies&lt;/h2&gt;
&lt;h3&gt;Time-Based (TTL)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Simple TTL
cache.set(key, value, ttl=300)  # 5 minutes
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Event-Based (Explicit Invalidation)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# On write, invalidate related keys
def update_user(user_id, data):
    db.update_user(user_id, data)
    cache.delete(f&quot;user:{user_id}&quot;)
    cache.delete(f&quot;user:{user_id}:profile&quot;)
    cache.delete(f&quot;user:{user_id}:posts&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Key-Based Invalidation (Versioning)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Include version in key
cache_key = f&quot;user:{user_id}:v{user.version}&quot;

# On update, increment version
user.version += 1
db.save(user)
# Old cache key becomes unreachable
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Cache Tags (Redis 7+)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-redis&quot;&gt;# Tag related keys
SET user:1:profile &quot;...&quot; EX 300
SADD tag:user:1 user:1:profile

# Invalidate all user:1 keys
SMEMBERS tag:user:1 → DEL each key
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Common Pitfalls&lt;/h2&gt;
&lt;h3&gt;1. Thundering Herd&lt;/h3&gt;
&lt;p&gt;Multiple requests hit expired cache simultaneously.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Solution: Use distributed lock
async def get_with_lock(key: str):
    value = await cache.get(key)
    if value:
        return value
    
    # Try to acquire lock
    lock = await redis.set(f&quot;lock:{key}&quot;, &quot;1&quot;, nx=True, ex=10)
    if lock:
        try:
            value = await db.get(key)
            await cache.set(key, value)
            return value
        finally:
            await redis.delete(f&quot;lock:{key}&quot;)
    else:
        # Wait for other request to populate
        await asyncio.sleep(0.1)
        return await cache.get(key)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Cache Stampede (Dogpile Effect)&lt;/h3&gt;
&lt;p&gt;Same as thundering herd - use &lt;strong&gt;probabilistic early expiration&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Add jitter to TTL
import random
ttl = base_ttl + random.randint(0, base_ttl // 4)
cache.set(key, value, ttl=ttl)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Hot Keys&lt;/h3&gt;
&lt;p&gt;Single key gets disproportionate traffic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Solution: Key sharding + local cache
def get_sharded(key: str):
    # Split hot key across multiple cache entries
    shard = hash(key) % 10
    return cache.get(f&quot;{key}:shard:{shard}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Cache Pollution&lt;/h3&gt;
&lt;p&gt;Cache filled with rarely-accessed data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Solution: LRU eviction + frequency-based admission
# Redis: maxmemory-policy allkeys-lfu
# Or use TinyLFU / W-TinyLFU (Ristretto, Caffeine)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Multi-Level Caching&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Request → CDN (edge) → API Gateway → Redis (distributed) → Local (in-memory) → DB
              ↓              ↓               ↓                ↓
           Static         Auth/Rate       Session/         Hot data
           Assets         Limit           User Data
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// Multi-level cache in Go
type MultiLevelCache struct {
    local  *ristretto.Cache  // In-process, ~100MB
    shared *redis.Client     // Distributed
}

func (m *MultiLevelCache) Get(key string) (string, bool) {
    // L1: Local cache (fastest)
    if val, ok := m.local.Get(key); ok {
        return val.(string), true
    }
    
    // L2: Shared cache
    val, err := m.shared.Get(ctx, key).Result()
    if err == nil {
        m.local.Set(key, val, 1)
        return val, true
    }
    
    return &quot;&quot;, false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Cache Sizing&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cache Size&lt;/th&gt;
&lt;th&gt;Hit Rate (Typical)&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1% of data&lt;/td&gt;
&lt;td&gt;50-70%&lt;/td&gt;
&lt;td&gt;Very hot subset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5% of data&lt;/td&gt;
&lt;td&gt;80-90%&lt;/td&gt;
&lt;td&gt;Most applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10% of data&lt;/td&gt;
&lt;td&gt;90-95%&lt;/td&gt;
&lt;td&gt;Read-heavy workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20%+ of data&lt;/td&gt;
&lt;td&gt;95%+&lt;/td&gt;
&lt;td&gt;Specialized&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Monitoring Metrics&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-prometheus&quot;&gt;# Key cache metrics
cache_hit_rate{instance=&quot;...&quot;}  # Target: &amp;gt;90%
cache_miss_rate{instance=&quot;...&quot;}
cache_eviction_rate{instance=&quot;...&quot;}
cache_memory_usage_bytes{instance=&quot;...&quot;}
cache_latency_p99{instance=&quot;...&quot;}  # Target: &amp;lt;1ms
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/caching/&quot;&gt;Caching Best Practices&lt;/a&gt; - AWS&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://redis.io/docs/manual/patterns/&quot;&gt;Redis Caching Patterns&lt;/a&gt; - Redis&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.facebook.com/notes/facebook-engineering/scaling-memcached-at-facebook/10151641623253920/&quot;&gt;Scaling Memcached at Facebook&lt;/a&gt; - Facebook Engineering&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ben-manes/caffeine&quot;&gt;Caffeine Cache&lt;/a&gt; - High-performance Java cache&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dgraph-io/ristretto&quot;&gt;Ristretto&lt;/a&gt; - Fast Go cache&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>caching</category><category>caching</category><category>redis</category><category>memcached</category><category>cache-invalidation</category><category>cache-patterns</category><category>cdn</category></item><item><title>SQL vs NoSQL: Choosing the Right Database</title><link>https://system-design-notes.vercel.app/notes/databases-sql-vs-nosql/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/databases-sql-vs-nosql/</guid><description>A comprehensive comparison of relational and non-relational databases, their trade-offs, and decision frameworks for system design.</description><pubDate>Mon, 22 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Choosing between SQL and NoSQL is one of the most fundamental architectural decisions. This guide helps you make an informed choice based on your specific requirements.&lt;/p&gt;
&lt;h2&gt;Quick Comparison&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;SQL (Relational)&lt;/th&gt;
&lt;th&gt;NoSQL (Non-Relational)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tables, rows, columns&lt;/td&gt;
&lt;td&gt;Documents, key-value, wide-column, graph&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schema&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fixed, enforced&lt;/td&gt;
&lt;td&gt;Flexible, schema-on-read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Query Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQL (standardized)&lt;/td&gt;
&lt;td&gt;Varies by database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transactions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ACID (strong)&lt;/td&gt;
&lt;td&gt;BASE (eventual)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vertical (primarily)&lt;/td&gt;
&lt;td&gt;Horizontal (native)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Joins&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native support&lt;/td&gt;
&lt;td&gt;Limited/denormalized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Eventual (tunable)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;SQL Databases&lt;/h2&gt;
&lt;h3&gt;When to Choose SQL&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Complex relationships&lt;/strong&gt;: Foreign keys, joins, many-to-many&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACID transactions required&lt;/strong&gt;: Financial, inventory, booking systems&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured, stable schema&lt;/strong&gt;: Well-defined entities that don&apos;t change often&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ad-hoc queries&lt;/strong&gt;: Need flexible querying without knowing access patterns upfront&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mature ecosystem&lt;/strong&gt;: Tooling, ORMs, migration tools, expertise&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Popular SQL Databases&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Database&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Notable Features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;General purpose, complex queries&lt;/td&gt;
&lt;td&gt;JSONB, extensions, window functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MySQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Web applications, read-heavy&lt;/td&gt;
&lt;td&gt;Simple, fast, wide adoption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SQL Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise, .NET ecosystem&lt;/td&gt;
&lt;td&gt;T-SQL, integration services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CockroachDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Distributed SQL&lt;/td&gt;
&lt;td&gt;Horizontal scaling, strong consistency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TiDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTAP (hybrid transactional/analytical)&lt;/td&gt;
&lt;td&gt;MySQL compatible, distributed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Schema Design Example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Normalized schema for e-commerce
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id),
    status VARCHAR(50) NOT NULL DEFAULT &apos;pending&apos;,
    total_cents INTEGER NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE order_items (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    order_id UUID REFERENCES orders(id),
    product_id UUID NOT NULL,
    quantity INTEGER NOT NULL,
    price_cents INTEGER NOT NULL
);

-- Indexes for common queries
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status_created ON orders(status, created_at DESC);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;NoSQL Databases&lt;/h2&gt;
&lt;h3&gt;Types of NoSQL&lt;/h3&gt;
&lt;h4&gt;Document Databases (MongoDB, Couchbase)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Store JSON-like documents&lt;/li&gt;
&lt;li&gt;Flexible schema, nested data&lt;/li&gt;
&lt;li&gt;Good for: Content management, catalogs, user profiles&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Key-Value Stores (Redis, DynamoDB, Riak)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Simple key → value mapping&lt;/li&gt;
&lt;li&gt;Extremely fast reads/writes&lt;/li&gt;
&lt;li&gt;Good for: Caching, sessions, shopping carts&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Wide-Column Stores (Cassandra, HBase, ScyllaDB)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Column families, sparse columns&lt;/li&gt;
&lt;li&gt;Time-series, high write throughput&lt;/li&gt;
&lt;li&gt;Good for: Logging, metrics, sensor data&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Graph Databases (Neo4j, Amazon Neptune, ArangoDB)&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Nodes and relationships&lt;/li&gt;
&lt;li&gt;Traversals, recommendations&lt;/li&gt;
&lt;li&gt;Good for: Social networks, fraud detection, knowledge graphs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;When to Choose NoSQL&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Massive scale&lt;/strong&gt;: Petabytes, millions of writes/second&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexible schema&lt;/strong&gt;: Rapidly changing data structures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Specific access patterns&lt;/strong&gt;: Known query patterns, no ad-hoc queries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High availability&lt;/strong&gt;: AP systems for global applications&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Specialized workloads&lt;/strong&gt;: Time-series, graphs, key-value&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;NoSQL Data Modeling&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// MongoDB: Embedded vs Referenced
// Embedded (one-to-few, read together)
{
  _id: ObjectId(&quot;...&quot;),
  title: &quot;System Design Notes&quot;,
  author: { name: &quot;John&quot;, email: &quot;john@example.com&quot; },
  sections: [
    { title: &quot;CAP Theorem&quot;, content: &quot;...&quot;, order: 1 },
    { title: &quot;Consensus&quot;, content: &quot;...&quot;, order: 2 }
  ],
  tags: [&quot;distributed-systems&quot;, &quot;architecture&quot;],
  createdAt: ISODate(&quot;2024-01-15&quot;)
}

// Referenced (one-to-many, independent lifecycle)
{
  _id: ObjectId(&quot;...&quot;),
  title: &quot;System Design Notes&quot;,
  authorId: ObjectId(&quot;...&quot;),  // Reference to users collection
  sectionIds: [ObjectId(&quot;...&quot;), ObjectId(&quot;...&quot;)],  // Array of refs
  tags: [&quot;distributed-systems&quot;, &quot;architecture&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Decision Framework&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;START: What&apos;s your data like?
├── Highly relational, complex joins?
│   └── YES → SQL (PostgreSQL)
├── Need ACID transactions?
│   └── YES → SQL (PostgreSQL, CockroachDB)
├── Massive scale, simple access patterns?
│   └── YES → NoSQL (Cassandra, DynamoDB)
├── Flexible schema, rapid iteration?
│   └── YES → NoSQL (MongoDB)
├── Graph relationships, traversals?
│   └── YES → Graph DB (Neo4j)
├── Key-value, caching, sessions?
│   └── YES → Redis, DynamoDB
└── Time-series, metrics, logs?
    └── YES → Wide-column (TimescaleDB, InfluxDB)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Hybrid Approaches&lt;/h2&gt;
&lt;p&gt;Many modern systems use &lt;strong&gt;polyglot persistence&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────────────────────────────────────┐
│                  Application                     │
└──────────┬──────────────┬───────────┬───────────┘
           │              │           │
    ┌──────▼──────┐ ┌─────▼────┐ ┌────▼────┐
    │ PostgreSQL  │ │  Redis   │ │Elastic  │
    │ (Orders,    │ │ (Cache,  │ │Search   │
    │  Users,     │ │  Sessions)│ │(Logs,   │
    │  Payments)  │ │          │ │  Docs)  │
    └─────────────┘ └──────────┘ └─────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;NewSQL: Best of Both Worlds&lt;/h2&gt;
&lt;p&gt;NewSQL databases provide horizontal scaling with ACID:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CockroachDB&lt;/strong&gt;: PostgreSQL-compatible, geo-distributed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TiDB&lt;/strong&gt;: MySQL-compatible, HTAP&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Spanner&lt;/strong&gt;: Global consistency with TrueTime&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;YugabyteDB&lt;/strong&gt;: PostgreSQL-compatible, multi-cloud&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- CockroachDB: Globally distributed, strong consistency
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email STRING UNIQUE NOT NULL,
    region STRING NOT NULL
) 
WITH (replication_zone = &apos;global&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Checklist for Database Selection&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[ ] What are the &lt;strong&gt;access patterns&lt;/strong&gt;? (Known vs ad-hoc)&lt;/li&gt;
&lt;li&gt;[ ] What are the &lt;strong&gt;consistency requirements&lt;/strong&gt;? (Strong vs eventual)&lt;/li&gt;
&lt;li&gt;[ ] What is the &lt;strong&gt;scale&lt;/strong&gt;? (Current and projected)&lt;/li&gt;
&lt;li&gt;[ ] What is the &lt;strong&gt;team expertise&lt;/strong&gt;? (SQL vs NoSQL experience)&lt;/li&gt;
&lt;li&gt;[ ] What are the &lt;strong&gt;operational constraints&lt;/strong&gt;? (Managed vs self-hosted)&lt;/li&gt;
&lt;li&gt;[ ] What are the &lt;strong&gt;compliance requirements&lt;/strong&gt;? (ACID, audit trails)&lt;/li&gt;
&lt;li&gt;[ ] What is the &lt;strong&gt;data lifecycle&lt;/strong&gt;? (Retention, archival, GDPR)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.databass.dev/&quot;&gt;Database Internals&lt;/a&gt; - Alex Petrov&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dataintensiveapplications.com/&quot;&gt;Designing Data-Intensive Applications&lt;/a&gt; - Martin Kleppmann (Ch. 2-3)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://martinfowler.com/books/nosql.html&quot;&gt;NoSQL Distilled&lt;/a&gt; - Pramod Sadalage &amp;amp; Martin Fowler&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cockroachlabs.com/blog/choosing-a-database/&quot;&gt;Choosing a Database&lt;/a&gt; - Cockroach Labs&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>databases</category><category>sql</category><category>nosql</category><category>database-selection</category><category>acid</category><category>base</category><category>data-modeling</category></item><item><title>Scalability Patterns: Vertical vs Horizontal</title><link>https://system-design-notes.vercel.app/notes/system-design-fundamentals-scalability/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/system-design-fundamentals-scalability/</guid><description>Understanding vertical and horizontal scaling, when to use each, and practical patterns for building scalable systems.</description><pubDate>Sat, 20 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Scalability is a system&apos;s ability to handle increased load by adding resources. There are two fundamental approaches: &lt;strong&gt;vertical scaling&lt;/strong&gt; (scale up) and &lt;strong&gt;horizontal scaling&lt;/strong&gt; (scale out).&lt;/p&gt;
&lt;h2&gt;Vertical Scaling (Scale Up)&lt;/h2&gt;
&lt;p&gt;Adding more resources (CPU, RAM, storage) to a single machine.&lt;/p&gt;
&lt;h3&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Simple: No application changes needed&lt;/li&gt;
&lt;li&gt;Strong consistency: Single database, no distributed transactions&lt;/li&gt;
&lt;li&gt;Lower operational complexity: One server to manage&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hardware limits&lt;/strong&gt;: Maximum CPU, RAM, storage per machine&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single point of failure&lt;/strong&gt;: If the machine dies, everything dies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost&lt;/strong&gt;: High-end hardware is disproportionately expensive&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Downtime&lt;/strong&gt;: Often requires restart for upgrades&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;When to Use&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Early-stage startups with predictable growth&lt;/li&gt;
&lt;li&gt;Legacy applications hard to distribute&lt;/li&gt;
&lt;li&gt;Workloads that don&apos;t parallelize well (some ML training)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Example: AWS RDS vertical scaling
# r5.large → r5.xlarge → r5.2xlarge → r5.4xlarge
# vCPUs: 2 → 4 → 8 → 16
# Memory: 16GB → 32GB → 64GB → 128GB
# Cost/month: ~$180 → $360 → $720 → $1440
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Horizontal Scaling (Scale Out)&lt;/h2&gt;
&lt;p&gt;Adding more machines to distribute the load.&lt;/p&gt;
&lt;h3&gt;Pros&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No practical limits&lt;/strong&gt;: Can scale to thousands of machines&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fault tolerance&lt;/strong&gt;: Failure of one node doesn&apos;t bring down system&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost-effective&lt;/strong&gt;: Commodity hardware, linear cost scaling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Elasticity&lt;/strong&gt;: Add/remove nodes based on demand&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cons&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Complexity&lt;/strong&gt;: Distributed systems challenges (CAP, consensus, consistency)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application changes&lt;/strong&gt;: Code must be designed for distribution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operational overhead&lt;/strong&gt;: More servers to monitor, deploy, debug&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network latency&lt;/strong&gt;: Inter-node communication adds latency&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;When to Use&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;High-growth applications&lt;/li&gt;
&lt;li&gt;Systems requiring high availability&lt;/li&gt;
&lt;li&gt;Microservices architectures&lt;/li&gt;
&lt;li&gt;Big data processing&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Scaling Patterns&lt;/h2&gt;
&lt;h3&gt;1. Load Balancing&lt;/h3&gt;
&lt;p&gt;Distribute incoming requests across multiple servers.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-nginx&quot;&gt;# NGINX load balancer config
upstream backend {
    least_conn;  # Least connections algorithm
    server 10.0.0.1:8080 weight=3;
    server 10.0.0.2:8080 weight=2;
    server 10.0.0.3:8080 weight=1;
    
    # Health checks
    server 10.0.0.4:8080 backup;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Stateless Services&lt;/h3&gt;
&lt;p&gt;Services that don&apos;t store session state locally.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client → Load Balancer → [Service A, Service B, Service C]
                              ↓
                        Shared Cache (Redis)
                              ↓
                        Database
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Database Sharding&lt;/h3&gt;
&lt;p&gt;Partition data across multiple database instances.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Simple hash-based sharding
def get_shard(user_id: int, num_shards: int) -&amp;gt; int:
    return hash(user_id) % num_shards

# Consistent hashing (better for dynamic scaling)
import hashlib

class ConsistentHashRing:
    def __init__(self, nodes: list, replicas: int = 150):
        self.ring = {}
        self.sorted_keys = []
        
        for node in nodes:
            for i in range(replicas):
                key = self._hash(f&quot;{node}:{i}&quot;)
                self.ring[key] = node
                self.sorted_keys.append(key)
        self.sorted_keys.sort()
    
    def get_node(self, key: str) -&amp;gt; str:
        hash_key = self._hash(key)
        for node_hash in self.sorted_keys:
            if node_hash &amp;gt;= hash_key:
                return self.ring[node_hash]
        return self.ring[self.sorted_keys[0]]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Read Replicas&lt;/h3&gt;
&lt;p&gt;Scale reads by replicating data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Primary handles writes
INSERT INTO orders (...) VALUES (...);

-- Replicas handle reads
SELECT * FROM orders WHERE user_id = ?;  -- Routed to replica
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. Caching Layers&lt;/h3&gt;
&lt;p&gt;Reduce database load with strategic caching.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Request → CDN → API Gateway → Redis Cache → Database
              ↓              ↓
           Static          Computed
           Assets          Responses
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The Scalability Checklist&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Vertical Approach&lt;/th&gt;
&lt;th&gt;Horizontal Approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bigger VMs&lt;/td&gt;
&lt;td&gt;More containers/pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bigger instance&lt;/td&gt;
&lt;td&gt;Sharding + read replicas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;More memory&lt;/td&gt;
&lt;td&gt;Cluster mode (Redis Cluster)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bigger disks&lt;/td&gt;
&lt;td&gt;Distributed FS (S3, HDFS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Higher bandwidth&lt;/td&gt;
&lt;td&gt;Load balancers, CDN&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Cost Comparison&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Monthly Cost (Example)&lt;/th&gt;
&lt;th&gt;Max Throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vertical (r5.4xlarge)&lt;/td&gt;
&lt;td&gt;~$1,440&lt;/td&gt;
&lt;td&gt;~50K req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal (10 × r5.large)&lt;/td&gt;
&lt;td&gt;~$1,800&lt;/td&gt;
&lt;td&gt;~200K req/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal (auto-scaled)&lt;/td&gt;
&lt;td&gt;Variable&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Anti-Patterns to Avoid&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Distributed monolith&lt;/strong&gt;: Services that must be deployed together&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chatty services&lt;/strong&gt;: Excessive inter-service communication&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared database&lt;/strong&gt;: Multiple services sharing one database&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Synchronous chains&lt;/strong&gt;: A→B→C→D where each call blocks&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://martinfowler.com/articles/scalability.html&quot;&gt;Scalability Patterns&lt;/a&gt; - Martin Fowler&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/Art-Scalability-Scalable-Architecture-Organizations/dp/0134032802&quot;&gt;The Art of Scalability&lt;/a&gt; - Abbott &amp;amp; Fisher&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dataintensiveapplications.com/&quot;&gt;Designing Data-Intensive Applications&lt;/a&gt; - Martin Kleppmann (Ch. 5-6)&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>system-design-fundamentals</category><category>scalability</category><category>horizontal-scaling</category><category>vertical-scaling</category><category>load-balancing</category><category>sharding</category></item><item><title>Consensus Algorithms: Raft vs Paxos</title><link>https://system-design-notes.vercel.app/notes/distributed-systems-consensus-algorithms/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/distributed-systems-consensus-algorithms/</guid><description>Comparing Raft and Paxos consensus algorithms, their trade-offs, and when to use each in distributed systems.</description><pubDate>Thu, 18 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Consensus is the problem of getting a group of nodes to agree on a single value. It&apos;s fundamental to building reliable distributed systems.&lt;/p&gt;
&lt;h2&gt;Why Consensus Matters&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Leader election&lt;/strong&gt;: Who coordinates?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State machine replication&lt;/strong&gt;: Keeping replicas in sync&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration management&lt;/strong&gt;: Cluster membership changes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distributed locks&lt;/strong&gt;: Coordinating access to resources&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Paxos: The Classic Algorithm&lt;/h2&gt;
&lt;p&gt;Paxos was introduced by Leslie Lamport in 1998. It&apos;s notoriously difficult to understand and implement correctly.&lt;/p&gt;
&lt;h3&gt;Basic Paxos (Single-Decree)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prepare Phase&lt;/strong&gt;: Proposer sends &lt;code&gt;prepare(n)&lt;/code&gt; to majority&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Promise Phase&lt;/strong&gt;: Acceptors promise not to accept proposals &lt;code&gt;&amp;lt; n&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accept Phase&lt;/strong&gt;: Proposer sends &lt;code&gt;accept(n, value)&lt;/code&gt; to majority&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn Phase&lt;/strong&gt;: Learners learn the accepted value&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Multi-Paxos&lt;/h3&gt;
&lt;p&gt;Optimizes for repeated consensus by electing a stable leader.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// Simplified Paxos proposer
type Proposer struct {
    proposalNumber int
    acceptors      []Acceptor
}

func (p *Proposer) Propose(value interface{}) error {
    // Phase 1: Prepare
    n := p.generateProposalNumber()
    promises := p.sendPrepare(n)
    
    if len(promises) &amp;lt; p.quorumSize() {
        return ErrNoQuorum
    }
    
    // Phase 2: Accept
    chosenValue := p.chooseValue(promises, value)
    accepted := p.sendAccept(n, chosenValue)
    
    if len(accepted) &amp;lt; p.quorumSize() {
        return ErrNoQuorum
    }
    
    return nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Raft: Understandable Consensus&lt;/h2&gt;
&lt;p&gt;Raft was designed by Diego Ongaro and John Ousterhout in 2014 with the explicit goal of being &lt;strong&gt;understandable&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;Key Differences from Paxos&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Paxos&lt;/th&gt;
&lt;th&gt;Raft&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Leader&lt;/td&gt;
&lt;td&gt;Implicit, multiple possible&lt;/td&gt;
&lt;td&gt;Explicit, single leader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Log Structure&lt;/td&gt;
&lt;td&gt;Single-decree, complex composition&lt;/td&gt;
&lt;td&gt;Continuous log entries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Membership Changes&lt;/td&gt;
&lt;td&gt;Complex&lt;/td&gt;
&lt;td&gt;Joint consensus (simple)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readability&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Raft States&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Follower → Candidate → Leader
    ↑_______________|
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Follower&lt;/strong&gt;: Passive, responds to RPCs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Candidate&lt;/strong&gt;: Runs for leader, requests votes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leader&lt;/strong&gt;: Handles all client requests, replicates log&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Raft Guarantees&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Election Safety&lt;/strong&gt;: At most one leader per term&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leader Completeness&lt;/strong&gt;: Leader has all committed entries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State Machine Safety&lt;/strong&gt;: Identical logs → identical state&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Leader Election&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// Raft election timeout (randomized 150-300ms)
func (rf *Raft) startElection() {
    rf.currentTerm++
    rf.votedFor = rf.me
    rf.state = Candidate
    
    votes := 1 // Vote for self
    for i := range rf.peers {
        if i == rf.me { continue }
        
        go func(server int) {
            args := RequestVoteArgs{
                Term:         rf.currentTerm,
                CandidateId:  rf.me,
                LastLogIndex: rf.getLastLogIndex(),
                LastLogTerm:  rf.getLastLogTerm(),
            }
            reply := RequestVoteReply{}
            if rf.sendRequestVote(server, &amp;amp;args, &amp;amp;reply) {
                rf.mu.Lock()
                if reply.VoteGranted {
                    votes++
                    if votes &amp;gt; len(rf.peers)/2 {
                        rf.becomeLeader()
                    }
                } else if reply.Term &amp;gt; rf.currentTerm {
                    rf.becomeFollower(reply.Term)
                }
                rf.mu.Unlock()
            }
        }(i)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;When to Use Which?&lt;/h2&gt;
&lt;h3&gt;Use Raft When:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Building a new system (easier to implement correctly)&lt;/li&gt;
&lt;li&gt;Team needs to understand and debug the consensus logic&lt;/li&gt;
&lt;li&gt;Need clear membership change semantics&lt;/li&gt;
&lt;li&gt;Using etcd, Consul, or TiKV&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Use Paxos When:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Working with existing Paxos-based systems (Chubby, Spanner)&lt;/li&gt;
&lt;li&gt;Need highly optimized, battle-tested implementations&lt;/li&gt;
&lt;li&gt;Academic/research context&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Production Systems Using Raft&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;etcd&lt;/strong&gt;: Kubernetes configuration store&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consul&lt;/strong&gt;: Service mesh configuration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TiKV&lt;/strong&gt;: Distributed transactional KV store&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CockroachDB&lt;/strong&gt;: Distributed SQL (Raft variant)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis Cluster&lt;/strong&gt;: Redis 7+ uses Raft for replication&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://raft.github.io/raft.pdf&quot;&gt;In Search of an Understandable Consensus Algorithm&lt;/a&gt; - Original Raft paper&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lamport.azurewebsites.net/pubs/paxos-simple.pdf&quot;&gt;Paxos Made Simple&lt;/a&gt; - Lamport&apos;s Paxos paper&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://raft.github.io/&quot;&gt;Raft Visualization&lt;/a&gt; - Interactive visualization&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cs.cornell.edu/courses/cs5412/2011sp/notes/consensus.pdf&quot;&gt;Consensus in Distributed Systems&lt;/a&gt; - Cornell notes&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>distributed-systems</category><category>consensus</category><category>raft</category><category>paxos</category><category>leader-election</category><category>replication</category></item><item><title>CAP Theorem: Understanding the Trade-offs</title><link>https://system-design-notes.vercel.app/notes/distributed-systems-cap-theorem/</link><guid isPermaLink="true">https://system-design-notes.vercel.app/notes/distributed-systems-cap-theorem/</guid><description>A deep dive into the CAP theorem, its implications for distributed systems, and how to choose the right trade-offs for your system.</description><pubDate>Mon, 15 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The CAP theorem, formulated by Eric Brewer in 2000, states that a distributed data store can only simultaneously provide two out of three guarantees: &lt;strong&gt;Consistency&lt;/strong&gt;, &lt;strong&gt;Availability&lt;/strong&gt;, and &lt;strong&gt;Partition Tolerance&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;The Three Properties&lt;/h2&gt;
&lt;h3&gt;Consistency (C)&lt;/h3&gt;
&lt;p&gt;Every read receives the most recent write or an error. All nodes see the same data at the same time. This is &lt;strong&gt;linearizability&lt;/strong&gt; - not to be confused with ACID consistency.&lt;/p&gt;
&lt;h3&gt;Availability (A)&lt;/h3&gt;
&lt;p&gt;Every request receives a (non-error) response, without guarantee that it contains the most recent write. The system remains operational even if some nodes fail.&lt;/p&gt;
&lt;h3&gt;Partition Tolerance (P)&lt;/h3&gt;
&lt;p&gt;The system continues to operate despite an arbitrary number of messages being dropped or delayed by the network between nodes. &lt;strong&gt;Network partitions are inevitable&lt;/strong&gt; in distributed systems.&lt;/p&gt;
&lt;h2&gt;The Trade-off Space&lt;/h2&gt;
&lt;p&gt;Since network partitions &lt;strong&gt;will&lt;/strong&gt; happen, you must choose between CP and AP:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System Type&lt;/th&gt;
&lt;th&gt;Characteristics&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CP&lt;/strong&gt; (Consistency + Partition Tolerance)&lt;/td&gt;
&lt;td&gt;Sacrifices availability during partitions. Returns errors or times out if it can&apos;t guarantee consistency.&lt;/td&gt;
&lt;td&gt;MongoDB, Redis, HBase, ZooKeeper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AP&lt;/strong&gt; (Availability + Partition Tolerance)&lt;/td&gt;
&lt;td&gt;Sacrifices consistency during partitions. Always returns a response, but data may be stale.&lt;/td&gt;
&lt;td&gt;Cassandra, DynamoDB, CouchDB, Riak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CA&lt;/strong&gt; (Consistency + Availability)&lt;/td&gt;
&lt;td&gt;Only possible in a single-node system. Not a practical choice for distributed systems.&lt;/td&gt;
&lt;td&gt;Traditional RDBMS (single node)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;PACELC Theorem&lt;/h2&gt;
&lt;p&gt;The CAP theorem only describes behavior &lt;strong&gt;during a partition&lt;/strong&gt;. The PACELC theorem extends this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If Partition (P) then Availability vs Consistency (A vs C); Else (E) Latency vs Consistency (L vs C)&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even without partitions, there&apos;s a trade-off between latency and consistency. Strong consistency requires coordination, which adds latency.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Example: Choosing consistency level in Cassandra
from cassandra.cluster import Cluster
from cassandra import ConsistencyLevel

cluster = Cluster()
session = cluster.connect()

# Strong consistency - wait for quorum
session.execute(
    &quot;SELECT * FROM users WHERE id = ?&quot;,
    [user_id],
    consistency_level=ConsistencyLevel.QUORUM
)

# Eventual consistency - faster reads
session.execute(
    &quot;SELECT * FROM users WHERE id = ?&quot;,
    [user_id],
    consistency_level=ConsistencyLevel.ONE
)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Practical Decision Framework&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Financial transactions, inventory&lt;/strong&gt; → CP (Consistency critical)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Social media feeds, recommendations&lt;/strong&gt; → AP (Availability critical)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User profiles, sessions&lt;/strong&gt; → Often AP with read-repair&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration, metadata&lt;/strong&gt; → CP (Strong consistency needed)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Modern Perspectives&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Google Spanner&lt;/strong&gt;: Claims to be CA using TrueTime (globally synchronized clocks)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cosmos DB&lt;/strong&gt;: Offers 5 consistency levels from Strong to Eventual&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB&lt;/strong&gt;: Default eventual, supports strong consistent reads&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key insight: &lt;strong&gt;CAP is not a binary choice&lt;/strong&gt;. Modern systems offer tunable consistency per operation.&lt;/p&gt;
&lt;h2&gt;Further Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/&quot;&gt;Brewer&apos;s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services&lt;/a&gt; - Gilbert &amp;amp; Lynch proof&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/&quot;&gt;CAP Twelve Years Later: How the &quot;Rules&quot; Have Changed&lt;/a&gt; - Eric Brewer&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dbmsmusings.blogspot.com/2010/04/problems-with-cap-and-yahoos-little.html&quot;&gt;PACELC Theorem&lt;/a&gt; - Daniel Abadi&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>distributed-systems</category><category>consistency</category><category>availability</category><category>partition-tolerance</category><category>theorem</category></item></channel></rss>