+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/website/images/0vBc0hN.png b/website/images/0vBc0hN.png
new file mode 100644
index 00000000000..f1466344e2b
Binary files /dev/null and b/website/images/0vBc0hN.png differ
diff --git a/website/images/4edXG0T.png b/website/images/4edXG0T.png
new file mode 100644
index 00000000000..f2fc0c295e6
Binary files /dev/null and b/website/images/4edXG0T.png differ
diff --git a/website/images/4j99mhe.png b/website/images/4j99mhe.png
new file mode 100644
index 00000000000..9ced87347c1
Binary files /dev/null and b/website/images/4j99mhe.png differ
diff --git a/website/images/54GYsSx.png b/website/images/54GYsSx.png
new file mode 100644
index 00000000000..b23ea554e71
Binary files /dev/null and b/website/images/54GYsSx.png differ
diff --git a/website/images/5KeocQs.jpg b/website/images/5KeocQs.jpg
new file mode 100644
index 00000000000..0b718689256
Binary files /dev/null and b/website/images/5KeocQs.jpg differ
diff --git a/website/images/C9ioGtn.png b/website/images/C9ioGtn.png
new file mode 100644
index 00000000000..cea66c98028
Binary files /dev/null and b/website/images/C9ioGtn.png differ
diff --git a/website/images/IOyLj4i.jpg b/website/images/IOyLj4i.jpg
new file mode 100644
index 00000000000..413c8ee8d83
Binary files /dev/null and b/website/images/IOyLj4i.jpg differ
diff --git a/website/images/JdAsdvG.jpg b/website/images/JdAsdvG.jpg
new file mode 100644
index 00000000000..8c989e203eb
Binary files /dev/null and b/website/images/JdAsdvG.jpg differ
diff --git a/website/images/MzExP06.png b/website/images/MzExP06.png
new file mode 100644
index 00000000000..348df4761de
Binary files /dev/null and b/website/images/MzExP06.png differ
diff --git a/website/images/ONjORqk.png b/website/images/ONjORqk.png
new file mode 100644
index 00000000000..3841ca6a7e3
Binary files /dev/null and b/website/images/ONjORqk.png differ
diff --git a/website/images/OfVllex.png b/website/images/OfVllex.png
new file mode 100644
index 00000000000..c8dba136c0a
Binary files /dev/null and b/website/images/OfVllex.png differ
diff --git a/website/images/Q6z24La.png b/website/images/Q6z24La.png
new file mode 100644
index 00000000000..b673a72c5bd
Binary files /dev/null and b/website/images/Q6z24La.png differ
diff --git a/website/images/TcUo2fw.png b/website/images/TcUo2fw.png
new file mode 100644
index 00000000000..d0727cb62c4
Binary files /dev/null and b/website/images/TcUo2fw.png differ
diff --git a/website/images/U3qV33e.png b/website/images/U3qV33e.png
new file mode 100644
index 00000000000..a0e147e92e8
Binary files /dev/null and b/website/images/U3qV33e.png differ
diff --git a/website/images/V5q57vU.png b/website/images/V5q57vU.png
new file mode 100644
index 00000000000..39f4f05363f
Binary files /dev/null and b/website/images/V5q57vU.png differ
diff --git a/website/images/Xkm5CXz.png b/website/images/Xkm5CXz.png
new file mode 100644
index 00000000000..3aa5dd3805d
Binary files /dev/null and b/website/images/Xkm5CXz.png differ
diff --git a/website/images/b4YtAEN.png b/website/images/b4YtAEN.png
new file mode 100644
index 00000000000..bdb7ac4c5ae
Binary files /dev/null and b/website/images/b4YtAEN.png differ
diff --git a/website/images/bWxPtQA.png b/website/images/bWxPtQA.png
new file mode 100644
index 00000000000..e5e0726b7c2
Binary files /dev/null and b/website/images/bWxPtQA.png differ
diff --git a/website/images/bgLMI2u.png b/website/images/bgLMI2u.png
new file mode 100644
index 00000000000..e40e658dc11
Binary files /dev/null and b/website/images/bgLMI2u.png differ
diff --git a/website/images/cdCv5g7.png b/website/images/cdCv5g7.png
new file mode 100644
index 00000000000..d9ab23e51d7
Binary files /dev/null and b/website/images/cdCv5g7.png differ
diff --git a/website/images/fNcl65g.png b/website/images/fNcl65g.png
new file mode 100644
index 00000000000..7d5b6f6e3da
Binary files /dev/null and b/website/images/fNcl65g.png differ
diff --git a/website/images/h81n9iK.png b/website/images/h81n9iK.png
new file mode 100644
index 00000000000..2ba3f1b7548
Binary files /dev/null and b/website/images/h81n9iK.png differ
diff --git a/website/images/h9TAuGI.jpg b/website/images/h9TAuGI.jpg
new file mode 100644
index 00000000000..692e0631732
Binary files /dev/null and b/website/images/h9TAuGI.jpg differ
diff --git a/website/images/iF4Mkb5.png b/website/images/iF4Mkb5.png
new file mode 100644
index 00000000000..391e3cd9c48
Binary files /dev/null and b/website/images/iF4Mkb5.png differ
diff --git a/website/images/jj3A5N8.png b/website/images/jj3A5N8.png
new file mode 100644
index 00000000000..e400e5f4cd1
Binary files /dev/null and b/website/images/jj3A5N8.png differ
diff --git a/website/images/jrUBAF7.png b/website/images/jrUBAF7.png
new file mode 100644
index 00000000000..623b58242b9
Binary files /dev/null and b/website/images/jrUBAF7.png differ
diff --git a/website/images/krAHLGg.png b/website/images/krAHLGg.png
new file mode 100644
index 00000000000..7cc0e802381
Binary files /dev/null and b/website/images/krAHLGg.png differ
diff --git a/website/images/kxtjqgE.png b/website/images/kxtjqgE.png
new file mode 100644
index 00000000000..98d6c06b369
Binary files /dev/null and b/website/images/kxtjqgE.png differ
diff --git a/website/images/n16iOGk.png b/website/images/n16iOGk.png
new file mode 100644
index 00000000000..8a9be232fd9
Binary files /dev/null and b/website/images/n16iOGk.png differ
diff --git a/website/images/n41Azff.png b/website/images/n41Azff.png
new file mode 100644
index 00000000000..b4ed1a81b0f
Binary files /dev/null and b/website/images/n41Azff.png differ
diff --git a/website/images/rgSrvjG.png b/website/images/rgSrvjG.png
new file mode 100644
index 00000000000..dddfc503c3c
Binary files /dev/null and b/website/images/rgSrvjG.png differ
diff --git a/website/images/wU8x5Id.png b/website/images/wU8x5Id.png
new file mode 100644
index 00000000000..369a9c3a503
Binary files /dev/null and b/website/images/wU8x5Id.png differ
diff --git a/website/images/wXGqG5f.png b/website/images/wXGqG5f.png
new file mode 100644
index 00000000000..7184333c11b
Binary files /dev/null and b/website/images/wXGqG5f.png differ
diff --git a/website/images/yB5SYwm.png b/website/images/yB5SYwm.png
new file mode 100644
index 00000000000..6d494b4ddc2
Binary files /dev/null and b/website/images/yB5SYwm.png differ
diff --git a/website/images/yzDrJtA.jpg b/website/images/yzDrJtA.jpg
new file mode 100644
index 00000000000..f70ebd6504c
Binary files /dev/null and b/website/images/yzDrJtA.jpg differ
diff --git a/website/images/zdCAkB3.png b/website/images/zdCAkB3.png
new file mode 100644
index 00000000000..8df59582039
Binary files /dev/null and b/website/images/zdCAkB3.png differ
diff --git a/website/index.html b/website/index.html
new file mode 100644
index 00000000000..63ae3f409d7
--- /dev/null
+++ b/website/index.html
@@ -0,0 +1,149 @@
+
+
+
+
+
+ System Design Primer — Learn Large-Scale System Design
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Open Source • 250k+ Stars • Community Driven
+
+ The System Design
+ Primer
+
+
Learn how to design large-scale systems. Prep for the system design interview. An organized, interactive collection of resources to help you build systems at scale.
Why learn system design? Understanding how to build large-scale systems is one of the most valuable skills for any software engineer.
+
+
+
+
Learn How to Design Large-Scale Systems
+
Learning how to design scalable systems will help you become a better engineer.
+
System design is a broad topic. There are a vast number of resources scattered throughout the web on system design principles.
+
This course is an organized collection of resources to help you learn how to build systems at scale.
+
+
+
+
+ Key Insight
+
+
System design isn't just for interviews — it's the foundation of building reliable, scalable software in the real world.
+
+
+
Learn from the Open Source Community
+
This is a continually updated, open source project. Contributions are welcome! The best way to learn is by engaging with the community and seeing how real systems are built.
+
+
Prep for the System Design Interview
+
In addition to coding interviews, system design is a required component of the technical interview process at many tech companies.
+
Practice common system design interview questions and compare your results with sample solutions: discussions, code, and diagrams.
+
+
What You'll Learn
+
+
+
📚
+
Study Guide
+
A structured study plan for short, medium, and long timelines.
+
+
+
🎯
+
Interview Approach
+
Step-by-step methodology for tackling system design questions.
+
+
+
💡
+
Design Problems with Solutions
+
Real-world system design problems with detailed walkthroughs.
+
+
+
🏗️
+
Core Concepts
+
In-depth coverage of scalability, caching, databases, networking, and more.
+
+
+
+
Course Overview
+
This interactive course is organized into the following parts:
+
+
+
+
+
Part
Topics
Chapters
+
+
+
A
Getting Started
Motivation, Flashcards, Study Guide, Interview Approach
Ready to begin? Click "Mark Complete" above when you're done with this section, then move on to Chapter 02: Anki Flashcards. Use the sidebar to navigate between chapters anytime.
Use spaced repetition to retain key system design concepts. Anki flashcard decks are a proven study method for long-term knowledge retention.
+
+
+
+
+
+
Anki flashcard interface for system design study
+
+
+
Available Flashcard Decks
+
The provided Anki flashcard decks use spaced repetition to help you retain key system design concepts.
+
+
+
+
🃏
+
System Design Deck
+
Core system design concepts, trade-offs, and patterns in a flashcard format.
+
+
+
🏋️
+
System Design Exercises
+
Practice exercises to test your understanding of system design principles.
+
+
+
🎨
+
OO Design Deck
+
Object-oriented design concepts and exercises for interview prep.
+
+
+
+
How to Use Anki
+
+
Download Anki (free for desktop & Android, paid on iOS)
+
Import the .apkg flashcard deck files
+
Review cards daily — Anki will schedule cards based on spaced repetition
+
Rate your recall honestly (Again / Hard / Good / Easy) for effective scheduling
+
+
+
+
+
+ Tip: Great for On-the-Go Study
+
+
Anki flashcards are especially effective for studying during commutes, breaks, or any downtime. The mobile app syncs with the desktop version.
+
+
+
Coding Interview Resources
+
Looking for resources to help you prep for the Coding Interview? Check out the sister repo Interactive Coding Challenges, which contains an additional Anki deck for coding concepts.
Suggested topics to review based on your interview timeline. Adjust this guide based on your experience, target positions, and target companies.
+
+
+
+
+
+
Study guide overview — topics mapped to timelines
+
+
+
+
+
+ Do I need to know everything here?
+
+
No, you don't need to know everything here to prepare for the interview. What you are asked depends on your experience, technical background, target positions, and which companies you're interviewing with. Start broad and go deeper in a few areas.
+
+
+
Factors That Affect Your Prep
+
+
How much experience you have
+
What your technical background is
+
What positions you are interviewing for
+
Which companies you are interviewing with
+
A bit of luck 🍀
+
+
More experienced candidates are generally expected to know more about system design. Architects or team leads might be expected to know more than individual contributors. Top tech companies are likely to have one or more design interview rounds.
+
+
Timeline-Based Strategy
+
+
+
+
⚡
+
Short Timeline
+
Aim for breadth with system design topics. Practice by solving some interview questions.
+
+
+
📊
+
Medium Timeline
+
Aim for breadth and some depth with system design topics. Practice by solving many interview questions.
+
+
+
🎯
+
Long Timeline
+
Aim for breadth and more depth with system design topics. Practice by solving most interview questions.
+
+
+
+
Study Plan Matrix
+
+
+
+
Activity
Short
Medium
Long
+
+
+
+
Read through system design topics for a broad understanding
+
✅
✅
✅
+
+
+
Read company engineering blogs for your target companies
+
✅
✅
✅
+
+
+
Read through real world architectures
+
✅
✅
✅
+
+
+
Review "How to approach a system design interview question"
+
✅
✅
✅
+
+
+
Work through system design interview questions with solutions
+
Some
Many
Most
+
+
+
Work through OO design interview questions with solutions
+
Some
Many
Most
+
+
+
Review additional system design interview questions
+
Some
Many
Most
+
+
+
+
+
+
+
✅ Recommendation
+
Even on a short timeline, make sure to cover the core concepts (Part B) and at least 2-3 practice problems (Part F). Understanding trade-offs is more important than memorizing specific architectures.
How to Approach a System Design Interview Question
+
The system design interview is an open-ended conversation. You are expected to lead it. Use the following framework to guide the discussion systematically.
+
+
+
+
+
⚠️ Important
+
The system design interview is not about getting the "right" answer. It's about demonstrating your thought process, ability to make trade-offs, and capacity to design systems under constraints.
+
+
+
Step 1: Outline Use Cases, Constraints, and Assumptions
+
Gather requirements and scope the problem. Ask questions to clarify use cases and constraints. Discuss assumptions.
+
+
+
+
👥
+
Who uses it?
+
Identify the users and their roles in the system.
+
+
+
⚙️
+
What does it do?
+
Define the core functionality and behavior.
+
+
+
📊
+
How much scale?
+
Users, data volume, requests per second.
+
+
+
📈
+
Input/Output?
+
What goes in and what comes out of the system.
+
+
+
+
Key Questions to Ask
+
+
Who is going to use it?
+
How are they going to use it?
+
How many users are there?
+
What does the system do?
+
What are the inputs and outputs of the system?
+
How much data do we expect to handle?
+
How many requests per second do we expect?
+
What is the expected read to write ratio?
+
+
+
Step 2: Create a High-Level Design
+
Outline a high-level design with all important components.
+
+
Sketch the main components and connections
+
Justify your ideas and decisions
+
+
+
+
✅ Pro Tip
+
Draw a simple diagram with boxes and arrows. Start with the client, load balancer, web server, application server, database, and cache. Then add more detail as needed.
+
+
+
Step 3: Design Core Components
+
Dive into details for each core component. For example, if you were asked to design a URL shortening service, discuss:
+
+
Generating and storing a hash of the full URL
+
+
MD5 and Base62 hashing
+
Hash collisions
+
SQL or NoSQL
+
Database schema
+
+
+
Translating a hashed URL to the full URL
+
+
Database lookup
+
+
+
API and object-oriented design
+
+
+
Step 4: Scale the Design
+
Identify and address bottlenecks, given the constraints. Consider whether you need:
+
+
+
+
⚖️
+
Load Balancer
+
Distribute traffic across multiple servers for reliability.
+
+
+
↔️
+
Horizontal Scaling
+
Add more machines to handle increased load.
+
+
+
⚡
+
Caching
+
Reduce database load and improve response times.
+
+
+
🗄️
+
Database Sharding
+
Split data across multiple databases for scale.
+
+
+
+
Discuss potential solutions and trade-offs. Everything is a trade-off. Address bottlenecks using principles of scalable system design.
+
+
Back-of-the-Envelope Calculations
+
You might be asked to do some estimates by hand. Keep these references handy:
+
+
Use back-of-the-envelope calculations to evaluate different designs
+
Powers of two table (see Appendix)
+
Latency numbers every programmer should know (see Appendix)
+ `
+ }
+
+};
diff --git a/website/js/chapters/chapters-b.js b/website/js/chapters/chapters-b.js
new file mode 100644
index 00000000000..5ac611c554a
--- /dev/null
+++ b/website/js/chapters/chapters-b.js
@@ -0,0 +1,796 @@
+/**
+ * Part B: Core Concepts — Chapters 05–09
+ * Performance vs Scalability, Latency vs Throughput,
+ * Availability vs Consistency, Consistency Patterns, Availability Patterns
+ *
+ * Registered into global CHAPTERS_B object.
+ */
+
+const CHAPTERS_B = {
+
+ // ================================================================
+ // Chapter 05 — Performance vs Scalability
+ // ================================================================
+ 'performance-scalability': {
+ part: 'Part B',
+ title: 'Performance vs Scalability',
+ content: `
+
+
Chapter 05 — Core Concepts
+
Performance vs Scalability
+
Understanding the fundamental difference between performance and scalability — and why conflating them leads to flawed system designs.
+
+
+
+ 📊 Scalability Decision Flow
+ Flow
+
+
+
+flowchart TD
+ A["System is slow"] --> B{"Slow for single user?"}
+ B -->|Yes| C["Performance Problem"]
+ C --> D["Optimize code / algorithms"]
+ B -->|No| E{"Slow under load?"}
+ E -->|Yes| F["Scalability Problem"]
+ F --> G{"Stateless service?"}
+ G -->|Yes| H["Scale Horizontally"]
+ G -->|No| I["Scale Vertically"]
+
+
+
+
+
+
+
+
+ Core Principle
+
+
Everything is a trade-off. A service is scalable if it results in increased performance proportional to resources added.
+
+
+
What is Scalability?
+
A service is scalable if it results in increased performance in a manner proportional to resources added. Generally, increasing performance means serving more units of work, but it can also mean handling larger units of work, such as when datasets grow.
+
+
+
+
+
+
+
+
+
+
Performance Problem Slow
+
Your system is slow for a single user.
+
+
+
If you have a performance problem, your system is slow even for a single user. This might be caused by inefficient algorithms, unoptimized database queries, or poor resource utilization.
+
Fixing a performance problem means making the system faster for everyone, even without adding more hardware.
+
+
+
+
+
+
+
Scalability Problem Load
+
Your system is fast for one user but slow under heavy load.
+
+
+
If you have a scalability problem, your system performs well for a single user but degrades under heavy load. The system cannot handle growth in users, data, or traffic.
+
Fixing a scalability problem means the system can grow by adding resources (scaling up or out) proportionally.
+
+
+
+
+
+
Side-by-Side Comparison
+
+
+
+
+
Performance
+
+
+
+
How fast is each individual request?
+
Measured in latency (ms)
+
Improved by optimization
+
Better algorithms, caching, indexing
+
+
+
+
+
+
Scalability
+
+
+
+
How does the system handle growth?
+
Measured in throughput (req/s)
+
Improved by architecture
+
Horizontal scaling, sharding, replication
+
+
+
+
+
+
+
+
Key Takeaway
+
+
+
You can have a system with great performance that doesn't scale, and a system that scales well but has mediocre per-request performance. The best systems optimize both — but start by understanding which problem you're actually solving.
Two fundamental measures of system performance — learn when to optimize for each, and how they interact.
+
+
+
+ 📊 Latency vs Throughput
+ Flow
+
+
+
+flowchart LR
+ A["Request"] --> B["Queue Wait"]
+ B --> C["Processing"]
+ C --> D["Response"]
+ A -.->|"Latency = total time"| D
+ E["Throughput"] -.->|"Requests / second"| C
+
+
+
+
+
+
+
+
+ Core Principle
+
+
Generally, you should aim for maximal throughput with acceptable latency.
+
+
+
Definitions
+
+
+
+
+
Latency Time per Action
+
+
+
Latency is the time to perform some action or to produce some result. It is typically measured in milliseconds (ms) or microseconds (µs).
+
Examples: time for a database query to return, time for an API response, round-trip time of a network packet.
+
+
+
+
+
Throughput Actions per Time
+
+
+
Throughput is the number of such actions or results per unit of time. It is typically measured in requests per second (req/s), queries per second (QPS), or bits per second (bps).
+
Examples: web server handling 10,000 req/s, disk reading 500 MB/s, network transmitting 1 Gbps.
+
+
+
+
+
Side-by-Side Comparison
+
+
+
+
+
Latency
+
How long does one operation take?
+
+
+
+
Measured in ms or µs
+
Lower is better
+
Affected by network hops, disk I/O, processing time
+
Critical for real-time systems (gaming, trading)
+
Analogy: how fast a single car travels on a highway
+
+
+
+
+
+
Throughput
+
How many operations per second?
+
+
+
+
Measured in req/s, QPS, or bps
+
Higher is better
+
Affected by concurrency, bandwidth, parallelism
+
Critical for batch processing, data pipelines
+
Analogy: how many cars pass a point per hour
+
+
+
+
+
+
+
+
The Relationship
+
+
+
+
Latency and throughput are related but not inversely proportional in all cases. You can improve throughput (by adding parallelism) without reducing latency of individual requests. Conversely, reducing latency doesn't always increase throughput.
+
+
💡 Design Guideline
+
Aim for maximal throughput with acceptable latency. Don't sacrifice latency beyond what your users or SLAs can tolerate, but within that envelope, push throughput as high as possible.
The CAP theorem and the fundamental trade-off every distributed system must make.
+
+
+
+ 📊 CAP Theorem Decision Flow
+ Flow
+
+
+
+flowchart TD
+ A["Distributed System"] --> B{"Network Partition?"}
+ B -->|No partition| C["Can have C + A"]
+ B -->|Partition occurs| D{"Choose one:"}
+ D -->|Consistency| E["CP System"]
+ E --> F["Reject requests until partition heals"]
+ D -->|Availability| G["AP System"]
+ G --> H["Serve possibly stale data"]
+
In a distributed computer system, you can only support two of the following three guarantees simultaneously.
+
+
+
+
+
+
C Consistency
+
+
+
Every read receives the most recent write or an error.
+
+
+
+
+
A Availability
+
+
+
Every request receives a response, without guarantee it contains the most recent version.
+
+
+
+
+
P Partition Tolerance
+
+
+
The system continues to operate despite arbitrary network partitioning due to failures.
+
+
+
+
+
+
⚠️ Networks Aren't Reliable
+
Networks aren't reliable, so you'll always need to support partition tolerance. This means you must make a software trade-off between consistency and availability.
Sacrifice availability for correctness during partitions.
+
+
+
Waiting for a response from the partitioned node might result in a timeout error. CP is a good choice if your business needs require atomic reads and writes.
+
+
Guarantees every read returns the latest write
+
May return errors or timeouts during network partitions
+
Use cases: financial transactions, inventory management, distributed locks
+
+
+
+
+
+
+
+
AP — Availability & Partition Tolerance Always On
+
Sacrifice consistency for uptime during partitions.
+
+
+
Responses return the most readily available version of the data on any node, which might not be the latest. Writes might take some time to propagate when the partition is resolved.
+
AP is a good choice if the business needs to allow for eventual consistency or when the system needs to continue working despite external errors.
+
+
Always returns a response — even if stale
+
Data eventually converges across nodes
+
Use cases: social media feeds, shopping carts, DNS
With multiple copies of the same data, how do you synchronize them so clients have a consistent view?
+
+
+
+ 📊 Consistency Pattern Selection
+ Flow
+
+
+
+flowchart TD
+ A["Choose Consistency"] --> B{"Need real-time accuracy?"}
+ B -->|Yes| C["Strong Consistency"]
+ C --> D["RDBMS / Transactions"]
+ B -->|No| E{"Tolerate stale reads?"}
+ E -->|Briefly| F["Eventual Consistency"]
+ F --> G["DNS / Email / Async replication"]
+ E -->|Yes| H["Weak Consistency"]
+ H --> I["VoIP / Video chat / Gaming"]
+
+
+
+
+
+
+
+
+ Recall
+
+
From the CAP theorem: Consistency means every read receives the most recent write or an error. With multiple copies of data, we must choose how to synchronize them.
+
+
+
Three Consistency Patterns
+
+
+
+
+
+
+
+
+
+
+
+
Weak Consistency Best Effort
+
After a write, reads may or may not see it.
+
+
+
A best effort approach is taken. After a write, there is no guarantee that subsequent reads will return the updated value.
+
This approach is seen in systems such as memcached. Weak consistency works well in real-time use cases such as:
+
+
VoIP — if you lose reception, you don't hear what was spoken during the loss
+
Video chat — dropped frames are simply skipped
+
Realtime multiplayer games — brief desync is acceptable
+
+
+
+
+
+
+
+
+
Eventual Consistency Converges
+
After a write, reads will eventually see it (typically within milliseconds).
+
+
+
Data is replicated asynchronously. After a write completes, reads will eventually return the updated value — typically within milliseconds.
+
This approach is seen in systems such as:
+
+
DNS — record updates propagate over minutes/hours
+
Email — eventual delivery across servers
+
Highly available systems — where uptime is more critical than immediate consistency
+
+
+
+
+
+
+
+
+
Strong Consistency Guaranteed
+
After a write, reads will see it immediately.
+
+
+
Data is replicated synchronously. After a write completes, any subsequent read will return the updated value. This provides the strongest guarantees but at the cost of higher latency.
+
This approach is seen in systems such as:
+
+
File systems — writes are durable and immediately visible
+
RDBMSes — transactional guarantees (ACID)
+
Systems that need transactions — banking, inventory, booking
Two complementary patterns to support high availability: fail-over and replication — plus how to quantify availability in numbers.
+
+
+
+ 📊 Failover Architecture Flow
+ Flow
+
+
+
+flowchart TD
+ A["Active Server"] --> B["Heartbeat Monitor"]
+ C["Passive Server"] --> B
+ B --> D{"Active healthy?"}
+ D -->|Yes| E["Route traffic to Active"]
+ D -->|No| F["Promote Passive to Active"]
+ F --> G["DNS / VIP Failover"]
+ G --> E
+
+
+
+
+
+
+
+
+ Overview
+
+
There are two complementary patterns to support high availability: fail-over and replication.
+
+
+
+
Fail-over
+
+
+
+
+
+
+
+
+
+
+
Active-Passive Master-Slave
+
One active server handles traffic; a passive standby takes over on failure.
+
+
+
With active-passive fail-over, heartbeats are sent between the active and the passive server on standby. If the heartbeat is interrupted, the passive server takes over the active's IP address and resumes service.
+
The length of downtime is determined by whether the passive server is already running in 'hot' standby or whether it needs to start up from 'cold' standby.
+
Only the active server handles traffic. Active-passive failover can also be referred to as master-slave failover.
+
+
+
+
+
+
+
+
Active-Active Master-Master
+
Both servers manage traffic, spreading the load between them.
+
+
+
In active-active, both servers are managing traffic, spreading the load between them.
+
+
If the servers are public-facing, the DNS would need to know about the public IPs of both servers.
+
If the servers are internal-facing, application logic would need to know about both servers.
+
+
Active-active failover can also be referred to as master-master failover.
+
+
+
+
+
+
+
+
+ Disadvantages of Fail-over
+
+
+
+
Fail-over adds more hardware and additional complexity.
+
There is a potential for loss of data if the active system fails before any newly written data can be replicated to the passive.
+
+
+
+
+
+
+
+
Replication
+
+
+
+
Master-Slave & Master-Master Replication
+
Replication strategies are covered in depth in the Database section.
+
+
+
+
+
+
Master-Slave
+
+
+
Master serves reads and writes; replicates writes to one or more slaves. Slaves serve reads only. If the master goes offline, the system continues in read-only mode until a slave is promoted.
+
+
+
+
+
Master-Master
+
+
+
Both masters serve reads and writes, coordinating with each other. If either master goes down, the other continues with both reads and writes. Increases write availability but adds complexity.
+
+
+
+
+
+
+
+
+
+
Availability in Numbers
+
+
Availability is often quantified by uptime (or downtime) as a percentage of time the service is available. Availability is generally measured in number of 9s — a service with 99.99% availability is described as having four 9s.
+
+
+
+
+
+
+
+
+
+
+
+
+
Duration
+
Acceptable Downtime
+
+
+
+
+
Downtime per year
+
8h 45min 57s
+
+
+
Downtime per month
+
43m 49.7s
+
+
+
Downtime per week
+
10m 4.8s
+
+
+
Downtime per day
+
1m 26.4s
+
+
+
+
+
+
+
+
+
+
+
+
Duration
+
Acceptable Downtime
+
+
+
+
+
Downtime per year
+
52min 35.7s
+
+
+
Downtime per month
+
4m 23s
+
+
+
Downtime per week
+
1m 5s
+
+
+
Downtime per day
+
8.6s
+
+
+
+
+
+
+
+
+
+
+
Availability in Sequence vs in Parallel
+
+
If a service consists of multiple components prone to failure, the service's overall availability depends on whether the components are in sequence or in parallel.
+
+
+
+
+
In Sequence Decreases
+
Overall availability decreases when components are in sequence.
+
+
+
+ Availability(Total) = Avail(Foo) × Avail(Bar)
+
+
If both Foo and Bar each had 99.9% availability, their total availability in sequence would be 99.8%.
+
+
+
+
+
In Parallel Increases
+
Overall availability increases when components are in parallel.
If both Foo and Bar each had 99.9% availability, their total availability in parallel would be 99.9999%.
+
+
+
+
+
+
💡 Key Insight
+
Use redundancy (parallel components) to improve availability. Every serial dependency is a single point of failure that drags down your overall uptime.
How the internet's phonebook translates human-readable domain names into machine-routable IP addresses — and the trade-offs involved.
+
+
+
+ 🔄 DNS Resolution Flow
+ Sequence
+
+
+
+sequenceDiagram
+ participant Browser
+ participant Local as Local DNS Cache
+ participant Root as Root Server
+ participant TLD as TLD Server
+ participant Auth as Authoritative Server
+ Browser->>Local: Resolve example.com
+ alt Cache Hit
+ Local-->>Browser: Return cached IP
+ else Cache Miss
+ Local->>Root: Query root
+ Root-->>Local: Refer to .com TLD
+ Local->>TLD: Query .com
+ TLD-->>Local: Refer to authoritative
+ Local->>Auth: Query example.com
+ Auth-->>Local: Return IP address
+ Local-->>Browser: Return IP
+ end
+
A Domain Name System (DNS) translates a domain name such as www.example.com to an IP address. DNS is hierarchical, with a few authoritative servers at the top level.
+
+
+
How DNS Works
+
+
+
+
Hierarchical Resolution & Caching
+
+
+
DNS is hierarchical, with a few authoritative servers at the top level. Your router or ISP provides information about which DNS server(s) to contact when doing a lookup.
+
Lower level DNS servers cache mappings, which could become stale due to DNS propagation delays. DNS results can also be cached by your browser or OS for a certain period of time, determined by the time to live (TTL).
+
+
+
+
DNS Record Types
+
+
+
+
+
NS Name Server
+
+
+
Specifies the DNS servers for your domain/subdomain.
+
+
+
+
+
MX Mail Exchange
+
+
+
Specifies the mail servers for accepting messages.
+
+
+
+
+
A Address Record
+
+
+
Points a name to an IP address.
+
+
+
+
+
CNAME Canonical
+
+
+
Points a name to another name or CNAME (e.g. example.com to www.example.com) or to an A record.
+
+
+
+
+
Managed DNS & Traffic Routing
+
+
+
+
Managed DNS Services
+
Services such as CloudFlare and Route 53 provide managed DNS with advanced traffic routing.
+
+
+
+
+
+
+
+
+
+
+
+
+
Weighted Round Robin Flexible
+
+
+
Distributes traffic based on assigned weights. Useful for:
+
+
Prevent traffic from going to servers under maintenance
+
Balance between varying cluster sizes
+
A/B testing — send a percentage of users to a new version
+
+
+
+
+
+
+
+
+
Latency-Based Routing Performance
+
+
+
Routes users to the region that provides the lowest latency. AWS Route 53 measures latency between your users and AWS regions to determine the best endpoint.
+
+
+
+
+
+
+
+
Geolocation-Based Routing Location
+
+
+
Routes users based on their geographic location. Useful for content localization, regulatory compliance, and distributing load across regions.
+
+
+
+
+
+
+
+
+
+
Disadvantages
+
+
+
+
+ Disadvantages of DNS
+
+
+
+
Accessing a DNS server introduces a slight delay, although mitigated by caching.
+
DNS server management can be complex and is generally managed by governments, ISPs, and large companies.
+
DNS services have come under DDoS attack, preventing users from accessing websites without knowing the IP address(es) directly.
A content delivery network (CDN) is a globally distributed network of proxy servers, serving content from locations closer to the user. Generally, static files such as HTML/CSS/JS, photos, and videos are served from CDN, although some CDNs such as Amazon's CloudFront support dynamic content.
+
+
+
Why Use a CDN?
+
+
+
+
Performance Benefits
+
CDNs significantly improve performance in two key ways.
+
+
+
+
+
+
Proximity
+
+
+
Users receive content from data centers close to them, reducing latency and improving load times.
+
+
+
+
+
Offloading
+
+
+
Your servers do not have to serve requests that the CDN fulfills, freeing origin capacity.
+
+
+
+
+
+
+
+
+
Push vs Pull CDNs
+
+
+
+
+
+
+
+
+
+
+
Push CDN Upload on Change
+
You proactively upload content to the CDN when changes occur.
+
+
+
Push CDNs receive new content whenever changes occur on your server. You take full responsibility for providing content, uploading directly to the CDN and rewriting URLs to point to the CDN.
+
You can configure when content expires and when it is updated. Content is uploaded only when it is new or changed, minimizing traffic but maximizing storage.
+
+
💡 Best For
+
Sites with a small amount of traffic or sites with content that isn't often updated. Content is placed on the CDN once, instead of being re-pulled at regular intervals.
+
+
+
+
+
+
+
+
+
Pull CDN Lazy Fetch
+
CDN grabs content from your server on the first user request.
+
+
+
Pull CDNs grab new content from your server when the first user requests the content. You leave the content on your server and rewrite URLs to point to the CDN. This results in a slower first request until the content is cached on the CDN.
+
A time-to-live (TTL) determines how long content is cached. Pull CDNs minimize storage space on the CDN, but can create redundant traffic if files expire and are pulled before they have actually changed.
+
+
💡 Best For
+
Sites with heavy traffic, as traffic is spread out more evenly with only recently-requested content remaining on the CDN.
+
+
+
+
+
+
+
Comparison at a Glance
+
+
+
+
+
Push CDN
+
Proactive upload model
+
+
+
+
You upload content when it changes
+
Full control of expiry and updates
+
Minimizes traffic, maximizes storage
+
Best for low-traffic or rarely-updated sites
+
+
+
+
+
+
Pull CDN
+
On-demand fetch model
+
+
+
+
CDN fetches content on first request
+
TTL determines cache duration
+
Minimizes CDN storage, possible redundant pulls
+
Best for heavy-traffic sites
+
+
+
+
+
+
+
+
Disadvantages
+
+
+
+
+ Disadvantages of CDN
+
+
+
+
CDN costs could be significant depending on traffic, although this should be weighed with additional costs you would incur not using a CDN.
+
Content might be stale if it is updated before the TTL expires it.
+
CDNs require changing URLs for static content to point to the CDN.
Distributing incoming client requests across computing resources to improve reliability, eliminate single points of failure, and enable horizontal scaling.
+
+
+
+ 📊 Load Balancer Request Flow
+ Flow
+
+
+
+flowchart TD
+ A["Client Request"] --> B["Load Balancer"]
+ B --> C{"L4 or L7?"}
+ C -->|L4 Transport| D["Route by IP/Port"]
+ C -->|L7 Application| E["Route by URL/Headers"]
+ D --> F["Server Pool"]
+ E --> F
+ F --> G["Health Check"]
+ G -->|Healthy| H["Process Request"]
+ G -->|Unhealthy| I["Remove from Pool"]
+ H --> J["Response to Client"]
+
Load balancers distribute incoming client requests to computing resources such as application servers and databases. The load balancer returns the response from the computing resource to the appropriate client.
+
+
+
What Load Balancers Do
+
+
+
+
Effectiveness
+
Load balancers address three critical concerns.
+
+
+
+
+
+
Health
+
+
+
Preventing requests from going to unhealthy servers.
+
+
+
+
+
Load
+
+
+
Preventing overloading resources.
+
+
+
+
+
SPOF
+
+
+
Helping to eliminate a single point of failure.
+
+
+
+
+
+
+
Implementation & Benefits
+
+
+
+
Load balancers can be implemented with hardware (expensive) or with software such as HAProxy.
+
Additional benefits include:
+
+
SSL termination — Decrypt incoming requests and encrypt server responses so backend servers do not have to perform these potentially expensive operations. Removes the need to install X.509 certificates on each server.
+
Session persistence — Issue cookies and route a specific client's requests to the same instance if the web apps do not keep track of sessions.
+
+
+
💡 High Availability
+
To protect against failures, it's common to set up multiple load balancers, either in active-passive or active-active mode.
+
+
+
+
+
Routing Methods
+
+
+
+
+
+
Method
+
Description
+
+
+
+
+
Random
+
Distributes requests randomly across servers
+
+
+
Least Loaded
+
Sends requests to the server with the fewest active connections
+
+
+
Session / Cookies
+
Routes based on session affinity or cookie values
+
+
+
Round Robin
+
Cycles through servers sequentially (or weighted)
+
+
+
Layer 4
+
Transport layer — routes based on IP/ports
+
+
+
Layer 7
+
Application layer — routes based on headers, message, cookies
+
+
+
+
+
+
+
+
Layer 4 vs Layer 7 Load Balancing
+
+
+
+
+
+
+
+
+
+
+
Layer 4 Load Balancing Transport
+
Operates at the transport layer — fast and efficient.
+
+
+
Layer 4 load balancers look at info at the transport layer to decide how to distribute requests. Generally, this involves the source, destination IP addresses, and ports in the header, but not the contents of the packet.
+
Layer 4 load balancers forward network packets to and from the upstream server, performing Network Address Translation (NAT).
+
+
Less time and computing resources required
+
Less flexible — cannot inspect application data
+
Faster for high-throughput scenarios
+
+
+
+
+
+
+
+
+
Layer 7 Load Balancing Application
+
Operates at the application layer — flexible and content-aware.
+
+
+
Layer 7 load balancers look at the application layer to decide how to distribute requests. This can involve contents of the header, message, and cookies.
+
Layer 7 load balancers terminate network traffic, read the message, make a load-balancing decision, then open a connection to the selected server. For example, a layer 7 load balancer can direct video traffic to video servers while directing billing traffic to security-hardened servers.
+
+
More time and computing resources required
+
Highly flexible — can route based on content
+
Enables smart routing and content-based decisions
+
+
+
+
+
+
+
+
⚠️ Trade-Off
+
At the cost of flexibility, Layer 4 load balancing requires less time and computing resources than Layer 7, although the performance impact can be minimal on modern commodity hardware.
+
+
+
+
+
Horizontal Scaling
+
+
+
+
Scaling Out with Commodity Machines
+
+
+
Load balancers enable horizontal scaling, improving both performance and availability. Scaling out using commodity machines is more cost efficient and results in higher availability than scaling up a single server on more expensive hardware (Vertical Scaling).
+
It is also easier to hire for talent working on commodity hardware than it is for specialized enterprise systems.
+
+
+
+
+
+
+ Disadvantages of Horizontal Scaling
+
+
+
+
Scaling horizontally introduces complexity and involves cloning servers.
+
Servers should be stateless: they should not contain any user-related data like sessions or profile pictures.
+
Sessions can be stored in a centralized data store such as a database (SQL, NoSQL) or a persistent cache (Redis, Memcached).
+
Downstream servers such as caches and databases need to handle more simultaneous connections as upstream servers scale out.
+
+
+
+
+
+
+
Disadvantages
+
+
+
+
+ Disadvantages of Load Balancer
+
+
+
+
The load balancer can become a performance bottleneck if it does not have enough resources or is not configured properly.
+
Introducing a load balancer to eliminate a single point of failure results in increased complexity.
+
A single load balancer is a single point of failure; configuring multiple load balancers further increases complexity.
A web server that centralizes internal services and provides unified interfaces to the public — adding security, scalability, and caching in one layer.
+
+
+
+ 📊 Reverse Proxy Request Flow
+ Flow
+
+
+
+flowchart TD
+ A["Client"] --> B["Reverse Proxy"]
+ B --> C["SSL Termination"]
+ C --> D["Compression"]
+ D --> E{"Static content?"}
+ E -->|Yes| F["Serve from Cache"]
+ E -->|No| G["Forward to Backend"]
+ G --> H["Backend Server 1"]
+ G --> I["Backend Server 2"]
+ H --> J["Response"]
+ I --> J
+ J --> B
+ B --> A
+
A reverse proxy is a web server that centralizes internal services and provides unified interfaces to the public. Requests from clients are forwarded to a server that can fulfill it before the reverse proxy returns the server's response to the client.
+
+
+
Benefits
+
+
+
+
+
Security
+
+
+
Hide information about backend servers, blacklist IPs, limit number of connections per client.
+
+
+
+
+
Scalability
+
+
+
Clients only see the reverse proxy's IP, allowing you to scale servers or change their configuration transparently.
+
+
+
+
+
SSL Termination
+
+
+
Decrypt incoming requests and encrypt server responses so backend servers do not have to perform these expensive operations. Removes the need to install X.509 certificates on each server.
+
+
+
+
+
Compression
+
+
+
Compress server responses to reduce bandwidth usage and improve transfer speed.
+
+
+
+
+
+
+
Additional Capabilities
+
+
+
+
+
+
Caching
+
+
+
Return the response for cached requests without hitting the origin server.
+
+
+
+
+
Static Content
+
+
+
Serve static content directly — HTML/CSS/JS, photos, videos, etc.
+
+
+
+
+
+
+
+
+
Load Balancer vs Reverse Proxy
+
+
+
+
+
Load Balancer
+
Multiple servers required
+
+
+
+
Useful when you have multiple servers
+
Routes traffic to a set of servers serving the same function
+
Primary goal: distribute load
+
+
+
+
+
+
Reverse Proxy
+
Even one server benefits
+
+
+
+
Useful even with just one web server or application server
Separating the web layer from the application (platform) layer allows you to scale and configure both independently — a cornerstone of resilient system design.
+
+
+
+ 📊 Application Layer Request Flow
+ Flow
+
+
+
+flowchart TD
+ A["Client"] --> B["Web Layer"]
+ B --> C["Application Layer"]
+ C --> D{"Request type"}
+ D -->|Read| E["Cache Layer"]
+ D -->|Write| F["Message Queue"]
+ E -->|Miss| G["Database"]
+ F --> H["Worker Service"]
+ H --> G
+ E -->|Hit| I["Return Response"]
+ G --> I
+
Separating out the web layer from the application layer (also known as the platform layer) allows you to scale and configure both layers independently. Adding a new API results in adding application servers without necessarily adding additional web servers. The single responsibility principle advocates for small and autonomous services that work together.
+
+
+
Why Separate the Layers?
+
+
+
+
Independent Scaling
+
Scale each layer according to its own demand profile.
+
+
+
+
Adding a new API results in adding application servers without necessarily adding additional web servers.
+
The single responsibility principle advocates for small and autonomous services that work together.
+
Workers in the application layer also enable asynchronism — processing tasks from message queues independently.
+
+
+
+
+
+
+
Key Concepts
+
+
+
+
+
+
+
+
+
+
+
Microservices Architecture
+
A suite of independently deployable, small, modular services.
+
+
+
Each service runs a unique process and communicates through a well-defined, lightweight mechanism to serve a business goal.
+
+
💡 Example — Pinterest
+
Pinterest could have the following microservices: user profile, follower, feed, search, photo upload, etc. Each can be developed, deployed, and scaled independently.
+
+
+
+
+
+
+
+
+
Service Discovery Operations
+
Finding and connecting to services based on registered names, addresses, and ports.
+
+
+
Systems such as Consul, Etcd, and Zookeeper can help services find each other by tracking registered names, addresses, and ports. Health checks help verify service integrity and are often done using an HTTP endpoint.
+
+
+
+
Consul
+
+
+
Service mesh, health checking, key/value store by HashiCorp.
+
+
+
+
+
Etcd
+
+
+
Distributed key-value store for shared configuration and service discovery.
+
+
+
+
+
Zookeeper
+
+
+
Centralized service for maintaining configuration, naming, synchronization, and group services.
+
+
+
+
+
+
+
+
+
+
+
Disadvantages
+
+
+
+
+ Disadvantages of the Application Layer
+
+
+
+
Adding an application layer with loosely coupled services requires a different approach from an architectural, operations, and process viewpoint (vs a monolithic system).
+
Microservices can add complexity in terms of deployments and operations.
A comprehensive guide to relational and non-relational databases, replication strategies, sharding, denormalization, SQL tuning, and choosing between SQL and NoSQL.
+
+
+
+ 📊 SQL vs NoSQL Decision Flow
+ Flow
+
+
+
+flowchart TD
+ A["Choose Database"] --> B{"Structured with relationships?"}
+ B -->|Yes| C{"Need ACID transactions?"}
+ C -->|Yes| D["Relational DB / SQL"]
+ C -->|No| E["Consider NoSQL"]
+ B -->|No| F{"Data pattern?"}
+ F -->|Key-Value| G["Redis / DynamoDB"]
+ F -->|Document| H["MongoDB / CouchDB"]
+ F -->|Wide Column| I["Cassandra / HBase"]
+ F -->|Graph| J["Neo4j"]
+
Databases are at the heart of most systems. Choosing the right database technology, replication strategy, and scaling mechanism is critical for building reliable, performant applications.
+
+
+
+
+
+
Database Types
+
+
+
+
+
+
+
+
+
+
+
+
Relational Database Management System (RDBMS) SQL
+
A collection of data items organized in tables with ACID guarantees.
+
+
+
ACID Properties
+
+
+
+
A Atomicity
+
+
Each transaction is all or nothing.
+
+
+
+
C Consistency
+
+
Any transaction will bring the database from one valid state to another.
+
+
+
+
I Isolation
+
+
Executing transactions concurrently has the same results as if they were executed serially.
+
+
+
+
D Durability
+
+
Once a transaction has been committed, it will remain so.
+
+
+
+
There are many techniques to scale a relational database: master-slave replication, master-master replication, federation, sharding, denormalization, and SQL tuning.
+
+
+
+
+
+
+
+
+
NoSQL Non-Relational
+
A collection of data items represented in a key-value store, document store, wide column store, or graph database. Data is denormalized and joins are generally done in application code.
+
+
+
BASE Properties
+
+
💡 BASE vs ACID
+
Most NoSQL stores lack true ACID transactions and favor eventual consistency.
+
+
+
Basically available — the system guarantees availability.
+
Soft state — the state of the system may change over time, even without input.
+
Eventual consistency — the system will become consistent over a period of time, given no new input.
+
+
+
+
+
NoSQL Store Types
+
+
+
+
+
+
+
+
+
+
+
+
+
Key-Value Store O(1)
+
+
+
Abstraction: hash table. Allows for O(1) reads and writes and is often backed by memory or SSD. Data stores can maintain keys in lexicographic order, allowing efficient retrieval of key ranges. Key-value stores provide high performance and are often used for simple data models or rapidly-changing data, such as an in-memory cache layer.
+
Examples: Redis, Memcached.
+
+
+
+
+
+
+
+
Document Store Flexible
+
+
+
Abstraction: key-value store with documents stored as values. Documents are organized by collections, tags, metadata, or directories. Although documents can be organized or grouped together, documents may have fields that are completely different from each other.
+
Some document stores like MongoDB and CouchDB also provide a SQL-like language to perform complex queries. DynamoDB supports both key-values and documents.
+
Documents can be stored in XML, JSON, binary, etc.
+
+
+
+
+
+
+
+
Wide Column Store Columnar
+
+
+
+
+
Source: SQL & NoSQL, a brief history
+
+
Abstraction: nested map ColumnFamily<RowKey, Columns<ColKey, Value, Timestamp>>. Each column family can be thought of as a set of rows that share the same set of columns. A basic unit of data is a column (name/value pair). Columns can be grouped in column families. Super column families further group column families.
+
Wide column stores offer high availability and high scalability, often used for very large data sets.
+
Examples: Bigtable, HBase, Cassandra.
+
+
+
+
+
+
+
+
Graph Database Relationships
+
+
+
+
+
Source: Graph database
+
+
Abstraction: graph. In a graph database, each node is a record and each arc is a relationship between two nodes. Graph databases are optimized to represent complex relationships with many foreign keys or many-to-many relationships.
+
Graphs databases offer high performance for data models with complex relationships, such as social networks.
The master serves reads and writes, replicating writes to one or more slaves, which serve only reads. Slaves can also replicate to additional slaves in a tree-like fashion. If the master goes offline, the system can continue to operate in read-only mode until a slave is promoted to a master or a new master is provisioned.
+
+
⚠️ Disadvantage
+
Additional logic is needed to promote a slave to a master.
Both masters serve reads and writes and coordinate with each other on writes. If either master goes down, the other master can continue to operate with both reads and writes.
+
+
⚠️ Disadvantages
+
+
You'll need a load balancer or you'll need to make changes to your application logic to determine where to write.
+
Most master-master systems are either loosely consistent (violating ACID) or have increased write latency due to synchronization.
+
Conflict resolution comes more into play as more write nodes are added and as latency increases.
+
+
+
+
+
+
+
+
+
💡 Replication Disadvantages (General)
+
+
There is potential for loss of data if the master fails before newly written data can be replicated.
+
Writes are replayed to the read replicas — if there are a lot of writes, the read replicas can get bogged down and can't do as many reads.
+
The more read slaves, the more you have to replicate, which leads to greater replication lag.
+
Replication adds more hardware and additional complexity.
+
+
+
+
+
+
+
+
+
Scaling Techniques
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Federation (Functional Partitioning) Split by Function
Federation (or functional partitioning) splits up databases by function. For example, instead of a single, monolithic database, you could have three databases: forums, users, and products, resulting in less read and write traffic to each database and therefore less replication lag.
+
Smaller databases result in more data that can fit in memory, which in turn results in more cache hits due to the improved cache locality. With no single central master serializing writes, you can write in parallel, increasing throughput.
+
+
+
⚠️ Disadvantages
+
+
Federation is not effective if your schema requires huge functions or tables.
+
You'll need to update your application logic to determine which database to read and write.
+
Joining data from two databases is more complex with a server link.
+
Federation adds more hardware and additional complexity.
Sharding distributes data across different databases such that each database can only manage a subset of the data. Taking a users database as an example, as the number of users increases, more shards are added to the cluster.
+
Similar to the advantages of federation, sharding results in less read and write traffic, less replication, and more cache hits. Index size also goes down, which generally improves performance with faster queries. If one shard goes down, the other shards are still operational. There is no single central master serializing writes, allowing you to write in parallel with increased throughput.
+
+
+
⚠️ Disadvantages
+
+
You'll need to update your application logic to work with shards, which could result in complex SQL queries.
+
Data distribution can become lopsided in a shard. For example, a set of power users on a shard could result in increased load to that shard compared to others.
+
Joining data from multiple shards is more complex.
+
Sharding adds more hardware and additional complexity.
+
+
+
+
+
+
+
+
+
+
+
Denormalization Read Optimized
+
+
+
Denormalization attempts to improve read performance at the expense of some write performance. Redundant copies of the data are written in multiple tables to avoid expensive joins. Some RDBMS such as PostgreSQL and Oracle support materialized views which handle the work of storing redundant information and keeping redundant copies consistent.
+
Once data becomes distributed with techniques such as federation and sharding, managing joins across data centers further increases complexity. Denormalization might circumvent the need for such complex joins.
+
+
+
💡 When to Denormalize
+
In most systems, reads can heavily outnumber writes 100:1 or even 1000:1. A read resulting in a complex database join can be very expensive, spending a significant amount of time on disk operations.
+
+
+
+
⚠️ Disadvantages
+
+
Data is duplicated.
+
Constraints can help redundant copies of information stay in sync, which increases complexity of the database design.
+
A denormalized database under heavy write load might perform worse than its normalized counterpart.
+
+
+
+
+
+
+
+
+
+
+
SQL Tuning Optimization
+
+
+
SQL tuning is a broad topic and many books have been written as reference. It's important to benchmark and profile to simulate and uncover bottlenecks.
+
+
+
+
+
Benchmark
+
+
+
Simulate high-load situations with tools such as ab (ApacheBench).
+
+
+
+
+
Profile
+
+
+
Enable tools such as the slow query log to help track performance issues.
+
+
+
+
+
Optimization Tips
+
+
+
+
+
Technique
+
Details
+
+
+
+
+
Tighten up the schema
+
Use CHAR instead of VARCHAR for fixed-length fields. TEXT for large blocks of text. INT for numbers up to 2^32. DECIMAL for currency to avoid floating point errors. Avoid storing large BLOBS — store the location instead. VARCHAR(255) is the max number of chars that can be counted in an 8-bit number. Set the NOT NULL constraint where applicable.
+
+
+
Use good indices
+
Columns that you are querying (SELECT, GROUP BY, ORDER BY, JOIN) could be faster with indices. Indices are usually represented as self-balancing B-tree that keeps data sorted and allows searches, sequential access, insertions, and deletions in logarithmic time.
+
+
+
Avoid expensive joins
+
Denormalize where performance demands it.
+
+
+
Partition tables
+
Break up a table by putting hot spots in a separate table to help keep it in memory.
+
+
+
Tune the query cache
+
In some cases, the query cache could lead to performance issues.
Caching improves page load times and reduces the load on your servers and databases — but cache invalidation is one of the hardest problems in computer science.
+
+
+
+ 🔄 Caching Strategy Flow
+ Sequence
+
+
+
+sequenceDiagram
+ participant App
+ participant Cache
+ participant DB
+ Note over App,DB: Cache-Aside Pattern
+ App->>Cache: Read key
+ alt Cache Hit
+ Cache-->>App: Return value
+ else Cache Miss
+ App->>DB: Query database
+ DB-->>App: Return value
+ App->>Cache: Store in cache
+ end
+ Note over App,DB: Write-Through Pattern
+ App->>Cache: Write key
+ Cache->>DB: Write to database
+ DB-->>Cache: Confirm
+ Cache-->>App: Confirm
+
Caching improves page load times and can reduce the load on your servers and databases. In this model, the dispatcher will first look up if the request has been made before and try to find the previous result to return, in order to save the actual execution.
+
+
+
Where to Cache
+
+
Caches can be located at multiple levels in your architecture in order to serve previously computed results.
+
+
+
+
+
Client
+
+
+
Caching can be located on the client side (OS or browser), server side, or in a distinct cache layer.
+
+
+
+
+
CDN
+
+
+
CDNs are considered a type of cache.
+
+
+
+
+
Web Server
+
+
+
Reverse proxies and caches such as Varnish can serve static and dynamic content directly. Web servers can also cache requests, returning responses without having to contact application servers.
+
+
+
+
+
Database
+
+
+
Your database usually includes some level of caching in a default configuration, optimized for a generic use case.
+
+
+
+
+
+
+
Application Caching In-Memory
+
In-memory caches such as Memcached and Redis are key-value stores between your application and your data storage.
+
+
+
Since the data is held in RAM, it is much faster than typical databases where data is stored on disk. RAM is more limited than disk, so cache invalidation algorithms such as least recently used (LRU) can help invalidate "cold" entries and keep "hot" data in memory.
+
+
💡 Redis Features
+
Redis has the following additional features: persistence option, built-in data structures such as sorted sets and lists.
+
+
What to Cache
+
There are multiple levels of caching available depending on granularity:
+
+
Row level
+
Query-level caching
+
Fully-formed serializable objects
+
Fully-rendered HTML
+
+
+
+
+
+
+
Caching Strategies
+
+
+
+
+
+
+
+
+
+
+
Caching at the Database Query Level
+
+
+
Whenever you query the database, hash the query as a key and store the result to the cache. This approach suffers from expiration issues:
+
+
Hard to delete a cached result with complex queries.
+
If one piece of data changes (e.g. a table cell), you need to delete all cached queries that might include the changed cell — cascading invalidation.
+
+
+
+
+
+
+
+
+
Caching at the Object Level Recommended
+
+
+
See your data as an object, similar to what you do with your application code. Have your application assemble the dataset from the database into a class instance or a data structure. Remove the object from cache if its underlying data has changed. Allows for asynchronous processing: workers assemble objects by consuming the latest cached object.
The application is responsible for reading and writing from storage. The cache does not interact with storage directly. The application does the following:
+
+
Look for entry in cache, resulting in a cache miss
+
Load entry from the database
+
Add entry to cache
+
Return entry
+
+
+
⚠️ Disadvantages
+
+
Each cache miss results in three trips — which can cause a noticeable delay.
+
Data can become stale if it is updated in the database. This issue is mitigated by setting a time-to-live (TTL) which forces an update of the cache entry, or by using write-through.
+
When a node fails, it is replaced by a new, empty node, increasing latency.
The application uses the cache as the main data store, reading and writing data to it, while the cache is responsible for reading and writing to the database.
+
+
Application adds/updates entry in cache
+
Cache synchronously writes entry to data store
+
Return
+
+
Write-through is a slow overall operation due to the write operation, but subsequent reads of just written data are fast. Users are generally more tolerant of latency when updating data than reading data. Data in the cache is not stale.
+
+
⚠️ Disadvantages
+
+
When a new node is created due to failure or scaling, the new node will not cache entries until the entry is updated in the database. Cache-aside in conjunction with write through can mitigate this issue.
+
Most data written might never be read, which can be minimized with a TTL.
You can configure the cache to automatically refresh any recently accessed cache entry prior to its expiration. Refresh-ahead can result in reduced latency vs read-through if the cache can accurately predict which items are likely to be needed in the future.
+
+
⚠️ Disadvantages
+
Not accurately predicting which items are likely to be needed in the future can result in reduced performance than without refresh-ahead.
+
+
+
+
+
+
+
+
+
Disadvantages
+
+
+
+
+ General Cache Disadvantages
+
+
+
+
Need to maintain consistency between caches and the source of truth such as the database through cache invalidation.
+
Cache invalidation is a difficult problem — there is additional complexity associated with when to update the cache.
+
Need to make application changes such as adding Redis or memcached.
Asynchronous workflows help reduce request times for expensive operations that would otherwise be performed in-line — improving responsiveness and enabling scale.
Asynchronous workflows help reduce request times for expensive operations that would otherwise be performed in-line. They can also help by doing time-consuming work in advance, such as periodic aggregation of data.
+
+
+
Async Patterns
+
+
+
+
+
+
+
+
+
+
+
+
+
Message Queues Decoupling
+
Receive, hold, and deliver messages. The user is not blocked and the job is processed in the background.
+
+
+
Message queues receive, hold, and deliver messages. If an operation is too slow to perform inline, you can use a message queue with the following workflow:
+
+
An application publishes a job to the queue, then notifies the user of job status
+
A worker picks up the job from the queue, processes it, then signals the job is complete
+
+
The user is not blocked and the job is processed in the background. During this time, the client might optionally do a small amount of processing to make it seem like the task has completed.
+
+
Popular Implementations
+
+
+
+
Redis
+
+
+
Useful as a simple message broker but messages can be lost.
+
+
+
+
+
RabbitMQ
+
+
+
Popular, mature, supports AMQP protocol. Requires you to adapt to the "AMQP" protocol and manage your own nodes.
+
+
+
+
+
Amazon SQS
+
+
+
Hosted — but can have high latency and has the possibility of messages being delivered twice.
+
+
+
+
+
+
+
+
+
+
+
+
Task Queues Compute
+
Receive tasks and related data, run them, then deliver their results.
+
+
+
Tasks queues receive tasks and their related data, runs them, then delivers their results. They can support scheduling and can be used to run computationally-intensive jobs in the background.
+
+
💡 Python — Celery
+
Celery has support for scheduling and primarily has python support.
+
+
+
+
+
+
+
+
+
+
Back Pressure Flow Control
+
Limit queue sizes to maintain high throughput rates and good response times for jobs already in the queue.
+
+
+
If queues start to grow significantly, the queue size can become larger than memory, resulting in cache misses, disk reads, and even slower performance. Back pressure can help by limiting the queue size, thereby maintaining a high throughput rate and good response times for jobs already in the queue.
+
Once the queue fills up, clients get a server busy or HTTP 503 status code to try again later. Clients can retry the request at a later time, perhaps with exponential backoff.
+
+
+
+
+
+
+
+
Disadvantages
+
+
+
+
+ Disadvantages of Asynchronism
+
+
+
+
Use cases such as inexpensive calculations and real-time workflows might be better suited for synchronous operations, as introducing queues can add delays and complexity.
How systems talk to each other — from low-level transport protocols to high-level architectural styles like REST and RPC.
+
+
+
+
+
+
OSI 7 Layer Model
+
+
+
+
+
Hypertext Transfer Protocol (HTTP)
+
+
+
+
+ Application Layer Protocol
+
+
HTTP is a method for encoding and transporting data between a client and a server. It is a request/response protocol: clients issue requests and servers issue responses with relevant content and completion status info about the request.
+
+
+
+
+
How HTTP Works
+
Self-contained request/response protocol relying on lower-level TCP and UDP.
+
+
+
HTTP is self-contained, allowing requests and responses to flow through many intermediate routers and servers that perform load balancing, caching, encryption, and compression.
+
A basic HTTP request consists of a verb (method) and a resource (endpoint). HTTP is an application layer protocol relying on lower-level protocols such as TCP and UDP.
+
+
+
+
HTTP Verbs
+
+
+
+
+
+
Verb
+
Description
+
Idempotent*
+
Safe
+
Cacheable
+
+
+
+
+
GET
+
Reads a resource
+
Yes
+
Yes
+
Yes
+
+
+
POST
+
Creates a resource or triggers a process
+
No
+
No
+
Yes if response contains freshness info
+
+
+
PUT
+
Creates or replaces a resource
+
Yes
+
No
+
No
+
+
+
PATCH
+
Partially updates a resource
+
No
+
No
+
Yes if response contains freshness info
+
+
+
DELETE
+
Deletes a resource
+
Yes
+
No
+
No
+
+
+
+
* Can be called many times without different outcomes.
+
+
+
+
+
+
+
Transport Protocols: TCP vs UDP
+
+
+
+
+
+
+
+
+
+
+
+
TCP — Transmission Control Protocol Reliable
+
Connection-oriented protocol over an IP network. Guarantees delivery, order, and integrity.
+
+
+
+
+
TCP connection handshake
+
+
+
TCP is a connection-oriented protocol over an IP network. Connection is established and terminated using a handshake.
+
All packets sent are guaranteed to reach the destination in the original order and without corruption through:
+
+
Sequence numbers and checksum fields for each packet
+
Acknowledgement packets and automatic retransmission
+
+
If the sender does not receive a correct response, it will resend the packets. If there are multiple timeouts, the connection is dropped.
+
TCP also implements flow control and congestion control. These guarantees cause delays and generally result in less efficient transmission than UDP.
+
+
+
⚠️ Resource Usage
+
To ensure high throughput, web servers can keep a large number of TCP connections open, resulting in high memory usage. Connection pooling can help mitigate this, as well as switching to UDP where applicable.
+
+
+
Use TCP When You Need:
+
+
All of the data to arrive intact
+
The best automatic estimation of network throughput
+
+
+
Common Use Cases:
+
+
+
+ Web Servers
+
+
+
+
+ Database
+
+
+
+
+ SMTP / FTP / SSH
+
+
+
+
+
+
+
+
+
+
+
+
UDP — User Datagram Protocol Fast
+
Connectionless protocol. Datagrams may arrive out of order or not at all — but it's very fast.
+
+
+
+
+
UDP datagram structure
+
+
+
UDP is connectionless. Datagrams (analogous to packets) are guaranteed only at the datagram level. Datagrams might reach their destination out of order or not at all.
+
UDP does not support congestion control. Without the guarantees that TCP support, UDP is generally more efficient.
+
UDP can broadcast, sending datagrams to all devices on the subnet. This is useful with DHCP because the client has not yet received an IP address, thus preventing a way for TCP to stream without the IP address.
+
+
+
💡 When to Use UDP
+
Use UDP when you need the lowest latency, when late data is worse than loss of data, or when you want to implement your own error correction.
+
+
+
Use UDP When You Need:
+
+
The lowest latency
+
Late data is worse than loss of data
+
You want to implement your own error correction
+
+
+
Common Use Cases:
+
+
+
+ VoIP / Video Chat
+
+
+
+
+ Streaming Media
+
+
+
+
+ Realtime Multiplayer Games
+
+
+
+
+ DHCP / DNS (lookups)
+
+
+
+
+
+
+
+
+
TCP vs UDP — Side-by-Side
+
+
+
+
+
TCP Reliable & Ordered
+
+
+
+
Connection-oriented (handshake)
+
Guaranteed delivery & ordering
+
Flow control & congestion control
+
Higher latency; higher overhead
+
Best for: web, DB, email, file transfer
+
+
+
+
+
+
UDP Fast & Lightweight
+
+
+
+
Connectionless (no handshake)
+
No delivery or ordering guarantees
+
No congestion control
+
Lower latency; lower overhead
+
Best for: streaming, gaming, VoIP
+
+
+
+
+
+
+
+
+
+
Remote Procedure Call (RPC)
+
+
+
+
RPC Overview Behavior-Focused
+
A client causes a procedure to execute on a different address space (remote server), coded as if it were a local procedure call.
+
+
+
+
+
RPC request/response flow
+
+
+
In an RPC, a client causes a procedure to execute on a different address space, usually a remote server. The procedure is coded as if it were a local procedure call, abstracting away the details of how to communicate with the server from the client program.
+
Remote calls are usually slower and less reliable than local calls, so it is helpful to distinguish them. Popular RPC frameworks include Protobuf, Thrift, and Avro.
+
+
RPC Call Flow
+
+
+
+
Client program — calls the client stub procedure
+
Client stub procedure — marshals (packs) the procedure ID and arguments into a request message
+
Client communication module — OS sends the message from the client to the server
+
Server communication module — OS passes the incoming packets to the server stub procedure
+
Server stub procedure — unmarshalls the results, calls the server procedure matching the procedure ID
+
Server procedure — executes and returns result to the server stub, which reverses the process back to the client
+
+
+
+
+
RPC is focused on exposing behaviors. RPCs are often used for performance reasons with internal communications, as you can hand-craft native calls to better fit your use cases.
+
+
+
💡 Choose a Native Library (SDK) When:
+
+
You know your target platform
+
You want to control how your "logic" is accessed
+
You want to control how error control happens off your library
+
Performance and end user experience is your primary concern
+
+
+
+
+
+
Disadvantages of RPC
+
+
+
+
+ RPC Trade-Offs
+
+
+
+
RPC clients become tightly coupled to the service implementation
+
A new API must be defined for every new operation or use case
+
It can be difficult to debug RPC calls
+
You might not be able to leverage existing technologies out of the box — for example, it might require additional effort to ensure RPC calls are properly cached on caching servers such as Squid
+
+
+
+
+
+
+
+
+
Representational State Transfer (REST)
+
+
+
+
REST Overview Data-Focused
+
An architectural style enforcing a client/server model where the client acts on a set of resources managed by the server.
+
+
+
REST is an architectural style enforcing a client/server model where the client acts on a set of resources managed by the server. The server provides a representation of resources and actions that can either manipulate or get a new representation of resources. All communication must be stateless and cacheable.
+
+
Four Qualities of a RESTful Interface
+
+
+
+
1 Identify Resources
+
+
+
Identify resources via URI — use the same URI regardless of any operation.
+
+
+
+
+
2 Change with Representations
+
+
+
Change with representations — use verbs (GET, POST, PUT, DELETE, etc.).
+
+
+
+
+
3 Self-Descriptive Messages
+
+
+
Self-descriptive error messages — use status codes; don't reinvent the wheel.
+
+
+
+
+
4 HATEOAS
+
+
+
Hypermedia as the Engine of Application State — the response should include links to related resources.
+
+
+
+
+
REST is focused on exposing data. It minimizes the coupling between client/server and is often used for public HTTP APIs. REST excels at horizontal scaling and partitioning.
+
+
+
+
Disadvantages of REST
+
+
+
+
+ REST Trade-Offs
+
+
+
+
REST may not be well-suited for resources that are not naturally organized in a simple hierarchy
+
The limited set of verbs may not fit every use case, and some actions are awkward to model as CRUD
+
Fetching complicated resources with nested hierarchies requires multiple round trips between the client and server
+
Over time, more fields might be added to an API response, leading to payload bloat — older clients receiving data they don't need
Security is a broad topic. Unless you have considerable experience, a security background, or are applying for a position that requires knowledge of security, you probably don't need to know more than the basics.
+
+
+
+
+
+
+ Security Essentials
+
+
Security is a broad topic. Unless you have considerable experience, a security background, or are applying for a position that requires knowledge of security, you probably don't need to know more than the basics.
+
+
+
Core Security Principles
+
+
+
+
+
1 Encrypt in Transit & at Rest
+
+
+
+
+
+ Encryption
+
+
Encrypt data in transit (TLS/SSL for network communication) and at rest (encrypt databases, file storage, backups). This protects sensitive data from interception and unauthorized access.
+
+
+
+
+
+
+
2 Sanitize All User Inputs
+
+
+
+
+
+ Input Sanitization
+
+
Sanitize all user inputs or any input parameters exposed to the user to prevent XSS (Cross-Site Scripting) and SQL injection attacks. Never trust user input.
+
+
+
+
+
+
+
3 Parameterized Queries
+
+
+
+
+
+ SQL Injection Prevention
+
+
Use parameterized queries to prevent SQL injection. Never build SQL queries by concatenating user-supplied strings — use prepared statements or an ORM's built-in protections.
+
+
+
+
+
+
+
4 Principle of Least Privilege
+
+
+
+
+
+ Least Privilege
+
+
Apply the principle of least privilege. Every module, process, or user should only have access to the information and resources necessary for its legitimate purpose. Minimize the blast radius of any potential breach.
+
+
+
+
+
+
+
+
Quick Reference
+
+
+
+
Security Checklist
+
A concise summary of the key security practices every system designer should follow.
Design a URL shortening service like Pastebin or Bit.ly — where users enter text or a URL and receive a randomly generated short link that redirects to the original content.
+
+
+
+
+
+
High-level architecture for a Pastebin / URL shortening service
+
+
+
+
+
+ Interview Tip
+
+
Design Bit.ly is a similar question, except Pastebin requires storing the paste contents instead of the original unshortened URL. Both fundamentally involve generating and storing a hash of the full URL.
+
+
+
Use Cases & Constraints
+
+
+
+
+
Use Case
Description
+
+
+
Create paste
User enters a block of text and gets a randomly generated short link (with optional expiration)
+
View paste
User enters a paste's short URL and views the contents
+
Analytics
Service tracks monthly visit stats for pages
+
Expiration
Service deletes expired pastes; default is no expiration
+
High availability
Service remains available even during partial failures
+
+
+
+
+
+
+
Back-of-the-Envelope Estimates
+
Key numbers to ground the design
+
+
+
+
10 million users, 10 million paste writes/month, 100 million reads/month
+
10:1 read-to-write ratio — read-heavy system
+
~1 KB content per paste → 12.7 GB new content per month
+
~4 writes/sec average, ~40 reads/sec average
+
+
+
+
+
+
+ 📊 Request Flow — Read & Write Paths
+ Flow
+
+
+
+flowchart TD
+ A["Client"] --> B["Web Server"]
+ B --> C{"Write or Read?"}
+ C -->|Write| D["Write API"]
+ D --> E["Generate short URL"]
+ E --> F["Store in SQL DB"]
+ E --> G["Store content in Object Store"]
+ C -->|Read| H["Read API"]
+ H --> I["Check Cache"]
+ I -->|Hit| J["Return content"]
+ I -->|Miss| K["Query SQL DB"]
+ K --> L["Fetch from Object Store"]
+ L --> J
+
+
+
+
Key Design Decisions
+
+
+
+
+
Hash Generation
+
+
+
Generate a unique short URL using MD5 hash → take the first 7 characters and Base62 encode. Check for collisions in the database and regenerate if needed.
+
+
+
+
+
SQL vs NoSQL
+
+
+
Use a relational database as a large hash table mapping generated URLs to paste file paths. Alternatively, consider a NoSQL key-value store for simpler lookups.
+
+
+
+
+
Caching
+
+
+
Cache frequently accessed URLs in a Memory Cache (Redis/Memcached) to handle the read-heavy workload efficiently and reduce database load.
+
+
+
+
+
Object Store
+
+
+
Store paste contents in a managed Object Store (e.g. Amazon S3) instead of managing a file server directly. The database maps short URLs to object store paths.
+
+
+
+
+
Architecture Overview
+
+
+
+
Core Components
+
+
+
+
Client → Web Server (reverse proxy) → Read/Write API
+
Write API: generates unique URL, stores paste in Object Store, saves metadata in SQL Database
+
Read API: looks up shortlink, checks Memory Cache first, falls back to SQL Database, fetches paste from Object Store
+
Analytics Service: MapReduce jobs on stored logs to compute monthly stats
+
Cleanup Service: periodically scans for and deletes expired pastes
+
+
+
+
+
+
+
+ Database Schema
+
+
The pastes table stores: shortlink (char 7, PK), expiration_length_in_minutes (int), created_at (datetime), paste_path (varchar 255). The shortlink column is used as the primary key for fast lookups.
+
+
+
+
+
+
+
📖 Full Solution
+
Dive into the complete step-by-step solution with code samples, database schemas, and scaling discussion.
Design the core infrastructure behind Twitter's home timeline, user timeline, and search functionality — handling hundreds of billions of reads per month and fan-out to millions of followers.
+
+
+
+
+
+
High-level architecture for Twitter timeline and search
+
+
+
+
+
+ Interview Tip
+
+
Design the Facebook feed and Design Facebook search are similar questions. The core challenges — fan-out, timeline assembly, and search indexing — are shared across social media feed systems.
+
+
+
Use Cases & Constraints
+
+
+
+
+
Use Case
Description
+
+
+
Post tweet
User posts a tweet; service pushes to followers via push notifications and emails
+
User timeline
User views their own activity feed
+
Home timeline
User views activity from people they follow
+
Search
User searches keywords and sees matching tweets
+
High availability
Service remains available even during partial failures
+
+
+
+
+
+
+
Back-of-the-Envelope Estimates
+
Key numbers to ground the design
+
+
+
+
100 million active users, 500 million tweets/day
+
Each tweet fans out to ~10 followers on average → 5 billion deliveries/day
~10 KB per tweet (including media) → 150 TB new content/month
+
+
+
+
+
+
+ 📊 Tweet Fan-Out Flow
+ Flow
+
+
+
+flowchart TD
+ A["User posts tweet"] --> B["Write API"]
+ B --> C["Store in SQL DB"]
+ B --> D["Fan-Out Service"]
+ D --> E{"User has < 10K followers?"}
+ E -->|Yes| F["Fan-out on write"]
+ F --> G["Push to follower timelines in Cache"]
+ E -->|No| H["Fan-out on read"]
+ H --> I["Merge at read time"]
+ J["User views timeline"] --> K["Timeline Service"]
+ K --> L["Read from Cache"]
+ L --> M["Merge with celebrity tweets"]
+ M --> N["Return timeline"]
+
+
+
+
Key Design Decisions
+
+
+
+
+
Fan-Out
+
+
+
Fan-out on write: pre-compute timelines when a tweet is posted, pushing to each follower's cached timeline. Fast reads but expensive writes for popular users.
+
Fan-out on read: assemble timeline at read time by querying followees' tweets. Slower reads but cheap writes — useful for celebrity accounts with millions of followers.
+
+
+
+
+
Hybrid Approach
+
+
+
Use fan-out on write for most users and fan-out on read for celebrity users (millions of followers). This hybrid approach balances write cost with read performance.
+
+
+
+
+
Search Indexing
+
+
+
Build a reverse index mapping search terms to tweet IDs. Tweets are tokenized and indexed on write. Search queries are parsed, normalized, and matched against the index.
+
+
+
+
+
Timeline Cache
+
+
+
Store pre-computed timelines in a Memory Cache (Redis). Each user's home timeline is a list of tweet IDs. The cache is updated on fan-out and read from on timeline views.
+
+
+
+
+
Architecture Overview
+
+
+
+
Core Components
+
+
+
+
Client → Web Server (reverse proxy) → Read/Write API
+
Tweet Write API: stores tweet in SQL Database, triggers Fan-Out Service to push to follower timelines (Memory Cache)
+
Fan-Out Service: queries User Graph Service for followers, writes tweet to each follower's timeline in Memory Cache, enqueues push notifications
+
Timeline Read API: fetches pre-computed timeline from Memory Cache, hydrates tweet data from Tweet Info Service
+
Search API: parses query, looks up Reverse Index, fetches matching tweet details from Document Store
+
+
+
+
+
+
+
+ Celebrity Problem
+
+
Users with millions of followers (celebrities) make fan-out on write extremely expensive. The hybrid approach handles these users separately — their tweets are fetched on read and merged into the timeline at query time.
+
+
+
+
+
+
+
📖 Full Solution
+
Dive into the complete step-by-step solution with code samples, database schemas, and scaling discussion.
Design a scalable web crawler that indexes billions of pages — generating reverse indexes, page titles, snippets, and supporting fresh search results across the web.
+
+
+
+
+
+
High-level architecture for a scalable web crawler
+
+
+
+
+
+ Interview Tip
+
+
This exercise uses more traditional systems — avoid existing solutions like Solr or Nutch. The goal is to demonstrate your understanding of crawling, indexing, and distributed systems fundamentals.
+
+
+
Use Cases & Constraints
+
+
+
+
+
Use Case
Description
+
+
+
Crawl URLs
Service crawls a list of URLs, generating reverse index of words to pages
+
Generate metadata
Service generates titles and snippets for crawled pages (static, not query-dependent)
+
Search
User inputs search term and sees relevant pages with titles and snippets
+
High availability
Service remains available even during partial failures
+flowchart TD
+ A["URL Frontier"] --> B["Dequeue URL"]
+ B --> C["DNS Resolver"]
+ C --> D["Crawler Worker"]
+ D --> E["Download Page"]
+ E --> F["Duplicate Check"]
+ F -->|New| G["Parse Links"]
+ F -->|Duplicate| H["Discard"]
+ G --> I["Add to URL Frontier"]
+ G --> J["Reverse Index Service"]
+ J --> K["Document Store"]
+
+
+
+
Key Design Decisions
+
+
+
+
+
Crawl Strategy
+
+
+
Use BFS with a prioritized URL frontier. Pages are ranked by popularity and freshness. A sorted set (e.g. Redis) maintains the ranked crawl queue.
+
+
+
+
+
Duplicate Detection
+
+
+
Compute a page signature for each crawled page. Before processing, check against crawled_links in the database. Similar signatures reduce the link's priority to avoid infinite loops.
+
+
+
+
+
Distributed Crawling
+
+
+
Distribute crawling across multiple workers. Each worker pulls top-ranked URLs from the frontier, crawls them, and enqueues jobs for the Reverse Index Service and Document Service.
+
+
+
+
+
Politeness
+
+
+
Respect robots.txt rules and enforce rate limits per domain. Add delays between requests to the same host to avoid overwhelming target servers.
+
+
+
+
+
Architecture Overview
+
+
+
+
Core Components
+
+
+
+
URL Frontier: prioritized queue of URLs to crawl, stored in Redis sorted sets
Reverse Index Service: builds inverted index mapping words → pages containing those words
+
Document Service: generates and stores static titles and snippets for each page
+
NoSQL Database: stores links_to_crawl and crawled_links with page signatures
+
Search API: queries reverse index, ranks results, and returns pages with titles and snippets
+
+
+
+
+
+
+
+ Avoiding Infinite Loops
+
+
The web is a graph that may contain cycles. The crawler prevents infinite loops by computing page signatures and checking against previously crawled pages. If a similar page signature already exists, the link's priority is reduced rather than re-crawling it.
+
+
+
+
+
+
+
📖 Full Solution
+
Dive into the complete step-by-step solution with code samples, database schemas, and scaling discussion.
Design a personal finance aggregation service like Mint.com — connecting bank accounts, extracting and categorizing transactions, recommending budgets, and analyzing spending patterns.
+
+
+
+
+
+
High-level architecture for Mint.com personal finance service
+
+
+
+
+
+ Interview Tip
+
+
This problem is write-heavy (10:1 write-to-read ratio). Users make transactions daily but few visit the site daily. Focus on efficient transaction ingestion and categorization at scale.
+
+
+
Use Cases & Constraints
+
+
+
+
+
Use Case
Description
+
+
+
Connect account
User connects to a financial account
+
Extract transactions
Service extracts transactions from connected accounts, updates daily
+
Categorize
Service categorizes transactions (with manual override); no automatic re-categorization
+
Spending analysis
Service analyzes monthly spending by category
+
Budget recommendations
Service recommends a budget; users can manually set budgets; notifications on approach/exceed
+
High availability
Service remains available even during partial failures
+
+
+
+
+
+
+
Back-of-the-Envelope Estimates
+
Key numbers to ground the design
+
+
+
+
10 million users, 30 million financial accounts, 50,000 sellers
~50 bytes per transaction → 250 GB new content/month
+
+
+
+
+
+
+ 📊 Data Pipeline Flow
+ Flow
+
+
+
+flowchart TD
+ A["Bank APIs"] --> B["Transaction Extraction Service"]
+ B --> C["Raw Transactions Queue"]
+ C --> D["Category Service"]
+ D --> E["MapReduce Categorization"]
+ E --> F["Budget Service"]
+ F --> G{"Over budget?"}
+ G -->|Yes| H["Notification Service"]
+ G -->|No| I["Update Dashboard"]
+ H --> J["Email / SMS Alert"]
+ I --> K["Analytics Store"]
+
+
+
+
Key Design Decisions
+
+
+
+
+
Transaction Categorization
+
+
+
Use MapReduce to categorize transactions at scale. Map sellers to categories using a lookup table. Users can manually override categories, which are stored per-user.
+
+
+
+
+
Data Pulling
+
+
+
Pull transaction data from financial institutions daily using their APIs. Only update accounts for users active in the past 30 days to avoid unnecessary load.
+
+
+
+
+
Budget Analysis
+
+
+
Aggregate categorized spending monthly. Compare against user-defined or system-recommended budgets. Send notifications when spending approaches or exceeds budget thresholds.
+
+
+
+
+
Security
+
+
+
Financial data requires encryption at rest and in transit. Store bank credentials securely (vaulted). Use SSL/TLS for all API communication. Comply with financial data regulations.
+
+
+
+
+
Architecture Overview
+
+
+
+
Core Components
+
+
+
+
Client → Web Server (reverse proxy) → Accounts API / Budget API
+
Accounts API: manages user accounts and bank connections in SQL Database
+
Transaction Extraction Service: pulls transactions daily from financial APIs, stores raw data
+
Category Service: MapReduce jobs categorize transactions using seller → category mapping
+
Budget Service: analyzes monthly categorized spend and compares against budgets
+
Notification Service: sends alerts when budgets are approached or exceeded
Design the data structures and algorithms for a social network — enabling users to search for someone and see the shortest path of connections to that person (like LinkedIn's "degrees of connection").
+
+
+
+
+
+
High-level architecture for social graph shortest path
+
+
+
+
+
+ Interview Tip
+
+
This exercise uses more traditional systems — avoid graph-specific solutions like GraphQL or graph databases like Neo4j. Focus on demonstrating BFS, adjacency lists, and distributed system fundamentals.
+
+
+
Use Cases & Constraints
+
+
+
+
+
Use Case
Description
+
+
+
Shortest path
User searches for someone and sees the shortest path of connections to that person
+
High availability
Service remains available even during partial failures
Graph data won't fit on a single machine — must be distributed
+
+
+
+
+
+
+ 📊 Bidirectional BFS Search Flow
+ Flow
+
+
+
+flowchart TD
+ A["Search: User A to User B"] --> B["BFS Service"]
+ B --> C["Start BFS from User A"]
+ B --> D["Start BFS from User B"]
+ C --> E["Person Server Lookup"]
+ D --> E
+ E --> F["Query User Graph Shard"]
+ F --> G{"Paths intersect?"}
+ G -->|Yes| H["Return shortest path"]
+ G -->|No| I["Expand next BFS level"]
+ I --> E
+
+
+
+
Key Design Decisions
+
+
+
+
+
BFS Shortest Path
+
+
+
Use Breadth-First Search (BFS) to find the shortest unweighted path between two users. BFS explores all nodes at distance k before moving to distance k+1, guaranteeing the shortest path.
+
+
+
+
+
Bidirectional BFS
+
+
+
Optimize with bidirectional BFS — search from both the source and destination simultaneously. When the two frontiers meet, we've found the shortest path. This dramatically reduces the search space.
+
+
+
+
+
Sharding
+
+
+
Shard users across multiple machines using a Person Server lookup. Each machine stores a subset of users and their adjacency lists. Lookups route to the correct shard via a lookup service.
+
+
+
+
+
Caching
+
+
+
Cache frequently accessed user adjacency lists in Memory Cache. Popular users (many connections) are accessed often and benefit greatly from caching to reduce database lookups.
+
+
+
+
+
Architecture Overview
+
+
+
+
Core Components
+
+
+
+
Client → Web Server (reverse proxy) → Search API
+
User Graph Service: stores user friend relationships as adjacency lists, sharded across machines
+
Person Server: lookup service mapping user IDs to the machine storing their data
+
BFS Service: performs bidirectional BFS across sharded graph data, communicating with multiple Person Servers
+
Memory Cache: caches hot adjacency lists (Redis/Memcached) to speed up BFS traversal
+
+
+
+
+
+
+
+ Graph Edges
+
+
Graph edges are unweighted — each friend connection has equal weight. This makes BFS the optimal algorithm since it naturally finds shortest paths in unweighted graphs. With 50 friends per user average and 100M users, there are 5 billion edges to store and traverse.
+
+
+
+
+
+
+
📖 Full Solution
+
Dive into the complete step-by-step solution with BFS code, sharding strategies, and scaling discussion.
Design a key-value cache that stores the results of the most recent web server queries — enabling fast cache hits, LRU eviction, and distributed operation across multiple machines.
+
+
+
+
+
+
High-level architecture for a key-value search cache
+
+
+
+
+
+ Interview Tip
+
+
This problem focuses on the cache layer itself — how to structure an efficient key-value store with LRU eviction. Consider hash tables, linked lists, and memory management as core building blocks.
+
+
+
Use Cases & Constraints
+
+
+
+
+
Use Case
Description
+
+
+
Cache hit
User sends a search request; service returns cached results for matching queries
+
Cache miss
User sends a search request with no cached result; service queries backend and caches the result
+
High availability
Service remains available with eventual consistency
+
+
+
+
+
+
+
Back-of-the-Envelope Estimates
+
Key numbers to ground the design
+
+
+
+
10 million users, 10 billion queries/month → ~4,000 requests/sec
2.7 TB of cache data/month if all queries were unique
+
Popular queries should almost always be in cache — need smart expiration/refresh
+
+
+
+
+
+
+ 📊 Cache Hit / Miss Flow
+ Flow
+
+
+
+flowchart TD
+ A["Query API"] --> B["Memory Cache"]
+ B --> C{"Cache Hit?"}
+ C -->|Yes| D["Return cached result"]
+ C -->|Miss| E["Reverse Index Service"]
+ E --> F["Document Service"]
+ F --> G["Return result"]
+ G --> H["Store in Cache (LRU)"]
+ H --> D
+
+
+
+
Key Design Decisions
+
+
+
+
+
LRU Eviction
+
+
+
Use a doubly-linked list for LRU ordering: new/accessed items move to the head, evicted items are removed from the tail. Combined with a hash table for O(1) lookups to each node.
+
+
+
+
+
Hash + Linked List
+
+
+
The hash table maps query strings to linked list nodes. On cache hit, move the node to head. On cache miss, add to head and evict from tail if capacity reached. Both operations are O(1).
+
+
+
+
+
Distribution
+
+
+
Distribute cache across multiple machines using consistent hashing. Each machine handles a subset of the key space. This allows horizontal scaling and graceful handling of node additions/removals.
+
+
+
+
+
Memory Management
+
+
+
Limited memory in cache requires careful management. Track memory usage and evict LRU entries when approaching limits. Monitor cache hit ratio to tune capacity and eviction policies.
Memory Cache: distributed hash table + LRU linked list across multiple machines
+
Reverse Index Service: on cache miss, finds documents matching the query terms
+
Document Service: returns titles and snippets for matching documents
+
Results are cached in Memory Cache before returning to client
+
+
+
+
+
+
+
+ Cache Invalidation
+
+
Cache entries become stale as underlying data changes. Strategies include TTL-based expiration (entries expire after a set time), write-through (update cache on write), or event-driven invalidation (invalidate when source data changes). Choose based on consistency requirements.
+
+
+
+
+
+
+
📖 Full Solution
+
Dive into the complete step-by-step solution with LRU cache code, query processing, and scaling discussion.
Design the sales ranking feature for an e-commerce platform like Amazon — computing the most popular products by category from billions of monthly transactions and serving rankings to users at scale.
+
+
+
+
+
+
High-level architecture for sales ranking by category
+
+
+
+
+
+ Interview Tip
+
+
Focus on the ranking computation — not the general e-commerce platform. The key challenge is efficiently processing billions of transactions to produce per-category rankings updated hourly.
+
+
+
Use Cases & Constraints
+
+
+
+
+
Use Case
Description
+
+
+
Compute rankings
Service calculates the past week's most popular products by category
+
View rankings
User views the most popular products by category
+
High availability
Service remains available even during partial failures
~40 bytes per transaction → 40 GB new content/month
+
+
+
+
+
+
+ 📊 Sales Ranking Pipeline
+ Flow
+
+
+
+flowchart TD
+ A["Sales Logs"] --> B["Map Phase"]
+ B --> C["Extract category + sales count"]
+ C --> D["Reduce Phase"]
+ D --> E["Aggregate per category"]
+ E --> F["Sort by rank"]
+ F --> G["Store in SQL DB"]
+ G --> H["Cache Layer"]
+ H --> I["Sales Rank API"]
+
+
+
+
Key Design Decisions
+
+
+
+
+
MapReduce
+
+
+
Use a multi-step MapReduce pipeline: (1) Map sales logs to extract (category, product_id, quantity), (2) Reduce to aggregate sales per product per category, (3) Sort by quantity within each category to produce rankings.
+
+
+
+
+
Sales Log Processing
+
+
+
Store raw sales logs in a managed Object Store (e.g. Amazon S3). MapReduce jobs process these logs periodically. Items can appear in multiple categories but cannot change categories.
+
+
+
+
+
Periodic Updates
+
+
+
Rankings are updated hourly (more frequently for popular products). Results are written to a sales_rank table in the SQL Database. The past week's data is used for ranking computation.
+
+
+
+
+
Caching Rankings
+
+
+
Cache computed rankings in a Memory Cache (Redis/Memcached). With a 100:1 read-to-write ratio, caching is critical. Rankings change infrequently (hourly) so cache invalidation is straightforward.
+
+
+
+
+
Architecture Overview
+
+
+
+
Core Components
+
+
+
+
Client → Web Server (reverse proxy) → Sales Rank API
+
Sales API: ingests purchase events, writes raw sales logs to Object Store
+
Sales Rank Service (MapReduce): processes sales logs → aggregates per category → sorts to produce rankings → writes to SQL Database
Memory Cache: caches rankings per category for fast reads at 40K req/sec
+
Sales Rank API: checks cache first, falls back to database, returns ranked products
+
+
+
+
+
+
+
+ Multi-Category Items
+
+
Items can appear in multiple categories simultaneously. The MapReduce pipeline emits a separate (category, product_id, quantity) tuple for each category an item belongs to, so an item can rank differently across its categories.
+
+
+
+
+
+
+
📖 Full Solution
+
Dive into the complete step-by-step solution with MapReduce pipeline, database schemas, and scaling discussion.
+ `
+ },
+
+ // ================================================================
+ // Chapter 27 — Design Scaling to Millions on AWS
+ // ================================================================
+ 'design-scaling-aws': {
+ part: 'Part F',
+ title: 'Scaling on AWS',
+ content: `
+
+
Chapter 27 — Practice Problems
+
Design a System that Scales to Millions of Users on AWS
+
Design an iterative approach to scaling a system from a single user to millions on AWS — progressively adding components like load balancers, CDNs, caching, read replicas, and auto scaling as bottlenecks appear.
+
+
+
+
+
+
High-level architecture for scaling to millions of users on AWS
+
+
+
+
+
+ Interview Tip
+
+
AWS-specific knowledge is not required unless applying for an AWS-focused role. The general scaling principles discussed here apply broadly across cloud providers and on-premise infrastructure. Focus on the iterative approach: benchmark → profile → address bottleneck → repeat.
+
+
+
Use Cases & Constraints
+
+
+
+
+
Use Case
Description
+
+
+
Read/write requests
User makes a read or write request; service processes and returns results
+
Scale iteratively
Service evolves from serving a small number of users to millions
+
General patterns
Discuss general scaling patterns as the architecture evolves
+
High availability
Service remains available even during partial failures
+
+
+
+
+
+
+
Back-of-the-Envelope Estimates
+
Key numbers to ground the design at full scale
+
+
+
+
10 million users at full scale
+
1 billion writes/month → ~400 writes/sec
+
100 billion reads/month → ~40,000 reads/sec
+
100:1 read-to-write ratio
+
1 KB per write → 1 TB new content/month
+
+
+
+
+
+
+ 📊 Progressive Scaling Architecture
+ Flow
+
+
+
+flowchart TD
+ A["Single EC2 Instance"] --> B["Separate DB: RDS"]
+ B --> C["Add ElastiCache"]
+ C --> D["Multiple EC2 behind ELB"]
+ D --> E["Auto Scaling Group"]
+ E --> F["Multi-AZ Deployment"]
+ F --> G["Add CloudFront CDN"]
+ G --> H["Route 53 DNS"]
+
+
+
+
Key Design Decisions — Iterative Scaling
+
+
+
+
+
Start Simple
+
+
+
Begin with a single EC2 instance running the web server and MySQL database. Use vertical scaling first — simply choose a bigger box. Monitor CPU, memory, IO, and network to identify bottlenecks.
+
+
+
+
+
Separate Components
+
+
+
Move the database to a separate instance (Amazon RDS). Add a MySQL read replica to handle read-heavy traffic. Separate the web server from the database to scale independently.
+
+
+
+
+
Add Caching & CDN
+
+
+
Add ElastiCache (Memcached/Redis) to cache database queries and session data. Use a CDN (CloudFront) to serve static content globally. These dramatically reduce database load and latency.
+
+
+
+
+
Horizontal Scaling
+
+
+
Add an Elastic Load Balancer (ELB) distributing traffic across multiple web servers. Enable Auto Scaling to add/remove instances based on demand. Deploy across multiple Availability Zones for fault tolerance.
+
+
+
+
+
Architecture Evolution
+
+
+
+
Progressive Scaling Steps
+
+
+
+
1 user: Single EC2 instance (web server + MySQL) — vertical scaling
+
Users+: Separate MySQL to Amazon RDS, add read replica
+
Users++: Add ElastiCache for read-heavy queries, CDN for static assets
+
Users+++: ELB → multiple web server instances with Auto Scaling
+
Users++++: Multiple Availability Zones, MySQL Multi-AZ deployment
The key pattern is: (1) Benchmark/Load Test → (2) Profile for bottlenecks → (3) Address bottleneck while evaluating trade-offs → (4) Repeat. Don't over-engineer upfront — add complexity only when metrics show it's needed.
+
+
+
AWS Components Summary
+
+
+
+
+
Component
AWS Service
Role
+
+
+
Compute
EC2 + Auto Scaling
Web/app servers that scale horizontally
+
Database
Amazon RDS (MySQL)
Managed relational database with read replicas
+
Cache
ElastiCache
In-memory caching for hot queries and sessions
+
CDN
CloudFront
Global content delivery for static assets
+
Load Balancer
ELB
Distributes traffic across multiple instances
+
DNS
Route 53
DNS with health checks and failover routing
+
Storage
S3
Object storage for static files and logs
+
+
+
+
+
+
+
+
+
📖 Full Solution
+
Dive into the complete step-by-step solution with detailed AWS architecture evolution and scaling discussion.
Design a hash table that maps keys to values with O(1) average-case lookup, insert, and delete — handling collisions and dynamic resizing gracefully.
+
+
+
+
+
+
+ Problem Statement
+
+
Design a HashTable class that supports get, set, and remove operations. Handle hash collisions, support dynamic resizing when the load factor exceeds a threshold, and ensure O(1) average time complexity for all operations.
+
+
+
Key Design Concepts
+
+
+
+
+
Hash Function
+
+
+
Maps keys to array indices. A good hash function distributes keys uniformly to minimize collisions.
+
+
+
+
+
Collision Handling
+
+
+
Chaining: each bucket stores a linked list of entries. Open addressing: probes for the next available slot.
+
+
+
+
+
Load Factor
+
+
+
Ratio of stored items to table size. When it exceeds a threshold (e.g. 0.75), the table resizes to maintain performance.
+
+
+
+
+
Dynamic Resizing
+
+
+
When the load factor is exceeded, allocate a larger array and rehash all existing entries into the new table.
+classDiagram
+ class HashTable {
+ +size : int
+ +table : list
+ +get(key) value
+ +set(key, value)
+ +remove(key)
+ -_hash_function(key) int
+ }
+ class Item {
+ +key
+ +value
+ }
+ HashTable "1" --> "*" Item : contains
+
+
+
+
+
+ 📊 set() Operation Flow
+ Flow
+
+
+
+flowchart TD
+ A["set(key, value)"] --> B["Hash the key"]
+ B --> C["Get bucket index"]
+ C --> D{"Key exists in bucket?"}
+ D -->|Yes| E["Update existing Item value"]
+ D -->|No| F["Append new Item to bucket"]
+ E --> G["Done"]
+ F --> G
+
+
+
+
+
+
Design Considerations
+
+
+
+
Key Decisions
+
+
+
+
Chaining vs Open Addressing — Chaining is simpler to implement and handles high load factors well; open addressing has better cache performance but degrades as load increases.
+
Resize threshold — A load factor of 0.75 balances memory usage and collision frequency. Resizing typically doubles the table capacity.
+
Hash function quality — A poor hash leads to clustering. Use prime-number modular arithmetic or well-known hash algorithms.
+
Thread safety — Consider concurrent access: fine-grained locking per bucket vs a global lock vs lock-free designs.
+
+
+
+
+
+
+
+
+
+ Jupyter Notebook Solution
+
+
Work through the full implementation with test cases in the interactive notebook: hash_map.ipynb
Design a cache with a fixed capacity that evicts the least recently used item when full — achieving O(1) time for both reads and writes.
+
+
+
+
+
+
+ Problem Statement
+
+
Design a Least Recently Used (LRU) Cache with a fixed capacity. Support get(key) and set(key, value) in O(1) time. When the cache is full, evict the least recently used entry before inserting a new one.
+
+
+
Key Design Concepts
+
+
+
+
+
Doubly Linked List
+
+
+
Maintains access order. The most recently used item is at the head; the least recently used is at the tail for O(1) eviction.
+
+
+
+
+
Hash Map
+
+
+
Maps keys directly to linked list nodes, enabling O(1) lookup without traversing the list.
+
+
+
+
+
Eviction Policy
+
+
+
When the cache reaches capacity, remove the node at the tail (least recently used) before inserting the new entry.
+
+
+
+
+
Move to Head
+
+
+
On every get or set, move the accessed node to the head of the list to mark it as most recently used.
+sequenceDiagram
+ participant Client
+ participant Cache as LRUCache
+ participant Map as lookup_map
+ participant List as linked_list
+ Note over Client,List: get(key) Operation
+ Client->>Cache: get(key)
+ Cache->>Map: lookup key
+ alt Cache Hit
+ Map-->>Cache: node found
+ Cache->>List: move node to front
+ Cache-->>Client: return value
+ else Cache Miss
+ Map-->>Cache: not found
+ Cache-->>Client: return None
+ end
+ Note over Client,List: set(key, value) Operation
+ Client->>Cache: set(key, value)
+ Cache->>Map: lookup key
+ alt Key Exists
+ Map-->>Cache: node found
+ Cache->>List: update and move to front
+ else New Key
+ alt Cache Full
+ Cache->>List: remove oldest node
+ Cache->>Map: delete oldest key
+ end
+ Cache->>List: add new node to front
+ Cache->>Map: store new key
+ end
+
+
+
+
+
+
Design Considerations
+
+
+
+
Key Decisions
+
+
+
+
Why doubly linked list? — Singly linked lists require O(n) to remove a node because you need the previous pointer. A doubly linked list enables O(1) removal.
+
Sentinel nodes — Use dummy head and tail nodes to avoid null-check edge cases when adding/removing from the list.
+
Thread safety — For concurrent access, consider read-write locks or a concurrent hash map with per-segment locking.
+
TTL support — Extend the design with a time-to-live field per entry and a background thread or lazy eviction.
+
+
+
+
+
+
+
+
+
+ Jupyter Notebook Solution
+
+
Work through the full implementation with test cases in the interactive notebook: lru_cache.ipynb
Model a call center with a hierarchy of employees — routing incoming calls to available staff and escalating when necessary.
+
+
+
+
+
+
+ Problem Statement
+
+
Design a Call Center with three levels of employees: respondent, manager, and director. An incoming call is first assigned to an available respondent. If the respondent can't handle the call, it escalates to a manager, then to a director.
+
+
+
Key Design Concepts
+
+
+
+
+
Employee Hierarchy
+
+
+
Three tiers: Respondent → Manager → Director. Each level has different capabilities and authority to resolve calls.
+
+
+
+
+
Call Routing
+
+
+
Incoming calls are dispatched to the first available employee at the lowest level. Use queues per level to track who is free.
+
+
+
+
+
Escalation
+
+
+
When an employee cannot handle a call, it escalates to the next tier. If no one at any level is available, the call is queued.
+
+
+
+
+
Inheritance
+
+
+
A base Employee class with shared logic; subclasses Respondent, Manager, Director override escalation behavior.
+
+
+
+
+
Class Hierarchy
+
+
+
+
+
+
Class
+
Responsibility
+
Key Methods / Fields
+
+
+
+
+
CallCenter
+
Manages employee queues and dispatches calls
+
dispatch_call(call), notify_call_completed(call)
+
+
+
Employee
+
Base class — tracks availability and current call
+
take_call(call), complete_call(), escalate()
+
+
+
Respondent / Manager / Director
+
Subclasses defining rank-specific behavior
+
Inherits from Employee, sets rank level
+
+
+
Call
+
Represents an incoming call with a minimum required rank
+
rank, state, handler
+
+
+
+
+
+
+
+ 📐 Call Center Class Structure
+ Class Diagram
+
+
+
+classDiagram
+ class CallCenter {
+ +respondents : list
+ +managers : list
+ +directors : list
+ +dispatch_call(call)
+ }
+ class Employee {
+ <<abstract>>
+ +call : Call
+ +can_handle(call) bool
+ +take_call(call)
+ +complete_call()
+ }
+ class Respondent
+ class Manager
+ class Director
+ class Call {
+ +rank : int
+ +caller : str
+ }
+ Employee <|-- Respondent
+ Employee <|-- Manager
+ Employee <|-- Director
+ CallCenter --> Employee : manages
+ Employee --> Call : handles
+
+
+
+
+
+ 📊 Call Dispatch Flow
+ Flow
+
+
+
+flowchart TD
+ A["Incoming Call"] --> B{"Respondent available?"}
+ B -->|Yes| C["Assign to Respondent"]
+ B -->|No| D{"Manager available?"}
+ D -->|Yes| E["Escalate to Manager"]
+ D -->|No| F{"Director available?"}
+ F -->|Yes| G["Escalate to Director"]
+ F -->|No| H["Queue the Call"]
+ C --> I["Call Handled"]
+ E --> I
+ G --> I
+ H --> J["Retry when available"]
+
+
+
+
+
+
Design Considerations
+
+
+
+
Key Decisions
+
+
+
+
Queue per rank — Maintain separate queues of available employees for each tier to enable fast lookup of the first free employee.
+
Call queuing — If all employees at every level are busy, hold the call in a waiting queue and assign it when someone frees up.
+
Escalation path — A call's rank field defines the minimum tier that can handle it. Escalation simply increments this rank.
+
Notification pattern — When a call completes, the employee notifies the call center so it can dispatch queued calls.
+
+
+
+
+
+
+
+
+
+ Jupyter Notebook Solution
+
+
Work through the full implementation with test cases in the interactive notebook: call_center.ipynb
Model a generic deck of cards using OOP — with support for shuffling, dealing, and extending to specific card games like Blackjack.
+
+
+
+
+
+
+ Problem Statement
+
+
Design a Deck of Cards using object-oriented principles. The deck should support shuffling and dealing cards. Demonstrate how to extend the design to support a specific game such as Blackjack, including hand scoring.
+
+
+
Key Design Concepts
+
+
+
+
+
Enums for Suits & Values
+
+
+
Use enumerations for Suit (Hearts, Diamonds, Clubs, Spades) and Face values to enforce type safety.
+
+
+
+
+
Inheritance & Polymorphism
+
+
+
A base Hand class with game-specific subclasses like BlackjackHand that override scoring logic.
+
+
+
+
+
Shuffle & Deal
+
+
+
The Deck encapsulates the collection of cards and provides shuffle() and deal_card() operations.
+
+
+
+
+
Extensibility
+
+
+
Design for extension: new card games only need to subclass Hand and implement their own scoring — no changes to Deck or Card.
+
+
+
+
+
Class Hierarchy
+
+
+
+
+
+
Class
+
Responsibility
+
Key Methods / Fields
+
+
+
+
+
Suit (Enum)
+
Enumerates the four suits
+
HEARTS, DIAMONDS, CLUBS, SPADES
+
+
+
Card
+
A single card with suit and face value
+
suit, value
+
+
+
Deck
+
Holds 52 cards, provides shuffle and deal
+
shuffle(), deal_card(), remaining_cards()
+
+
+
Hand
+
Base class for a player's hand of cards
+
add_card(card), score()
+
+
+
BlackjackHand
+
Extends Hand with Blackjack-specific scoring (Ace = 1 or 11)
+
score(), is_bust()
+
+
+
+
+
+
+
+ 📐 Deck of Cards Class Structure
+ Class Diagram
+
+
+
+classDiagram
+ class Suit {
+ <<enumeration>>
+ HEART
+ DIAMOND
+ CLUB
+ SPADE
+ }
+ class Card {
+ +suit : Suit
+ +value : int
+ }
+ class Deck {
+ +cards : list
+ +shuffle()
+ +deal_card() Card
+ +remaining() int
+ }
+ class Hand {
+ +cards : list
+ +add_card(card)
+ +score() int
+ }
+ class BlackjackHand {
+ +score() int
+ +is_bust() bool
+ }
+ Card --> Suit : has
+ Deck "1" --> "*" Card : contains
+ Hand "1" --> "*" Card : holds
+ Hand <|-- BlackjackHand
+
+
+
+
+
+
Design Considerations
+
+
+
+
Key Decisions
+
+
+
+
Generic vs game-specific — Keep Deck and Card game-agnostic. Game rules live in Hand subclasses.
+
Ace handling in Blackjack — An Ace counts as 11 unless it causes a bust, in which case it becomes 1. The score() method must handle this dynamically.
+
Enum vs constants — Enums for suits and face values prevent invalid card creation and improve readability.
+
Multiple decks — For casino games, extend to support a "shoe" of multiple shuffled decks.
+
+
+
+
+
+
+
+
+
+ Jupyter Notebook Solution
+
+
Work through the full implementation with test cases in the interactive notebook: deck_of_cards.ipynb
Model a multi-level parking lot that handles different vehicle sizes, assigns spots intelligently, and tracks availability in real time.
+
+
+
+
+
+
+ Problem Statement
+
+
Design a Parking Lot with multiple levels. Each level has rows of spots in three sizes: small (motorcycle), compact (car), and large (bus). A vehicle is assigned to an appropriate spot based on its size. Track available spots per level.
+
+
+
Key Design Concepts
+
+
+
+
+
Vehicle Sizing
+
+
+
Vehicles have different sizes: Motorcycle fits in any spot, Car fits in compact or large, Bus requires multiple large spots.
+
+
+
+
+
Spot Assignment
+
+
+
Each level tries to park the vehicle in the smallest suitable spot, scanning rows to find availability.
+
+
+
+
+
Multi-Level Structure
+
+
+
The ParkingLot contains multiple Level objects. When one level is full, try the next level.
+
+
+
+
+
Availability Tracking
+
+
+
Each level tracks its available spot count. The lot reports total availability across all levels.
Concrete vehicle types with specific size constraints
+
Inherits from Vehicle
+
+
+
+
+
+
+
+ 📐 Parking Lot Class Structure
+ Class Diagram
+
+
+
+classDiagram
+ class ParkingLot {
+ +levels : list
+ +park_vehicle(vehicle) bool
+ }
+ class Level {
+ +floor : int
+ +spots : list
+ +park_vehicle(vehicle) bool
+ +available_spots() int
+ }
+ class ParkingSpot {
+ +size : str
+ +vehicle : Vehicle
+ +is_available() bool
+ +park(vehicle)
+ +remove_vehicle()
+ }
+ class Vehicle {
+ <<abstract>>
+ +spots_needed : int
+ +size : str
+ +license_plate : str
+ }
+ class Motorcycle
+ class Car
+ class Bus
+ ParkingLot "1" --> "*" Level : has
+ Level "1" --> "*" ParkingSpot : contains
+ ParkingSpot --> Vehicle : holds
+ Vehicle <|-- Motorcycle
+ Vehicle <|-- Car
+ Vehicle <|-- Bus
+
+
+
+
+
+ 📊 Vehicle Parking Flow
+ Flow
+
+
+
+flowchart TD
+ A["Vehicle Arrives"] --> B["Find Level with Space"]
+ B --> C{"Level found?"}
+ C -->|No| D["Parking Full"]
+ C -->|Yes| E["Find Spot matching size"]
+ E --> F{"Spot available?"}
+ F -->|No| G["Try next Level"]
+ G --> C
+ F -->|Yes| H["Park Vehicle in Spot"]
+ H --> I["spot.is_available = false"]
+ I --> J["Return Success"]
+
+
+
+
+
+
Design Considerations
+
+
+
+
Key Decisions
+
+
+
+
Bus spanning multiple spots — A bus requires several contiguous large spots. The level must find a row with enough consecutive available large spots.
+
Spot selection strategy — Use the smallest spot that fits the vehicle to maximize capacity. A motorcycle can park in any spot, but prefers a small one.
+
Concurrency — In a real parking system, spot assignment must be atomic to prevent two vehicles from being assigned the same spot.
+
Extensibility — Adding new vehicle types (e.g. handicapped, electric with charging) should require minimal code changes.
+
+
+
+
+
+
+
+
+
+ Jupyter Notebook Solution
+
+
Work through the full implementation with test cases in the interactive notebook: parking_lot.ipynb
Model an online chat system supporting one-on-one and group conversations, user presence, and message history.
+
+
+
+
+
+
+ Problem Statement
+
+
Design an Online Chat system (similar to Facebook Messenger). Support 1:1 private chats and group chats. Track user status (online, offline, away). Store message history and allow users to add/remove participants from group conversations.
+
+
+
Key Design Concepts
+
+
+
+
+
User Management
+
+
+
Each user has a unique ID, profile, buddy list, and a status (online, offline, away). The server tracks all connected users.
+
+
+
+
+
Conversation Model
+
+
+
A base Conversation class with PrivateChat (two users) and GroupChat (multiple users with admin controls) subclasses.
+
+
+
+
+
Message History
+
+
+
Each conversation stores an ordered list of Message objects with timestamps, sender info, and content.
+
+
+
+
+
User Status
+
+
+
An enum representing Online, Offline, and Away. Status changes are broadcast to the user's contacts.
+
+
+
+
+
Class Hierarchy
+
+
+
+
+
+
Class
+
Responsibility
+
Key Methods / Fields
+
+
+
+
+
ChatServer
+
Central server managing users and conversations
+
sign_in(user), sign_out(user), send_message(msg)
+
+
+
User
+
Represents a user with profile, status, and buddy list
+
id, name, status, contacts, conversations
+
+
+
UserStatus (Enum)
+
Enumerates possible user states
+
ONLINE, OFFLINE, AWAY
+
+
+
Conversation
+
Base class for private and group chats
+
participants, messages, add_message(msg)
+
+
+
GroupChat
+
Extends Conversation with admin controls
+
add_user(user), remove_user(user), admins
+
+
+
Message
+
A single message with content, sender, and timestamp
+
content, sender, timestamp
+
+
+
+
+
+
+
+ 📐 Online Chat Class Structure
+ Class Diagram
+
+
+
+classDiagram
+ class ChatServer {
+ +users : list
+ +conversations : list
+ +add_user(user)
+ +remove_user(user)
+ +create_conversation(users) Conversation
+ +send_message(user, conversation, content)
+ }
+ class User {
+ +id : int
+ +name : str
+ +status : UserStatus
+ }
+ class UserStatus {
+ <<enumeration>>
+ ONLINE
+ OFFLINE
+ AWAY
+ }
+ class Conversation {
+ +users : list
+ +messages : list
+ +add_message(message)
+ }
+ class GroupChat {
+ +add_user(user)
+ +remove_user(user)
+ }
+ class Message {
+ +content : str
+ +timestamp : datetime
+ +sender : User
+ }
+ ChatServer "1" --> "*" User : manages
+ ChatServer "1" --> "*" Conversation : hosts
+ User --> UserStatus : has
+ Conversation <|-- GroupChat
+ Conversation "1" --> "*" Message : contains
+ Message --> User : sent by
+
+
+
+
+
+ 🔄 Send Message Flow
+ Sequence
+
+
+
+sequenceDiagram
+ participant A as User A
+ participant Server as ChatServer
+ participant Conv as Conversation
+ participant B as User B
+ A->>Server: send_message(conversation, content)
+ Server->>Server: validate user and conversation
+ Server->>Conv: add_message(message)
+ Conv->>Conv: store in messages list
+ Server->>B: notify new message
+ B-->>Server: acknowledge
+
+
+
+
+
+
Design Considerations
+
+
+
+
Key Decisions
+
+
+
+
Private vs Group chat — A PrivateChat always has exactly two participants. A GroupChat can have many, with add/remove semantics and admin roles.
+
Status notifications — When a user's status changes, notify all contacts using an observer pattern rather than polling.
+
Message delivery — For offline users, queue messages and deliver them upon sign-in. For online users, push messages in real time.
+
Scalability hooks — The OO model maps to a distributed system: the ChatServer becomes a cluster, conversations shard by ID, and message queues handle delivery.
+
+
+
+
+
+
+
+
+
+ Jupyter Notebook Solution
+
+
Work through the full implementation with test cases in the interactive notebook: online_chat.ipynb
+ `
+ },
+
+};
diff --git a/website/js/chapters/chapters-h.js b/website/js/chapters/chapters-h.js
new file mode 100644
index 00000000000..221298424f1
--- /dev/null
+++ b/website/js/chapters/chapters-h.js
@@ -0,0 +1,849 @@
+/**
+ * Part H: Appendix — Chapters 34–38
+ * Powers of Two Table, Latency Numbers, Additional Questions,
+ * Real World Architectures, Engineering Blogs
+ *
+ * Registered into global CHAPTERS_H object.
+ */
+
+const CHAPTERS_H = {
+
+ // ================================================================
+ // Chapter 34 — Powers of Two Table
+ // ================================================================
+ 'powers-of-two': {
+ part: 'Part H',
+ title: 'Powers of Two Table',
+ content: `
+
+
Chapter 34 — Appendix
+
Powers of Two Table
+
A quick-reference table for back-of-the-envelope calculations — essential for estimating storage, memory, and bandwidth requirements in system design interviews.
+
+
+
+
+
+
+ Why This Matters
+
+
You'll often be asked to do back-of-the-envelope estimates in system design interviews — for example, determining how long it takes to generate 100 image thumbnails from disk, or how much memory a data structure will consume. Knowing powers of two by heart lets you quickly convert between KB, MB, GB, and TB without a calculator.
+
+
+
Powers of Two Reference
+
+
+
+
+
+
Power
+
Exact Value
+
Approx Value
+
Bytes
+
+
+
+
+
27
+
128
+
—
+
—
+
+
+
28
+
256
+
—
+
—
+
+
+
210
+
1,024
+
1 thousand
+
1 KB
+
+
+
216
+
65,536
+
—
+
64 KB
+
+
+
220
+
1,048,576
+
1 million
+
1 MB
+
+
+
230
+
1,073,741,824
+
1 billion
+
1 GB
+
+
+
232
+
4,294,967,296
+
—
+
4 GB
+
+
+
240
+
1,099,511,627,776
+
1 trillion
+
1 TB
+
+
+
+
+
+
+
+
+
+
Quick Mental Math Tips
+
Useful shortcuts for estimation
+
+
+
+
1 KB ≈ 1 thousand bytes — a short email or text snippet
+
1 MB ≈ 1 million bytes — a high-resolution photo or a minute of MP3 audio
+
1 GB ≈ 1 billion bytes — a movie in SD or ~1000 photos
+
1 TB ≈ 1 trillion bytes — roughly 500 hours of HD video
Memorize these order-of-magnitude latency figures to make quick, informed decisions during system design discussions.
+
+
+
+
+
+
+ Core Reference
+
+
These numbers give you an intuitive sense of the relative costs of different operations. Knowing that a main memory reference is ~200× faster than an SSD read can guide caching and architecture decisions instantly.
+
+
+
Latency Comparison Numbers
+
+
+
+
+
+
Operation
+
Latency (ns)
+
Latency (μs)
+
Latency (ms)
+
Notes
+
+
+
+
+
L1 cache reference
+
0.5 ns
+
—
+
—
+
—
+
+
+
Branch mispredict
+
5 ns
+
—
+
—
+
—
+
+
+
L2 cache reference
+
7 ns
+
—
+
—
+
14× L1 cache
+
+
+
Mutex lock/unlock
+
25 ns
+
—
+
—
+
—
+
+
+
Main memory reference
+
100 ns
+
—
+
—
+
20× L2 cache, 200× L1 cache
+
+
+
Compress 1K bytes with Zippy
+
10,000 ns
+
10 μs
+
—
+
—
+
+
+
Send 1 KB over 1 Gbps network
+
10,000 ns
+
10 μs
+
—
+
—
+
+
+
Read 4 KB randomly from SSD*
+
150,000 ns
+
150 μs
+
—
+
~1 GB/sec SSD
+
+
+
Read 1 MB sequentially from memory
+
250,000 ns
+
250 μs
+
—
+
—
+
+
+
Round trip within same datacenter
+
500,000 ns
+
500 μs
+
—
+
—
+
+
+
Read 1 MB sequentially from SSD*
+
1,000,000 ns
+
1,000 μs
+
1 ms
+
~1 GB/sec SSD, 4× memory
+
+
+
HDD seek
+
10,000,000 ns
+
10,000 μs
+
10 ms
+
20× datacenter roundtrip
+
+
+
Read 1 MB sequentially from 1 Gbps
+
10,000,000 ns
+
10,000 μs
+
10 ms
+
40× memory, 10× SSD
+
+
+
Read 1 MB sequentially from HDD
+
30,000,000 ns
+
30,000 μs
+
30 ms
+
120× memory, 30× SSD
+
+
+
Send packet CA → Netherlands → CA
+
150,000,000 ns
+
150,000 μs
+
150 ms
+
—
+
+
+
+
+
+
+
+
Unit Conversions
+
+
+
+
1 ns = 10−9 seconds
+
1 μs = 10−6 seconds = 1,000 ns
+
1 ms = 10−3 seconds = 1,000 μs = 1,000,000 ns
+
+
+
+
+
+
+
Handy Metrics
+
+
+
+
Throughput & Round-Trip Rules of Thumb
+
Derived from the latency numbers above
+
+
+
+
HDD Read sequentially from HDD at 30 MB/s
+
Network Read sequentially from 1 Gbps Ethernet at 100 MB/s
+
SSD Read sequentially from SSD at 1 GB/s
+
Memory Read sequentially from main memory at 4 GB/s
+
WAN6–7 world-wide round trips per second
+
DC2,000 round trips per second within a data center
Common system design interview questions with links to resources on how to solve each — use these for extra practice beyond the core exercises.
+
+
+
+
+
+
+ Practice Tip
+
+
Each of these questions can be approached using the same structured framework: clarify requirements → estimate scale → define API → design high-level architecture → deep-dive into components → identify bottlenecks. The linked references show how experts think through each problem.
Articles on how real-world systems are designed — study the shared principles, common technologies, and patterns across these architectures.
+
+
+
+
+
+
+ How to Study These
+
+
Don't focus on nitty-gritty details for the following articles. Instead: identify shared principles, common technologies, and patterns. Study what problems are solved by each component, where it works, and where it doesn't. Review the lessons learned.
Architectures for companies you are interviewing with — questions you encounter might be from the same domain.
+
+
+
+
+
+
+ Interview Prep Tip
+
+
Before interviewing at a company, read their engineering blog. It gives you insight into the technologies they use, the scale they operate at, and the problems they've solved — all of which can come up in system design interviews.
+
+
+
Engineering Blogs Directory
+
+
+
+
Company Engineering Blogs
+
Alphabetical list of engineering blogs from top tech companies
Looking to add a blog? To avoid duplicating work, consider adding your company blog to: kilimchoi/engineering-blogs
+
+
+
+ `
+ },
+
+};
diff --git a/website/js/quizzes.js b/website/js/quizzes.js
new file mode 100644
index 00000000000..5a3d631d08c
--- /dev/null
+++ b/website/js/quizzes.js
@@ -0,0 +1,323 @@
+/**
+ * Quiz data for System Design Primer chapters.
+ * Each key matches a chapter key from CHAPTERS.
+ * Questions: { q: question, options: [A,B,C,D], answer: 0-based index, explanation: string }
+ */
+var QUIZZES = {
+ 'performance-scalability': [
+ {
+ q: 'What is the key difference between performance and scalability?',
+ options: [
+ 'Performance measures speed, scalability measures cost',
+ 'A service has a performance problem if it is slow for a single user; a scalability problem if it is fast for one but slow under load',
+ 'Performance and scalability are the same concept',
+ 'Scalability only applies to databases'
+ ],
+ answer: 1,
+ explanation: 'A performance problem means the system is slow even for one user. A scalability problem means it works for one user but degrades under load.'
+ },
+ {
+ q: 'Which scaling approach adds more machines to a pool of resources?',
+ options: [
+ 'Vertical scaling',
+ 'Diagonal scaling',
+ 'Horizontal scaling',
+ 'Functional scaling'
+ ],
+ answer: 2,
+ explanation: 'Horizontal scaling (scaling out) adds more machines, while vertical scaling (scaling up) adds more resources (CPU, RAM) to a single machine.'
+ }
+ ],
+
+ 'latency-throughput': [
+ {
+ q: 'What is latency?',
+ options: [
+ 'The number of requests processed per second',
+ 'The time to perform some action or to produce some result',
+ 'The total bandwidth of a network',
+ 'The amount of data stored in memory'
+ ],
+ answer: 1,
+ explanation: 'Latency is the time to perform some action or produce some result, typically measured in milliseconds.'
+ },
+ {
+ q: 'What should you generally aim for to optimize system performance?',
+ options: [
+ 'Maximum throughput with zero latency',
+ 'Minimum latency at the cost of throughput',
+ 'Maximum throughput with acceptable latency',
+ 'Equal latency and throughput values'
+ ],
+ answer: 2,
+ explanation: 'Generally, you should aim for maximal throughput with acceptable latency.'
+ }
+ ],
+
+ 'availability-consistency': [
+ {
+ q: 'According to the CAP theorem, which three properties cannot all be guaranteed simultaneously in a distributed system?',
+ options: [
+ 'Cost, Availability, Performance',
+ 'Consistency, Availability, Partition Tolerance',
+ 'Concurrency, Atomicity, Persistence',
+ 'Caching, Authorization, Privacy'
+ ],
+ answer: 1,
+ explanation: 'The CAP theorem states that a distributed system cannot simultaneously guarantee Consistency, Availability, and Partition Tolerance. You must choose two.'
+ },
+ {
+ q: 'In a CP system, what happens during a network partition?',
+ options: [
+ 'The system continues serving potentially stale data',
+ 'The system shuts down entirely',
+ 'The system may return errors or timeouts waiting for consistency',
+ 'All data is deleted'
+ ],
+ answer: 2,
+ explanation: 'CP systems prioritize consistency, so during a partition they may return errors or time out rather than serve potentially inconsistent data.'
+ }
+ ],
+
+ 'consistency-patterns': [
+ {
+ q: 'In weak consistency, what happens after a write?',
+ options: [
+ 'All subsequent reads immediately see the write',
+ 'Reads may or may not see the write; a best-effort approach is taken',
+ 'The write is rejected if any node is unavailable',
+ 'The write is stored but never readable'
+ ],
+ answer: 1,
+ explanation: 'Weak consistency means after a write, reads may or may not see it. This is a best-effort approach seen in systems like memcached and VoIP.'
+ },
+ {
+ q: 'Which consistency pattern guarantees that a read will eventually see the last write?',
+ options: [
+ 'Strong consistency',
+ 'Weak consistency',
+ 'Eventual consistency',
+ 'Immediate consistency'
+ ],
+ answer: 2,
+ explanation: 'Eventual consistency guarantees that given enough time without new writes, all reads will return the last written value. Used in systems like DNS and email.'
+ }
+ ],
+
+ 'dns': [
+ {
+ q: 'What does DNS translate?',
+ options: [
+ 'IP addresses to MAC addresses',
+ 'Domain names to IP addresses',
+ 'URLs to HTML pages',
+ 'Port numbers to service names'
+ ],
+ answer: 1,
+ explanation: 'DNS (Domain Name System) translates domain names like www.example.com to IP addresses like 93.184.216.34.'
+ },
+ {
+ q: 'Which DNS record type maps a name to another name?',
+ options: [
+ 'A record',
+ 'AAAA record',
+ 'CNAME record',
+ 'MX record'
+ ],
+ answer: 2,
+ explanation: 'A CNAME (Canonical Name) record maps a name to another name, acting as an alias. A records map to IPv4 addresses.'
+ }
+ ],
+
+ 'cdn': [
+ {
+ q: 'What is the primary purpose of a CDN?',
+ options: [
+ 'To replace the origin server completely',
+ 'To serve content from locations closer to the user, reducing latency',
+ 'To compress all files on the server',
+ 'To encrypt all traffic'
+ ],
+ answer: 1,
+ explanation: 'CDNs serve content from edge locations geographically closer to users, significantly reducing latency.'
+ },
+ {
+ q: 'In a pull CDN, when is content fetched from the origin?',
+ options: [
+ 'As soon as it is created on the origin',
+ 'When the first user requests that content',
+ 'Every hour on a schedule',
+ 'Only when manually triggered'
+ ],
+ answer: 1,
+ explanation: 'Pull CDNs lazily fetch content from the origin server upon the first request, then cache it for subsequent requests based on TTL.'
+ }
+ ],
+
+ 'load-balancer': [
+ {
+ q: 'Which load balancing approach operates at the transport layer?',
+ options: [
+ 'Layer 7 load balancing',
+ 'Layer 4 load balancing',
+ 'Layer 3 load balancing',
+ 'Application-level proxy'
+ ],
+ answer: 1,
+ explanation: 'Layer 4 load balancing operates at the transport layer and makes routing decisions based on IP addresses and TCP/UDP ports without inspecting packet contents.'
+ },
+ {
+ q: 'What is a major disadvantage of load balancers?',
+ options: [
+ 'They make systems slower in all cases',
+ 'They can become a bottleneck or single point of failure if not redundant',
+ 'They cannot work with HTTPS traffic',
+ 'They increase latency by 10x'
+ ],
+ answer: 1,
+ explanation: 'A load balancer can become a bottleneck if under-provisioned or a single point of failure if there is no redundancy. Multiple load balancers in active-passive or active-active configuration mitigate this.'
+ }
+ ],
+
+ 'reverse-proxy': [
+ {
+ q: 'What is a reverse proxy?',
+ options: [
+ 'A proxy configured by the client to access blocked sites',
+ 'A web server that centralizes internal services and provides a unified interface to the public',
+ 'A backup server that activates when the primary fails',
+ 'A DNS server that reverses IP lookups'
+ ],
+ answer: 1,
+ explanation: 'A reverse proxy is a web server that centralizes internal services and provides unified interfaces to the public, handling tasks like SSL termination, compression, and caching.'
+ }
+ ],
+
+ 'application-layer': [
+ {
+ q: 'What is the key benefit of separating the web/application layer from the platform layer?',
+ options: [
+ 'It reduces the total number of servers needed',
+ 'It allows scaling and configuring each layer independently',
+ 'It eliminates the need for load balancers',
+ 'It makes the code run faster'
+ ],
+ answer: 1,
+ explanation: 'Separating layers allows you to scale web servers and platform/API servers independently based on their specific load patterns.'
+ }
+ ],
+
+ 'database': [
+ {
+ q: 'What is the primary trade-off between SQL and NoSQL databases?',
+ options: [
+ 'SQL is always faster than NoSQL',
+ 'NoSQL cannot store structured data',
+ 'SQL provides ACID guarantees while NoSQL often trades consistency for availability and performance',
+ 'NoSQL is always more expensive'
+ ],
+ answer: 2,
+ explanation: 'SQL databases provide strong ACID guarantees (Atomicity, Consistency, Isolation, Durability). NoSQL databases typically sacrifice some consistency (BASE model) in exchange for availability, partition tolerance, and horizontal scalability.'
+ },
+ {
+ q: 'What does master-slave replication provide?',
+ options: [
+ 'Two-way writes to both databases',
+ 'Read scalability by distributing reads across replicas, with writes going to the master',
+ 'Automatic sharding of data',
+ 'Zero-downtime migrations'
+ ],
+ answer: 1,
+ explanation: 'In master-slave replication, the master serves reads and writes, and replicates writes to slaves which serve read-only traffic. This scales read throughput.'
+ },
+ {
+ q: 'What is federation (functional partitioning) in databases?',
+ options: [
+ 'Splitting one database into many identical copies',
+ 'Splitting databases by function (e.g., users, products, forums each in separate DBs)',
+ 'Encrypting all database traffic',
+ 'Running the database in multiple data centers simultaneously'
+ ],
+ answer: 1,
+ explanation: 'Federation splits up databases by function, so instead of a single monolithic database, you have separate databases for users, products, etc., reducing read and write traffic to each.'
+ }
+ ],
+
+ 'cache': [
+ {
+ q: 'What is the "cache-aside" (lazy-loading) pattern?',
+ options: [
+ 'The application writes to cache and database simultaneously',
+ 'The application reads from cache; on miss, reads from DB and populates cache',
+ 'The cache automatically pulls from the database on a timer',
+ 'Data is never stored in the database, only in cache'
+ ],
+ answer: 1,
+ explanation: 'In cache-aside, the application first checks the cache. On a miss, it reads from the database, stores the result in the cache, and returns. This lazy loads only requested data.'
+ },
+ {
+ q: 'What is a disadvantage of write-through caching?',
+ options: [
+ 'Data in cache can become stale',
+ 'Most data written to the cache might never be read, wasting resources',
+ 'It cannot work with relational databases',
+ 'It requires manual cache invalidation'
+ ],
+ answer: 1,
+ explanation: 'Write-through writes to both cache and DB on every write. The downside is that most data written might never be read before it expires, wasting cache space.'
+ }
+ ],
+
+ 'asynchronism': [
+ {
+ q: 'What is the main benefit of message queues in asynchronous workflows?',
+ options: [
+ 'They make all operations faster',
+ 'They decouple producers from consumers, allowing independent scaling and failure isolation',
+ 'They eliminate the need for databases',
+ 'They provide real-time responses to users'
+ ],
+ answer: 1,
+ explanation: 'Message queues decouple the producer from the consumer. The producer can publish even if the consumer is down, and each can scale independently.'
+ }
+ ],
+
+ 'communication': [
+ {
+ q: 'What is a key difference between TCP and UDP?',
+ options: [
+ 'TCP is faster than UDP',
+ 'TCP ensures reliable, ordered delivery; UDP is connectionless with no delivery guarantees',
+ 'UDP supports encryption but TCP does not',
+ 'TCP is for web traffic only, UDP is for email only'
+ ],
+ answer: 1,
+ explanation: 'TCP provides reliable, ordered data delivery with connection establishment. UDP is connectionless and provides no guarantees, making it faster for use cases like video streaming and gaming.'
+ },
+ {
+ q: 'In REST, what does it mean that the API is stateless?',
+ options: [
+ 'The server stores no client data at all',
+ 'Each request contains all information needed; the server does not store session state between requests',
+ 'REST cannot use cookies',
+ 'The database has no state'
+ ],
+ answer: 1,
+ explanation: 'REST is stateless - each request from the client must contain all necessary information. The server does not store client context between requests.'
+ }
+ ],
+
+ 'security': [
+ {
+ q: 'What is the principle of least privilege?',
+ options: [
+ 'Give users the minimum number of passwords',
+ 'Grant each user/process only the minimum access required to perform their function',
+ 'Use the cheapest security solution available',
+ 'Limit the number of security administrators'
+ ],
+ answer: 1,
+ explanation: 'The principle of least privilege means giving each user, process, or system only the minimum access level required to perform their function, reducing the attack surface.'
+ }
+ ]
+};
diff --git a/website/solutions/object_oriented_design/call_center/__init__.py b/website/solutions/object_oriented_design/call_center/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/object_oriented_design/call_center/call_center.ipynb b/website/solutions/object_oriented_design/call_center/call_center.ipynb
new file mode 100644
index 00000000000..c540c6a6422
--- /dev/null
+++ b/website/solutions/object_oriented_design/call_center/call_center.ipynb
@@ -0,0 +1,206 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/system-design-primer)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Design a call center"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Constraints and assumptions\n",
+ "\n",
+ "* What levels of employees are in the call center?\n",
+ " * Operator, supervisor, director\n",
+ "* Can we assume operators always get the initial calls?\n",
+ " * Yes\n",
+ "* If there is no available operators or the operator can't handle the call, does the call go to the supervisors?\n",
+ " * Yes\n",
+ "* If there is no available supervisors or the supervisor can't handle the call, does the call go to the directors?\n",
+ " * Yes\n",
+ "* Can we assume the directors can handle all calls?\n",
+ " * Yes\n",
+ "* What happens if nobody can answer the call?\n",
+ " * It gets queued\n",
+ "* Do we need to handle 'VIP' calls where we put someone to the front of the line?\n",
+ " * No\n",
+ "* Can we assume inputs are valid or do we have to validate them?\n",
+ " * Assume they're valid"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Solution"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Overwriting call_center.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile call_center.py\n",
+ "from abc import ABCMeta, abstractmethod\n",
+ "from collections import deque\n",
+ "from enum import Enum\n",
+ "\n",
+ "\n",
+ "class Rank(Enum):\n",
+ "\n",
+ " OPERATOR = 0\n",
+ " SUPERVISOR = 1\n",
+ " DIRECTOR = 2\n",
+ "\n",
+ "\n",
+ "class Employee(metaclass=ABCMeta):\n",
+ "\n",
+ " def __init__(self, employee_id, name, rank, call_center):\n",
+ " self.employee_id = employee_id\n",
+ " self.name = name\n",
+ " self.rank = rank\n",
+ " self.call = None\n",
+ " self.call_center = call_center\n",
+ "\n",
+ " def take_call(self, call):\n",
+ " \"\"\"Assume the employee will always successfully take the call.\"\"\"\n",
+ " self.call = call\n",
+ " self.call.employee = self\n",
+ " self.call.state = CallState.IN_PROGRESS\n",
+ "\n",
+ " def complete_call(self):\n",
+ " self.call.state = CallState.COMPLETE\n",
+ " self.call_center.notify_call_completed(self.call)\n",
+ "\n",
+ " @abstractmethod\n",
+ " def escalate_call(self):\n",
+ " pass\n",
+ "\n",
+ " def _escalate_call(self):\n",
+ " self.call.state = CallState.READY\n",
+ " call = self.call\n",
+ " self.call = None\n",
+ " self.call_center.notify_call_escalated(call)\n",
+ "\n",
+ "\n",
+ "class Operator(Employee):\n",
+ "\n",
+ " def __init__(self, employee_id, name):\n",
+ " super(Operator, self).__init__(employee_id, name, Rank.OPERATOR)\n",
+ "\n",
+ " def escalate_call(self):\n",
+ " self.call.level = Rank.SUPERVISOR\n",
+ " self._escalate_call()\n",
+ "\n",
+ "\n",
+ "class Supervisor(Employee):\n",
+ "\n",
+ " def __init__(self, employee_id, name):\n",
+ " super(Operator, self).__init__(employee_id, name, Rank.SUPERVISOR)\n",
+ "\n",
+ " def escalate_call(self):\n",
+ " self.call.level = Rank.DIRECTOR\n",
+ " self._escalate_call()\n",
+ "\n",
+ "\n",
+ "class Director(Employee):\n",
+ "\n",
+ " def __init__(self, employee_id, name):\n",
+ " super(Operator, self).__init__(employee_id, name, Rank.DIRECTOR)\n",
+ "\n",
+ " def escalate_call(self):\n",
+ " raise NotImplemented('Directors must be able to handle any call')\n",
+ "\n",
+ "\n",
+ "class CallState(Enum):\n",
+ "\n",
+ " READY = 0\n",
+ " IN_PROGRESS = 1\n",
+ " COMPLETE = 2\n",
+ "\n",
+ "\n",
+ "class Call(object):\n",
+ "\n",
+ " def __init__(self, rank):\n",
+ " self.state = CallState.READY\n",
+ " self.rank = rank\n",
+ " self.employee = None\n",
+ "\n",
+ "\n",
+ "class CallCenter(object):\n",
+ "\n",
+ " def __init__(self, operators, supervisors, directors):\n",
+ " self.operators = operators\n",
+ " self.supervisors = supervisors\n",
+ " self.directors = directors\n",
+ " self.queued_calls = deque()\n",
+ "\n",
+ " def dispatch_call(self, call):\n",
+ " if call.rank not in (Rank.OPERATOR, Rank.SUPERVISOR, Rank.DIRECTOR):\n",
+ " raise ValueError('Invalid call rank: {}'.format(call.rank))\n",
+ " employee = None\n",
+ " if call.rank == Rank.OPERATOR:\n",
+ " employee = self._dispatch_call(call, self.operators)\n",
+ " if call.rank == Rank.SUPERVISOR or employee is None:\n",
+ " employee = self._dispatch_call(call, self.supervisors)\n",
+ " if call.rank == Rank.DIRECTOR or employee is None:\n",
+ " employee = self._dispatch_call(call, self.directors)\n",
+ " if employee is None:\n",
+ " self.queued_calls.append(call)\n",
+ "\n",
+ " def _dispatch_call(self, call, employees):\n",
+ " for employee in employees:\n",
+ " if employee.call is None:\n",
+ " employee.take_call(call)\n",
+ " return employee\n",
+ " return None\n",
+ "\n",
+ " def notify_call_escalated(self, call): # ...\n",
+ " def notify_call_completed(self, call): # ...\n",
+ " def dispatch_queued_call_to_newly_freed_employee(self, call, employee): # ..."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/website/solutions/object_oriented_design/call_center/call_center.py b/website/solutions/object_oriented_design/call_center/call_center.py
new file mode 100644
index 00000000000..1d5e7bc6bc1
--- /dev/null
+++ b/website/solutions/object_oriented_design/call_center/call_center.py
@@ -0,0 +1,122 @@
+from abc import ABCMeta, abstractmethod
+from collections import deque
+from enum import Enum
+
+
+class Rank(Enum):
+
+ OPERATOR = 0
+ SUPERVISOR = 1
+ DIRECTOR = 2
+
+
+class Employee(metaclass=ABCMeta):
+
+ def __init__(self, employee_id, name, rank, call_center):
+ self.employee_id = employee_id
+ self.name = name
+ self.rank = rank
+ self.call = None
+ self.call_center = call_center
+
+ def take_call(self, call):
+ """Assume the employee will always successfully take the call."""
+ self.call = call
+ self.call.employee = self
+ self.call.state = CallState.IN_PROGRESS
+
+ def complete_call(self):
+ self.call.state = CallState.COMPLETE
+ self.call_center.notify_call_completed(self.call)
+
+ @abstractmethod
+ def escalate_call(self):
+ pass
+
+ def _escalate_call(self):
+ self.call.state = CallState.READY
+ call = self.call
+ self.call = None
+ self.call_center.notify_call_escalated(call)
+
+
+class Operator(Employee):
+
+ def __init__(self, employee_id, name):
+ super(Operator, self).__init__(employee_id, name, Rank.OPERATOR)
+
+ def escalate_call(self):
+ self.call.level = Rank.SUPERVISOR
+ self._escalate_call()
+
+
+class Supervisor(Employee):
+
+ def __init__(self, employee_id, name):
+ super(Operator, self).__init__(employee_id, name, Rank.SUPERVISOR)
+
+ def escalate_call(self):
+ self.call.level = Rank.DIRECTOR
+ self._escalate_call()
+
+
+class Director(Employee):
+
+ def __init__(self, employee_id, name):
+ super(Operator, self).__init__(employee_id, name, Rank.DIRECTOR)
+
+ def escalate_call(self):
+ raise NotImplementedError('Directors must be able to handle any call')
+
+
+class CallState(Enum):
+
+ READY = 0
+ IN_PROGRESS = 1
+ COMPLETE = 2
+
+
+class Call(object):
+
+ def __init__(self, rank):
+ self.state = CallState.READY
+ self.rank = rank
+ self.employee = None
+
+
+class CallCenter(object):
+
+ def __init__(self, operators, supervisors, directors):
+ self.operators = operators
+ self.supervisors = supervisors
+ self.directors = directors
+ self.queued_calls = deque()
+
+ def dispatch_call(self, call):
+ if call.rank not in (Rank.OPERATOR, Rank.SUPERVISOR, Rank.DIRECTOR):
+ raise ValueError('Invalid call rank: {}'.format(call.rank))
+ employee = None
+ if call.rank == Rank.OPERATOR:
+ employee = self._dispatch_call(call, self.operators)
+ if call.rank == Rank.SUPERVISOR or employee is None:
+ employee = self._dispatch_call(call, self.supervisors)
+ if call.rank == Rank.DIRECTOR or employee is None:
+ employee = self._dispatch_call(call, self.directors)
+ if employee is None:
+ self.queued_calls.append(call)
+
+ def _dispatch_call(self, call, employees):
+ for employee in employees:
+ if employee.call is None:
+ employee.take_call(call)
+ return employee
+ return None
+
+ def notify_call_escalated(self, call):
+ pass
+
+ def notify_call_completed(self, call):
+ pass
+
+ def dispatch_queued_call_to_newly_freed_employee(self, call, employee):
+ pass
diff --git a/website/solutions/object_oriented_design/deck_of_cards/__init__.py b/website/solutions/object_oriented_design/deck_of_cards/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/object_oriented_design/deck_of_cards/deck_of_cards.ipynb b/website/solutions/object_oriented_design/deck_of_cards/deck_of_cards.ipynb
new file mode 100644
index 00000000000..1a9bc1c530b
--- /dev/null
+++ b/website/solutions/object_oriented_design/deck_of_cards/deck_of_cards.ipynb
@@ -0,0 +1,195 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/system-design-primer)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Design a deck of cards"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Constraints and assumptions\n",
+ "\n",
+ "* Is this a generic deck of cards for games like poker and black jack?\n",
+ " * Yes, design a generic deck then extend it to black jack\n",
+ "* Can we assume the deck has 52 cards (2-10, Jack, Queen, King, Ace) and 4 suits?\n",
+ " * Yes\n",
+ "* Can we assume inputs are valid or do we have to validate them?\n",
+ " * Assume they're valid"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Solution"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Overwriting deck_of_cards.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile deck_of_cards.py\n",
+ "from abc import ABCMeta, abstractmethod\n",
+ "from enum import Enum\n",
+ "import sys\n",
+ "\n",
+ "\n",
+ "class Suit(Enum):\n",
+ "\n",
+ " HEART = 0\n",
+ " DIAMOND = 1\n",
+ " CLUBS = 2\n",
+ " SPADE = 3\n",
+ "\n",
+ "\n",
+ "class Card(metaclass=ABCMeta):\n",
+ "\n",
+ " def __init__(self, value, suit):\n",
+ " self.value = value\n",
+ " self.suit = suit\n",
+ " self.is_available = True\n",
+ "\n",
+ " @property\n",
+ " @abstractmethod\n",
+ " def value(self):\n",
+ " pass\n",
+ "\n",
+ " @value.setter\n",
+ " @abstractmethod\n",
+ " def value(self, other):\n",
+ " pass\n",
+ "\n",
+ "\n",
+ "class BlackJackCard(Card):\n",
+ "\n",
+ " def __init__(self, value, suit):\n",
+ " super(BlackJackCard, self).__init__(value, suit)\n",
+ "\n",
+ " def is_ace(self):\n",
+ " return self._value == 1\n",
+ "\n",
+ " def is_face_card(self):\n",
+ " \"\"\"Jack = 11, Queen = 12, King = 13\"\"\"\n",
+ " return 10 < self._value <= 13\n",
+ "\n",
+ " @property\n",
+ " def value(self):\n",
+ " if self.is_ace() == 1:\n",
+ " return 1\n",
+ " elif self.is_face_card():\n",
+ " return 10\n",
+ " else:\n",
+ " return self._value\n",
+ "\n",
+ " @value.setter\n",
+ " def value(self, new_value):\n",
+ " if 1 <= new_value <= 13:\n",
+ " self._value = new_value\n",
+ " else:\n",
+ " raise ValueError('Invalid card value: {}'.format(new_value))\n",
+ "\n",
+ "\n",
+ "class Hand(object):\n",
+ "\n",
+ " def __init__(self, cards):\n",
+ " self.cards = cards\n",
+ "\n",
+ " def add_card(self, card):\n",
+ " self.cards.append(card)\n",
+ "\n",
+ " def score(self):\n",
+ " total_value = 0\n",
+ " for card in self.cards:\n",
+ " total_value += card.value\n",
+ " return total_value\n",
+ "\n",
+ "\n",
+ "class BlackJackHand(Hand):\n",
+ "\n",
+ " BLACKJACK = 21\n",
+ "\n",
+ " def __init__(self, cards):\n",
+ " super(BlackJackHand, self).__init__(cards)\n",
+ "\n",
+ " def score(self):\n",
+ " min_over = sys.MAXSIZE\n",
+ " max_under = -sys.MAXSIZE\n",
+ " for score in self.possible_scores():\n",
+ " if self.BLACKJACK < score < min_over:\n",
+ " min_over = score\n",
+ " elif max_under < score <= self.BLACKJACK:\n",
+ " max_under = score\n",
+ " return max_under if max_under != -sys.MAXSIZE else min_over\n",
+ "\n",
+ " def possible_scores(self):\n",
+ " \"\"\"Return a list of possible scores, taking Aces into account.\"\"\"\n",
+ " # ...\n",
+ "\n",
+ "\n",
+ "class Deck(object):\n",
+ "\n",
+ " def __init__(self, cards):\n",
+ " self.cards = cards\n",
+ " self.deal_index = 0\n",
+ "\n",
+ " def remaining_cards(self):\n",
+ " return len(self.cards) - deal_index\n",
+ "\n",
+ " def deal_card():\n",
+ " try:\n",
+ " card = self.cards[self.deal_index]\n",
+ " card.is_available = False\n",
+ " self.deal_index += 1\n",
+ " except IndexError:\n",
+ " return None\n",
+ " return card\n",
+ "\n",
+ " def shuffle(self): # ..."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/website/solutions/object_oriented_design/deck_of_cards/deck_of_cards.py b/website/solutions/object_oriented_design/deck_of_cards/deck_of_cards.py
new file mode 100644
index 00000000000..a47087585e0
--- /dev/null
+++ b/website/solutions/object_oriented_design/deck_of_cards/deck_of_cards.py
@@ -0,0 +1,117 @@
+from abc import ABCMeta, abstractmethod
+from enum import Enum
+import sys
+
+
+class Suit(Enum):
+
+ HEART = 0
+ DIAMOND = 1
+ CLUBS = 2
+ SPADE = 3
+
+
+class Card(metaclass=ABCMeta):
+
+ def __init__(self, value, suit):
+ self.value = value
+ self.suit = suit
+ self.is_available = True
+
+ @property
+ @abstractmethod
+ def value(self):
+ pass
+
+ @value.setter
+ @abstractmethod
+ def value(self, other):
+ pass
+
+
+class BlackJackCard(Card):
+
+ def __init__(self, value, suit):
+ super(BlackJackCard, self).__init__(value, suit)
+
+ def is_ace(self):
+ return True if self._value == 1 else False
+
+ def is_face_card(self):
+ """Jack = 11, Queen = 12, King = 13"""
+ return True if 10 < self._value <= 13 else False
+
+ @property
+ def value(self):
+ if self.is_ace() == 1:
+ return 1
+ elif self.is_face_card():
+ return 10
+ else:
+ return self._value
+
+ @value.setter
+ def value(self, new_value):
+ if 1 <= new_value <= 13:
+ self._value = new_value
+ else:
+ raise ValueError('Invalid card value: {}'.format(new_value))
+
+
+class Hand(object):
+
+ def __init__(self, cards):
+ self.cards = cards
+
+ def add_card(self, card):
+ self.cards.append(card)
+
+ def score(self):
+ total_value = 0
+ for card in self.cards:
+ total_value += card.value
+ return total_value
+
+
+class BlackJackHand(Hand):
+
+ BLACKJACK = 21
+
+ def __init__(self, cards):
+ super(BlackJackHand, self).__init__(cards)
+
+ def score(self):
+ min_over = sys.MAXSIZE
+ max_under = -sys.MAXSIZE
+ for score in self.possible_scores():
+ if self.BLACKJACK < score < min_over:
+ min_over = score
+ elif max_under < score <= self.BLACKJACK:
+ max_under = score
+ return max_under if max_under != -sys.MAXSIZE else min_over
+
+ def possible_scores(self):
+ """Return a list of possible scores, taking Aces into account."""
+ pass
+
+
+class Deck(object):
+
+ def __init__(self, cards):
+ self.cards = cards
+ self.deal_index = 0
+
+ def remaining_cards(self):
+ return len(self.cards) - self.deal_index
+
+ def deal_card(self):
+ try:
+ card = self.cards[self.deal_index]
+ card.is_available = False
+ self.deal_index += 1
+ except IndexError:
+ return None
+ return card
+
+ def shuffle(self):
+ pass
diff --git a/website/solutions/object_oriented_design/hash_table/__init__.py b/website/solutions/object_oriented_design/hash_table/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/object_oriented_design/hash_table/hash_map.ipynb b/website/solutions/object_oriented_design/hash_table/hash_map.ipynb
new file mode 100644
index 00000000000..92713d94bb7
--- /dev/null
+++ b/website/solutions/object_oriented_design/hash_table/hash_map.ipynb
@@ -0,0 +1,121 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/system-design-primer)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Design a hash map"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Constraints and assumptions\n",
+ "\n",
+ "* For simplicity, are the keys integers only?\n",
+ " * Yes\n",
+ "* For collision resolution, can we use chaining?\n",
+ " * Yes\n",
+ "* Do we have to worry about load factors?\n",
+ " * No\n",
+ "* Can we assume inputs are valid or do we have to validate them?\n",
+ " * Assume they're valid\n",
+ "* Can we assume this fits memory?\n",
+ " * Yes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Solution"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Overwriting hash_map.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile hash_map.py\n",
+ "class Item(object):\n",
+ "\n",
+ " def __init__(self, key, value):\n",
+ " self.key = key\n",
+ " self.value = value\n",
+ "\n",
+ "\n",
+ "class HashTable(object):\n",
+ "\n",
+ " def __init__(self, size):\n",
+ " self.size = size\n",
+ " self.table = [[] for _ in range(self.size)]\n",
+ "\n",
+ " def _hash_function(self, key):\n",
+ " return key % self.size\n",
+ "\n",
+ " def set(self, key, value):\n",
+ " hash_index = self._hash_function(key)\n",
+ " for item in self.table[hash_index]:\n",
+ " if item.key == key:\n",
+ " item.value = value\n",
+ " return\n",
+ " self.table[hash_index].append(Item(key, value))\n",
+ "\n",
+ " def get(self, key):\n",
+ " hash_index = self._hash_function(key)\n",
+ " for item in self.table[hash_index]:\n",
+ " if item.key == key:\n",
+ " return item.value\n",
+ " raise KeyError('Key not found')\n",
+ "\n",
+ " def remove(self, key):\n",
+ " hash_index = self._hash_function(key)\n",
+ " for index, item in enumerate(self.table[hash_index]):\n",
+ " if item.key == key:\n",
+ " del self.table[hash_index][index]\n",
+ " return\n",
+ " raise KeyError('Key not found')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/website/solutions/object_oriented_design/hash_table/hash_map.py b/website/solutions/object_oriented_design/hash_table/hash_map.py
new file mode 100644
index 00000000000..33d9a35de15
--- /dev/null
+++ b/website/solutions/object_oriented_design/hash_table/hash_map.py
@@ -0,0 +1,38 @@
+class Item(object):
+
+ def __init__(self, key, value):
+ self.key = key
+ self.value = value
+
+
+class HashTable(object):
+
+ def __init__(self, size):
+ self.size = size
+ self.table = [[] for _ in range(self.size)]
+
+ def _hash_function(self, key):
+ return key % self.size
+
+ def set(self, key, value):
+ hash_index = self._hash_function(key)
+ for item in self.table[hash_index]:
+ if item.key == key:
+ item.value = value
+ return
+ self.table[hash_index].append(Item(key, value))
+
+ def get(self, key):
+ hash_index = self._hash_function(key)
+ for item in self.table[hash_index]:
+ if item.key == key:
+ return item.value
+ raise KeyError('Key not found')
+
+ def remove(self, key):
+ hash_index = self._hash_function(key)
+ for index, item in enumerate(self.table[hash_index]):
+ if item.key == key:
+ del self.table[hash_index][index]
+ return
+ raise KeyError('Key not found')
diff --git a/website/solutions/object_oriented_design/lru_cache/__init__.py b/website/solutions/object_oriented_design/lru_cache/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/object_oriented_design/lru_cache/lru_cache.ipynb b/website/solutions/object_oriented_design/lru_cache/lru_cache.ipynb
new file mode 100644
index 00000000000..cd91da11ba5
--- /dev/null
+++ b/website/solutions/object_oriented_design/lru_cache/lru_cache.ipynb
@@ -0,0 +1,141 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/system-design-primer)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Design an LRU cache"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Constraints and assumptions\n",
+ "\n",
+ "* What are we caching?\n",
+ " * We are caching the results of web queries\n",
+ "* Can we assume inputs are valid or do we have to validate them?\n",
+ " * Assume they're valid\n",
+ "* Can we assume this fits memory?\n",
+ " * Yes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Solution"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Overwriting lru_cache.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile lru_cache.py\n",
+ "class Node(object):\n",
+ "\n",
+ " def __init__(self, results):\n",
+ " self.results = results\n",
+ " self.prev = None\n",
+ " self.next = None\n",
+ "\n",
+ "\n",
+ "class LinkedList(object):\n",
+ "\n",
+ " def __init__(self):\n",
+ " self.head = None\n",
+ " self.tail = None\n",
+ "\n",
+ " def move_to_front(self, node): # ...\n",
+ " def append_to_front(self, node): # ...\n",
+ " def remove_from_tail(self): # ...\n",
+ "\n",
+ "\n",
+ "class Cache(object):\n",
+ "\n",
+ " def __init__(self, MAX_SIZE):\n",
+ " self.MAX_SIZE = MAX_SIZE\n",
+ " self.size = 0\n",
+ " self.lookup = {} # key: query, value: node\n",
+ " self.linked_list = LinkedList()\n",
+ "\n",
+ " def get(self, query)\n",
+ " \"\"\"Get the stored query result from the cache.\n",
+ " \n",
+ " Accessing a node updates its position to the front of the LRU list.\n",
+ " \"\"\"\n",
+ " node = self.lookup.get(query)\n",
+ " if node is None:\n",
+ " return None\n",
+ " self.linked_list.move_to_front(node)\n",
+ " return node.results\n",
+ "\n",
+ " def set(self, results, query):\n",
+ " \"\"\"Set the result for the given query key in the cache.\n",
+ " \n",
+ " When updating an entry, updates its position to the front of the LRU list.\n",
+ " If the entry is new and the cache is at capacity, removes the oldest entry\n",
+ " before the new entry is added.\n",
+ " \"\"\"\n",
+ " node = self.lookup.get(query)\n",
+ " if node is not None:\n",
+ " # Key exists in cache, update the value\n",
+ " node.results = results\n",
+ " self.linked_list.move_to_front(node)\n",
+ " else:\n",
+ " # Key does not exist in cache\n",
+ " if self.size == self.MAX_SIZE:\n",
+ " # Remove the oldest entry from the linked list and lookup\n",
+ " self.lookup.pop(self.linked_list.tail.query, None)\n",
+ " self.linked_list.remove_from_tail()\n",
+ " else:\n",
+ " self.size += 1\n",
+ " # Add the new key and value\n",
+ " new_node = Node(results)\n",
+ " self.linked_list.append_to_front(new_node)\n",
+ " self.lookup[query] = new_node"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/website/solutions/object_oriented_design/lru_cache/lru_cache.py b/website/solutions/object_oriented_design/lru_cache/lru_cache.py
new file mode 100644
index 00000000000..acee46516eb
--- /dev/null
+++ b/website/solutions/object_oriented_design/lru_cache/lru_cache.py
@@ -0,0 +1,66 @@
+class Node(object):
+
+ def __init__(self, results):
+ self.results = results
+ self.next = next
+
+
+class LinkedList(object):
+
+ def __init__(self):
+ self.head = None
+ self.tail = None
+
+ def move_to_front(self, node):
+ pass
+
+ def append_to_front(self, node):
+ pass
+
+ def remove_from_tail(self):
+ pass
+
+
+class Cache(object):
+
+ def __init__(self, MAX_SIZE):
+ self.MAX_SIZE = MAX_SIZE
+ self.size = 0
+ self.lookup = {} # key: query, value: node
+ self.linked_list = LinkedList()
+
+ def get(self, query):
+ """Get the stored query result from the cache.
+
+ Accessing a node updates its position to the front of the LRU list.
+ """
+ node = self.lookup.get(query)
+ if node is None:
+ return None
+ self.linked_list.move_to_front(node)
+ return node.results
+
+ def set(self, results, query):
+ """Set the result for the given query key in the cache.
+
+ When updating an entry, updates its position to the front of the LRU list.
+ If the entry is new and the cache is at capacity, removes the oldest entry
+ before the new entry is added.
+ """
+ node = self.lookup.get(query)
+ if node is not None:
+ # Key exists in cache, update the value
+ node.results = results
+ self.linked_list.move_to_front(node)
+ else:
+ # Key does not exist in cache
+ if self.size == self.MAX_SIZE:
+ # Remove the oldest entry from the linked list and lookup
+ self.lookup.pop(self.linked_list.tail.query, None)
+ self.linked_list.remove_from_tail()
+ else:
+ self.size += 1
+ # Add the new key and value
+ new_node = Node(results)
+ self.linked_list.append_to_front(new_node)
+ self.lookup[query] = new_node
diff --git a/website/solutions/object_oriented_design/online_chat/__init__.py b/website/solutions/object_oriented_design/online_chat/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/object_oriented_design/online_chat/online_chat.ipynb b/website/solutions/object_oriented_design/online_chat/online_chat.ipynb
new file mode 100644
index 00000000000..b9f84ef424e
--- /dev/null
+++ b/website/solutions/object_oriented_design/online_chat/online_chat.ipynb
@@ -0,0 +1,171 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/system-design-primer)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Design an online chat"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Constraints and assumptions\n",
+ "\n",
+ "* Assume we'll focus on the following workflows:\n",
+ " * Text conversations only\n",
+ " * Users\n",
+ " * Add a user\n",
+ " * Remove a user\n",
+ " * Update a user\n",
+ " * Add to a user's friends list\n",
+ " * Add friend request\n",
+ " * Approve friend request\n",
+ " * Reject friend request\n",
+ " * Remove from a user's friends list\n",
+ " * Create a group chat\n",
+ " * Invite friends to a group chat\n",
+ " * Post a message to a group chat\n",
+ " * Private 1-1 chat\n",
+ " * Invite a friend to a private chat\n",
+ " * Post a meesage to a private chat\n",
+ "* No need to worry about scaling initially"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Solution"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Overwriting online_chat.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile online_chat.py\n",
+ "from abc import ABCMeta\n",
+ "\n",
+ "\n",
+ "class UserService(object):\n",
+ "\n",
+ " def __init__(self):\n",
+ " self.users_by_id = {} # key: user id, value: User\n",
+ "\n",
+ " def add_user(self, user_id, name, pass_hash): # ...\n",
+ " def remove_user(self, user_id): # ...\n",
+ " def add_friend_request(self, from_user_id, to_user_id): # ...\n",
+ " def approve_friend_request(self, from_user_id, to_user_id): # ...\n",
+ " def reject_friend_request(self, from_user_id, to_user_id): # ...\n",
+ "\n",
+ "\n",
+ "class User(object):\n",
+ "\n",
+ " def __init__(self, user_id, name, pass_hash):\n",
+ " self.user_id = user_id\n",
+ " self.name = name\n",
+ " self.pass_hash = pass_hash\n",
+ " self.friends_by_id = {} # key: friend id, value: User\n",
+ " self.friend_ids_to_private_chats = {} # key: friend id, value: private chats\n",
+ " self.group_chats_by_id = {} # key: chat id, value: GroupChat\n",
+ " self.received_friend_requests_by_friend_id = {} # key: friend id, value: AddRequest\n",
+ " self.sent_friend_requests_by_friend_id = {} # key: friend id, value: AddRequest\n",
+ "\n",
+ " def message_user(self, friend_id, message): # ...\n",
+ " def message_group(self, group_id, message): # ...\n",
+ " def send_friend_request(self, friend_id): # ...\n",
+ " def receive_friend_request(self, friend_id): # ...\n",
+ " def approve_friend_request(self, friend_id): # ...\n",
+ " def reject_friend_request(self, friend_id): # ...\n",
+ "\n",
+ "\n",
+ "class Chat(metaclass=ABCMeta):\n",
+ "\n",
+ " def __init__(self, chat_id):\n",
+ " self.chat_id = chat_id\n",
+ " self.users = []\n",
+ " self.messages = []\n",
+ "\n",
+ "\n",
+ "class PrivateChat(Chat):\n",
+ "\n",
+ " def __init__(self, first_user, second_user):\n",
+ " super(PrivateChat, self).__init__()\n",
+ " self.users.append(first_user)\n",
+ " self.users.append(second_user)\n",
+ "\n",
+ "\n",
+ "class GroupChat(Chat):\n",
+ "\n",
+ " def add_user(self, user): # ...\n",
+ " def remove_user(self, user): # ... \n",
+ "\n",
+ "\n",
+ "class Message(object):\n",
+ "\n",
+ " def __init__(self, message_id, message, timestamp):\n",
+ " self.message_id = message_id\n",
+ " self.message = message\n",
+ " self.timestamp = timestamp\n",
+ "\n",
+ "\n",
+ "class AddRequest(object):\n",
+ "\n",
+ " def __init__(self, from_user_id, to_user_id, request_status, timestamp):\n",
+ " self.from_user_id = from_user_id\n",
+ " self.to_user_id = to_user_id\n",
+ " self.request_status = request_status\n",
+ " self.timestamp = timestamp\n",
+ "\n",
+ "\n",
+ "class RequestStatus(Enum):\n",
+ "\n",
+ " UNREAD = 0\n",
+ " READ = 1\n",
+ " ACCEPTED = 2\n",
+ " REJECTED = 3"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/website/solutions/object_oriented_design/online_chat/online_chat.py b/website/solutions/object_oriented_design/online_chat/online_chat.py
new file mode 100644
index 00000000000..7063ca04426
--- /dev/null
+++ b/website/solutions/object_oriented_design/online_chat/online_chat.py
@@ -0,0 +1,104 @@
+from abc import ABCMeta
+from enum import Enum
+
+
+class UserService(object):
+
+ def __init__(self):
+ self.users_by_id = {} # key: user id, value: User
+
+ def add_user(self, user_id, name, pass_hash):
+ pass
+
+ def remove_user(self, user_id):
+ pass
+
+ def add_friend_request(self, from_user_id, to_user_id):
+ pass
+
+ def approve_friend_request(self, from_user_id, to_user_id):
+ pass
+
+ def reject_friend_request(self, from_user_id, to_user_id):
+ pass
+
+
+class User(object):
+
+ def __init__(self, user_id, name, pass_hash):
+ self.user_id = user_id
+ self.name = name
+ self.pass_hash = pass_hash
+ self.friends_by_id = {} # key: friend id, value: User
+ self.friend_ids_to_private_chats = {} # key: friend id, value: private chats
+ self.group_chats_by_id = {} # key: chat id, value: GroupChat
+ self.received_friend_requests_by_friend_id = {} # key: friend id, value: AddRequest
+ self.sent_friend_requests_by_friend_id = {} # key: friend id, value: AddRequest
+
+ def message_user(self, friend_id, message):
+ pass
+
+ def message_group(self, group_id, message):
+ pass
+
+ def send_friend_request(self, friend_id):
+ pass
+
+ def receive_friend_request(self, friend_id):
+ pass
+
+ def approve_friend_request(self, friend_id):
+ pass
+
+ def reject_friend_request(self, friend_id):
+ pass
+
+
+class Chat(metaclass=ABCMeta):
+
+ def __init__(self, chat_id):
+ self.chat_id = chat_id
+ self.users = []
+ self.messages = []
+
+
+class PrivateChat(Chat):
+
+ def __init__(self, first_user, second_user):
+ super(PrivateChat, self).__init__()
+ self.users.append(first_user)
+ self.users.append(second_user)
+
+
+class GroupChat(Chat):
+
+ def add_user(self, user):
+ pass
+
+ def remove_user(self, user):
+ pass
+
+
+class Message(object):
+
+ def __init__(self, message_id, message, timestamp):
+ self.message_id = message_id
+ self.message = message
+ self.timestamp = timestamp
+
+
+class AddRequest(object):
+
+ def __init__(self, from_user_id, to_user_id, request_status, timestamp):
+ self.from_user_id = from_user_id
+ self.to_user_id = to_user_id
+ self.request_status = request_status
+ self.timestamp = timestamp
+
+
+class RequestStatus(Enum):
+
+ UNREAD = 0
+ READ = 1
+ ACCEPTED = 2
+ REJECTED = 3
diff --git a/website/solutions/object_oriented_design/parking_lot/__init__.py b/website/solutions/object_oriented_design/parking_lot/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/object_oriented_design/parking_lot/parking_lot.ipynb b/website/solutions/object_oriented_design/parking_lot/parking_lot.ipynb
new file mode 100644
index 00000000000..4613b79bded
--- /dev/null
+++ b/website/solutions/object_oriented_design/parking_lot/parking_lot.ipynb
@@ -0,0 +1,204 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook was prepared by [Donne Martin](https://github.com/donnemartin). Source and license info is on [GitHub](https://github.com/donnemartin/system-design-primer)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Design a parking lot"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Constraints and assumptions\n",
+ "\n",
+ "* What types of vehicles should we support?\n",
+ " * Motorcycle, Car, Bus\n",
+ "* Does each vehicle type take up a different amount of parking spots?\n",
+ " * Yes\n",
+ " * Motorcycle spot -> Motorcycle\n",
+ " * Compact spot -> Motorcycle, Car\n",
+ " * Large spot -> Motorcycle, Car\n",
+ " * Bus can park if we have 5 consecutive \"large\" spots\n",
+ "* Does the parking lot have multiple levels?\n",
+ " * Yes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Solution"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Overwriting parking_lot.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "%%writefile parking_lot.py\n",
+ "from abc import ABCMeta, abstractmethod\n",
+ "\n",
+ "\n",
+ "class VehicleSize(Enum):\n",
+ "\n",
+ " MOTORCYCLE = 0\n",
+ " COMPACT = 1\n",
+ " LARGE = 2\n",
+ "\n",
+ "\n",
+ "class Vehicle(metaclass=ABCMeta):\n",
+ "\n",
+ " def __init__(self, vehicle_size, license_plate, spot_size):\n",
+ " self.vehicle_size = vehicle_size\n",
+ " self.license_plate = license_plate\n",
+ " self.spot_size = spot_size\n",
+ " self.spots_taken = []\n",
+ "\n",
+ " def clear_spots(self):\n",
+ " for spot in self.spots_taken:\n",
+ " spot.remove_vehicle(self)\n",
+ " self.spots_taken = []\n",
+ "\n",
+ " def take_spot(self, spot):\n",
+ " self.spots_taken.append(spot)\n",
+ "\n",
+ " @abstractmethod\n",
+ " def can_fit_in_spot(self, spot):\n",
+ " pass\n",
+ "\n",
+ "\n",
+ "class Motorcycle(Vehicle):\n",
+ "\n",
+ " def __init__(self, license_plate):\n",
+ " super(Motorcycle, self).__init__(VehicleSize.MOTORCYCLE, license_plate, spot_size=1)\n",
+ "\n",
+ " def can_fit_in_spot(self, spot):\n",
+ " return True\n",
+ "\n",
+ "\n",
+ "class Car(Vehicle):\n",
+ "\n",
+ " def __init__(self, license_plate):\n",
+ " super(Car, self).__init__(VehicleSize.COMPACT, license_plate, spot_size=1)\n",
+ "\n",
+ " def can_fit_in_spot(self, spot):\n",
+ " return True if (spot.size == LARGE or spot.size == COMPACT) else False\n",
+ "\n",
+ "\n",
+ "class Bus(Vehicle):\n",
+ "\n",
+ " def __init__(self, license_plate):\n",
+ " super(Bus, self).__init__(VehicleSize.LARGE, license_plate, spot_size=5)\n",
+ "\n",
+ " def can_fit_in_spot(self, spot):\n",
+ " return True if spot.size == LARGE else False\n",
+ "\n",
+ "\n",
+ "class ParkingLot(object):\n",
+ "\n",
+ " def __init__(self, num_levels):\n",
+ " self.num_levels = num_levels\n",
+ " self.levels = []\n",
+ "\n",
+ " def park_vehicle(self, vehicle):\n",
+ " for level in levels:\n",
+ " if level.park_vehicle(vehicle):\n",
+ " return True\n",
+ " return False\n",
+ "\n",
+ "\n",
+ "class Level(object):\n",
+ "\n",
+ " SPOTS_PER_ROW = 10\n",
+ "\n",
+ " def __init__(self, floor, total_spots):\n",
+ " self.floor = floor\n",
+ " self.num_spots = total_spots\n",
+ " self.available_spots = 0\n",
+ " self.parking_spots = []\n",
+ "\n",
+ " def spot_freed(self):\n",
+ " self.available_spots += 1\n",
+ "\n",
+ " def park_vehicle(self, vehicle):\n",
+ " spot = self._find_available_spot(vehicle)\n",
+ " if spot is None:\n",
+ " return None\n",
+ " else:\n",
+ " spot.park_vehicle(vehicle)\n",
+ " return spot\n",
+ "\n",
+ " def _find_available_spot(self, vehicle):\n",
+ " \"\"\"Find an available spot where vehicle can fit, or return None\"\"\"\n",
+ " # ...\n",
+ "\n",
+ " def _park_starting_at_spot(self, spot, vehicle):\n",
+ " \"\"\"Occupy starting at spot.spot_number to vehicle.spot_size.\"\"\"\n",
+ " # ...\n",
+ "\n",
+ "\n",
+ "class ParkingSpot(object):\n",
+ "\n",
+ " def __init__(self, level, row, spot_number, spot_size, vehicle_size):\n",
+ " self.level = level\n",
+ " self.row = row\n",
+ " self.spot_number = spot_number\n",
+ " self.spot_size = spot_size\n",
+ " self.vehicle_size = vehicle_size\n",
+ " self.vehicle = None\n",
+ "\n",
+ " def is_available(self):\n",
+ " return True if self.vehicle is None else False\n",
+ "\n",
+ " def can_fit_vehicle(self, vehicle):\n",
+ " if self.vehicle is not None:\n",
+ " return False\n",
+ " return vehicle.can_fit_in_spot(self)\n",
+ "\n",
+ " def park_vehicle(self, vehicle): # ...\n",
+ " def remove_vehicle(self): # ..."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.4.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/website/solutions/object_oriented_design/parking_lot/parking_lot.py b/website/solutions/object_oriented_design/parking_lot/parking_lot.py
new file mode 100644
index 00000000000..08852d9dc22
--- /dev/null
+++ b/website/solutions/object_oriented_design/parking_lot/parking_lot.py
@@ -0,0 +1,125 @@
+from abc import ABCMeta, abstractmethod
+from enum import Enum
+
+
+class VehicleSize(Enum):
+
+ MOTORCYCLE = 0
+ COMPACT = 1
+ LARGE = 2
+
+
+class Vehicle(metaclass=ABCMeta):
+
+ def __init__(self, vehicle_size, license_plate, spot_size):
+ self.vehicle_size = vehicle_size
+ self.license_plate = license_plate
+ self.spot_size
+ self.spots_taken = []
+
+ def clear_spots(self):
+ for spot in self.spots_taken:
+ spot.remove_vehicle(self)
+ self.spots_taken = []
+
+ def take_spot(self, spot):
+ self.spots_taken.append(spot)
+
+ @abstractmethod
+ def can_fit_in_spot(self, spot):
+ pass
+
+
+class Motorcycle(Vehicle):
+
+ def __init__(self, license_plate):
+ super(Motorcycle, self).__init__(VehicleSize.MOTORCYCLE, license_plate, spot_size=1)
+
+ def can_fit_in_spot(self, spot):
+ return True
+
+
+class Car(Vehicle):
+
+ def __init__(self, license_plate):
+ super(Car, self).__init__(VehicleSize.COMPACT, license_plate, spot_size=1)
+
+ def can_fit_in_spot(self, spot):
+ return spot.size in (VehicleSize.LARGE, VehicleSize.COMPACT)
+
+
+class Bus(Vehicle):
+
+ def __init__(self, license_plate):
+ super(Bus, self).__init__(VehicleSize.LARGE, license_plate, spot_size=5)
+
+ def can_fit_in_spot(self, spot):
+ return spot.size == VehicleSize.LARGE
+
+
+class ParkingLot(object):
+
+ def __init__(self, num_levels):
+ self.num_levels = num_levels
+ self.levels = [] # List of Levels
+
+ def park_vehicle(self, vehicle):
+ for level in self.levels:
+ if level.park_vehicle(vehicle):
+ return True
+ return False
+
+
+class Level(object):
+
+ SPOTS_PER_ROW = 10
+
+ def __init__(self, floor, total_spots):
+ self.floor = floor
+ self.num_spots = total_spots
+ self.available_spots = 0
+ self.spots = [] # List of ParkingSpots
+
+ def spot_freed(self):
+ self.available_spots += 1
+
+ def park_vehicle(self, vehicle):
+ spot = self._find_available_spot(vehicle)
+ if spot is None:
+ return None
+ else:
+ spot.park_vehicle(vehicle)
+ return spot
+
+ def _find_available_spot(self, vehicle):
+ """Find an available spot where vehicle can fit, or return None"""
+ pass
+
+ def _park_starting_at_spot(self, spot, vehicle):
+ """Occupy starting at spot.spot_number to vehicle.spot_size."""
+ pass
+
+
+class ParkingSpot(object):
+
+ def __init__(self, level, row, spot_number, spot_size, vehicle_size):
+ self.level = level
+ self.row = row
+ self.spot_number = spot_number
+ self.spot_size = spot_size
+ self.vehicle_size = vehicle_size
+ self.vehicle = None
+
+ def is_available(self):
+ return True if self.vehicle is None else False
+
+ def can_fit_vehicle(self, vehicle):
+ if self.vehicle is not None:
+ return False
+ return vehicle.can_fit_in_spot(self)
+
+ def park_vehicle(self, vehicle):
+ pass
+
+ def remove_vehicle(self):
+ pass
diff --git a/website/solutions/system_design/mint/README-zh-Hans.md b/website/solutions/system_design/mint/README-zh-Hans.md
new file mode 100644
index 00000000000..58467bc6bf1
--- /dev/null
+++ b/website/solutions/system_design/mint/README-zh-Hans.md
@@ -0,0 +1,440 @@
+# 设计 Mint.com
+
+**注意:这个文档中的链接会直接指向[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题索引)中的有关部分,以避免重复的内容。您可以参考链接的相关内容,来了解其总的要点、方案的权衡取舍以及可选的替代方案。**
+
+## 第一步:简述用例与约束条件
+
+> 搜集需求与问题的范围。
+> 提出问题来明确用例与约束条件。
+> 讨论假设。
+
+我们将在没有面试官明确说明问题的情况下,自己定义一些用例以及限制条件。
+
+### 用例
+
+#### 我们将把问题限定在仅处理以下用例的范围中
+
+* **用户** 连接到一个财务账户
+* **服务** 从账户中提取交易
+ * 每日更新
+ * 分类交易
+ * 允许用户手动分类
+ * 不自动重新分类
+ * 按类别分析每月支出
+* **服务** 推荐预算
+ * 允许用户手动设置预算
+ * 当接近或者超出预算时,发送通知
+* **服务** 具有高可用性
+
+#### 非用例范围
+
+* **服务** 执行附加的日志记录和分析
+
+### 限制条件与假设
+
+#### 提出假设
+
+* 网络流量非均匀分布
+* 自动账户日更新只适用于 30 天内活跃的用户
+* 添加或者移除财务账户相对较少
+* 预算通知不需要及时
+* 1000 万用户
+ * 每个用户10个预算类别= 1亿个预算项
+ * 示例类别:
+ * Housing = $1,000
+ * Food = $200
+ * Gas = $100
+ * 卖方确定交易类别
+ * 50000 个卖方
+* 3000 万财务账户
+* 每月 50 亿交易
+* 每月 5 亿读请求
+* 10:1 读写比
+ * Write-heavy,用户每天都进行交易,但是每天很少访问该网站
+
+#### 计算用量
+
+**如果你需要进行粗略的用量计算,请向你的面试官说明。**
+
+* 每次交易的用量:
+ * `user_id` - 8 字节
+ * `created_at` - 5 字节
+ * `seller` - 32 字节
+ * `amount` - 5 字节
+ * Total: ~50 字节
+* 每月产生 250 GB 新的交易内容
+ * 每次交易 50 比特 * 50 亿交易每月
+ * 3年内新的交易内容 9 TB
+ * Assume most are new transactions instead of updates to existing ones
+* 平均每秒产生 2000 次交易
+* 平均每秒产生 200 读请求
+
+便利换算指南:
+
+* 每个月有 250 万秒
+* 每秒一个请求 = 每个月 250 万次请求
+* 每秒 40 个请求 = 每个月 1 亿次请求
+* 每秒 400 个请求 = 每个月 10 亿次请求
+
+## 第二步:概要设计
+
+> 列出所有重要组件以规划概要设计。
+
+
+
+## 第三步:设计核心组件
+
+> 深入每个核心组件的细节。
+
+### 用例:用户连接到一个财务账户
+
+我们可以将 1000 万用户的信息存储在一个[关系数据库](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms)中。我们应该讨论一下[选择SQL或NoSQL之间的用例和权衡](https://github.com/donnemartin/system-design-primer#sql-or-nosql)了。
+
+* **客户端** 作为一个[反向代理](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server),发送请求到 **Web 服务器**
+* **Web 服务器** 转发请求到 **账户API** 服务器
+* **账户API** 服务器将新输入的账户信息更新到 **SQL数据库** 的`accounts`表
+
+**告知你的面试官你准备写多少代码**。
+
+`accounts`表应该具有如下结构:
+
+```
+id int NOT NULL AUTO_INCREMENT
+created_at datetime NOT NULL
+last_update datetime NOT NULL
+account_url varchar(255) NOT NULL
+account_login varchar(32) NOT NULL
+account_password_hash char(64) NOT NULL
+user_id int NOT NULL
+PRIMARY KEY(id)
+FOREIGN KEY(user_id) REFERENCES users(id)
+```
+
+我们将在`id`,`user_id`和`created_at`等字段上创建一个[索引](https://github.com/donnemartin/system-design-primer#use-good-indices)以加速查找(对数时间而不是扫描整个表)并保持数据在内存中。从内存中顺序读取 1 MB数据花费大约250毫秒,而从SSD读取是其4倍,从磁盘读取是其80倍。1
+
+我们将使用公开的[**REST API**](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest):
+
+```
+$ curl -X POST --data '{ "user_id": "foo", "account_url": "bar", \
+ "account_login": "baz", "account_password": "qux" }' \
+ https://mint.com/api/v1/account
+```
+
+对于内部通信,我们可以使用[远程过程调用](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)。
+
+接下来,服务从账户中提取交易。
+
+### 用例:服务从账户中提取交易
+
+如下几种情况下,我们会想要从账户中提取信息:
+
+* 用户首次链接账户
+* 用户手动更新账户
+* 为过去 30 天内活跃的用户自动日更新
+
+数据流:
+
+* **客户端**向 **Web服务器** 发送请求
+* **Web服务器** 将请求转发到 **帐户API** 服务器
+* **帐户API** 服务器将job放在 **队列** 中,如 [Amazon SQS](https://aws.amazon.com/sqs/) 或者 [RabbitMQ](https://www.rabbitmq.com/)
+ * 提取交易可能需要一段时间,我们可能希望[与队列异步](https://github.com/donnemartin/system-design-primer#asynchronism)地来做,虽然这会引入额外的复杂度。
+* **交易提取服务** 执行如下操作:
+ * 从 **Queue** 中拉取并从金融机构中提取给定用户的交易,将结果作为原始日志文件存储在 **对象存储区**。
+ * 使用 **分类服务** 来分类每个交易
+ * 使用 **预算服务** 来按类别计算每月总支出
+ * **预算服务** 使用 **通知服务** 让用户知道他们是否接近或者已经超出预算
+ * 更新具有分类交易的 **SQL数据库** 的`transactions`表
+ * 按类别更新 **SQL数据库** `monthly_spending`表的每月总支出
+ * 通过 **通知服务** 提醒用户交易完成
+ * 使用一个 **队列** (没有画出来) 来异步发送通知
+
+`transactions`表应该具有如下结构:
+
+```
+id int NOT NULL AUTO_INCREMENT
+created_at datetime NOT NULL
+seller varchar(32) NOT NULL
+amount decimal NOT NULL
+user_id int NOT NULL
+PRIMARY KEY(id)
+FOREIGN KEY(user_id) REFERENCES users(id)
+```
+
+我们将在 `id`,`user_id`,和 `created_at`字段上创建[索引](https://github.com/donnemartin/system-design-primer#use-good-indices)。
+
+`monthly_spending`表应该具有如下结构:
+
+```
+id int NOT NULL AUTO_INCREMENT
+month_year date NOT NULL
+category varchar(32)
+amount decimal NOT NULL
+user_id int NOT NULL
+PRIMARY KEY(id)
+FOREIGN KEY(user_id) REFERENCES users(id)
+```
+
+我们将在`id`,`user_id`字段上创建[索引](https://github.com/donnemartin/system-design-primer#use-good-indices)。
+
+#### 分类服务
+
+对于 **分类服务**,我们可以生成一个带有最受欢迎卖家的卖家-类别字典。如果我们估计 50000 个卖家,并估计每个条目占用不少于 255 个字节,该字典只需要大约 12 MB内存。
+
+**告知你的面试官你准备写多少代码**。
+
+```python
+class DefaultCategories(Enum):
+
+ HOUSING = 0
+ FOOD = 1
+ GAS = 2
+ SHOPPING = 3
+ ...
+
+seller_category_map = {}
+seller_category_map['Exxon'] = DefaultCategories.GAS
+seller_category_map['Target'] = DefaultCategories.SHOPPING
+...
+```
+
+对于一开始没有在映射中的卖家,我们可以通过评估用户提供的手动类别来进行众包。在 O(1) 时间内,我们可以用堆来快速查找每个卖家的顶端的手动覆盖。
+
+```python
+class Categorizer(object):
+
+ def __init__(self, seller_category_map, self.seller_category_crowd_overrides_map):
+ self.seller_category_map = seller_category_map
+ self.seller_category_crowd_overrides_map = \
+ seller_category_crowd_overrides_map
+
+ def categorize(self, transaction):
+ if transaction.seller in self.seller_category_map:
+ return self.seller_category_map[transaction.seller]
+ elif transaction.seller in self.seller_category_crowd_overrides_map:
+ self.seller_category_map[transaction.seller] = \
+ self.seller_category_crowd_overrides_map[transaction.seller].peek_min()
+ return self.seller_category_map[transaction.seller]
+ return None
+```
+
+交易实现:
+
+```python
+class Transaction(object):
+
+ def __init__(self, created_at, seller, amount):
+ self.timestamp = timestamp
+ self.seller = seller
+ self.amount = amount
+```
+
+### 用例:服务推荐预算
+
+首先,我们可以使用根据收入等级分配每类别金额的通用预算模板。使用这种方法,我们不必存储在约束中标识的 1 亿个预算项目,只需存储用户覆盖的预算项目。如果用户覆盖预算类别,我们可以在
+`TABLE budget_overrides`中存储此覆盖。
+
+```python
+class Budget(object):
+
+ def __init__(self, income):
+ self.income = income
+ self.categories_to_budget_map = self.create_budget_template()
+
+ def create_budget_template(self):
+ return {
+ 'DefaultCategories.HOUSING': income * .4,
+ 'DefaultCategories.FOOD': income * .2
+ 'DefaultCategories.GAS': income * .1,
+ 'DefaultCategories.SHOPPING': income * .2
+ ...
+ }
+
+ def override_category_budget(self, category, amount):
+ self.categories_to_budget_map[category] = amount
+```
+
+对于 **预算服务** 而言,我们可以在`transactions`表上运行SQL查询以生成`monthly_spending`聚合表。由于用户通常每个月有很多交易,所以`monthly_spending`表的行数可能会少于总共50亿次交易的行数。
+
+作为替代,我们可以在原始交易文件上运行 **MapReduce** 作业来:
+
+* 分类每个交易
+* 按类别生成每月总支出
+
+对交易文件的运行分析可以显著减少数据库的负载。
+
+如果用户更新类别,我们可以调用 **预算服务** 重新运行分析。
+
+**告知你的面试官你准备写多少代码**.
+
+日志文件格式样例,以tab分割:
+
+```
+user_id timestamp seller amount
+```
+
+**MapReduce** 实现:
+
+```python
+class SpendingByCategory(MRJob):
+
+ def __init__(self, categorizer):
+ self.categorizer = categorizer
+ self.current_year_month = calc_current_year_month()
+ ...
+
+ def calc_current_year_month(self):
+ """返回当前年月"""
+ ...
+
+ def extract_year_month(self, timestamp):
+ """返回时间戳的年,月部分"""
+ ...
+
+ def handle_budget_notifications(self, key, total):
+ """如果接近或超出预算,调用通知API"""
+ ...
+
+ def mapper(self, _, line):
+ """解析每个日志行,提取和转换相关行。
+
+ 参数行应为如下形式:
+
+ user_id timestamp seller amount
+
+ 使用分类器来将卖家转换成类别,生成如下形式的key-value对:
+
+ (user_id, 2016-01, shopping), 25
+ (user_id, 2016-01, shopping), 100
+ (user_id, 2016-01, gas), 50
+ """
+ user_id, timestamp, seller, amount = line.split('\t')
+ category = self.categorizer.categorize(seller)
+ period = self.extract_year_month(timestamp)
+ if period == self.current_year_month:
+ yield (user_id, period, category), amount
+
+ def reducer(self, key, value):
+ """将每个key对应的值求和。
+
+ (user_id, 2016-01, shopping), 125
+ (user_id, 2016-01, gas), 50
+ """
+ total = sum(values)
+ yield key, sum(values)
+```
+
+## 第四步:设计扩展
+
+> 根据限制条件,找到并解决瓶颈。
+
+
+
+**重要提示:不要从最初设计直接跳到最终设计中!**
+
+现在你要 1) **基准测试、负载测试**。2) **分析、描述**性能瓶颈。3) 在解决瓶颈问题的同时,评估替代方案、权衡利弊。4) 重复以上步骤。请阅读[「设计一个系统,并将其扩大到为数以百万计的 AWS 用户服务」](../scaling_aws/README.md) 来了解如何逐步扩大初始设计。
+
+讨论初始设计可能遇到的瓶颈及相关解决方案是很重要的。例如加上一个配置多台 **Web 服务器**的**负载均衡器**是否能够解决问题?**CDN**呢?**主从复制**呢?它们各自的替代方案和需要**权衡**的利弊又有什么呢?
+
+我们将会介绍一些组件来完成设计,并解决架构扩张问题。内置的负载均衡器将不做讨论以节省篇幅。
+
+**为了避免重复讨论**,请参考[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)相关部分来了解其要点、方案的权衡取舍以及可选的替代方案。
+
+* [DNS](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#域名系统)
+* [负载均衡器](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#负载均衡器)
+* [水平拓展](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#水平扩展)
+* [反向代理(web 服务器)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)
+* [API 服务(应用层)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用层)
+* [缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存)
+* [关系型数据库管理系统 (RDBMS)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#关系型数据库管理系统rdbms)
+* [SQL 故障主从切换](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#故障切换)
+* [主从复制](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#主从复制)
+* [异步](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#异步)
+* [一致性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#一致性模式)
+* [可用性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#可用性模式)
+
+我们将增加一个额外的用例:**用户** 访问摘要和交易数据。
+
+用户会话,按类别统计的统计信息,以及最近的事务可以放在 **内存缓存**(如 Redis 或 Memcached )中。
+
+* **客户端** 发送读请求给 **Web 服务器**
+* **Web 服务器** 转发请求到 **读 API** 服务器
+ * 静态内容可通过 **对象存储** 比如缓存在 **CDN** 上的 S3 来服务
+* **读 API** 服务器做如下动作:
+ * 检查 **内存缓存** 的内容
+ * 如果URL在 **内存缓存**中,返回缓存的内容
+ * 否则
+ * 如果URL在 **SQL 数据库**中,获取该内容
+ * 以其内容更新 **内存缓存**
+
+参考 [何时更新缓存](https://github.com/donnemartin/system-design-primer#when-to-update-the-cache) 中权衡和替代的内容。以上方法描述了 [cache-aside缓存模式](https://github.com/donnemartin/system-design-primer#cache-aside).
+
+我们可以使用诸如 Amazon Redshift 或者 Google BigQuery 等数据仓库解决方案,而不是将`monthly_spending`聚合表保留在 **SQL 数据库** 中。
+
+我们可能只想在数据库中存储一个月的`交易`数据,而将其余数据存储在数据仓库或者 **对象存储区** 中。**对象存储区** (如Amazon S3) 能够舒服地解决每月 250 GB新内容的限制。
+
+为了解决每秒 *平均* 2000 次读请求数(峰值时更高),受欢迎的内容的流量应由 **内存缓存** 而不是数据库来处理。 **内存缓存** 也可用于处理不均匀分布的流量和流量尖峰。 只要副本不陷入重复写入的困境,**SQL 读副本** 应该能够处理高速缓存未命中。
+
+*平均* 200 次交易写入每秒(峰值时更高)对于单个 **SQL 写入主-从服务** 来说可能是棘手的。我们可能需要考虑其它的 SQL 性能拓展技术:
+
+* [联合](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#联合)
+* [分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#分片)
+* [非规范化](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#非规范化)
+* [SQL 调优](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-调优)
+
+我们也可以考虑将一些数据移至 **NoSQL 数据库**。
+
+## 其它要点
+
+> 是否深入这些额外的主题,取决于你的问题范围和剩下的时间。
+
+#### NoSQL
+
+* [键-值存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#键-值存储)
+* [文档类型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#文档类型存储)
+* [列型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#列型存储)
+* [图数据库](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#图数据库)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)
+
+### 缓存
+
+* 在哪缓存
+ * [客户端缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#客户端缓存)
+ * [CDN 缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#cdn-缓存)
+ * [Web 服务器缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#web-服务器缓存)
+ * [数据库缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库缓存)
+ * [应用缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用缓存)
+* 什么需要缓存
+ * [数据库查询级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库查询级别的缓存)
+ * [对象级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#对象级别的缓存)
+* 何时更新缓存
+ * [缓存模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存模式)
+ * [直写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#直写模式)
+ * [回写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#回写模式)
+ * [刷新](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#刷新)
+
+### 异步与微服务
+
+* [消息队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#消息队列)
+* [任务队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#任务队列)
+* [背压](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#背压)
+* [微服务](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#微服务)
+
+### 通信
+
+* 可权衡选择的方案:
+ * 与客户端的外部通信 - [使用 REST 作为 HTTP API](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest)
+ * 服务器内部通信 - [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)
+* [服务发现](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#服务发现)
+
+### 安全性
+
+请参阅[「安全」](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#安全)一章。
+
+### 延迟数值
+
+请参阅[「每个程序员都应该知道的延迟数」](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#每个程序员都应该知道的延迟数)。
+
+### 持续探讨
+
+* 持续进行基准测试并监控你的系统,以解决他们提出的瓶颈问题。
+* 架构拓展是一个迭代的过程。
diff --git a/website/solutions/system_design/mint/README.md b/website/solutions/system_design/mint/README.md
new file mode 100644
index 00000000000..1ec31674db0
--- /dev/null
+++ b/website/solutions/system_design/mint/README.md
@@ -0,0 +1,441 @@
+# Design Mint.com
+
+*Note: This document links directly to relevant areas found in the [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) to avoid duplication. Refer to the linked content for general talking points, tradeoffs, and alternatives.*
+
+## Step 1: Outline use cases and constraints
+
+> Gather requirements and scope the problem.
+> Ask questions to clarify use cases and constraints.
+> Discuss assumptions.
+
+Without an interviewer to address clarifying questions, we'll define some use cases and constraints.
+
+### Use cases
+
+#### We'll scope the problem to handle only the following use cases
+
+* **User** connects to a financial account
+* **Service** extracts transactions from the account
+ * Updates daily
+ * Categorizes transactions
+ * Allows manual category override by the user
+ * No automatic re-categorization
+ * Analyzes monthly spending, by category
+* **Service** recommends a budget
+ * Allows users to manually set a budget
+ * Sends notifications when approaching or exceeding budget
+* **Service** has high availability
+
+#### Out of scope
+
+* **Service** performs additional logging and analytics
+
+### Constraints and assumptions
+
+#### State assumptions
+
+* Traffic is not evenly distributed
+* Automatic daily update of accounts applies only to users active in the past 30 days
+* Adding or removing financial accounts is relatively rare
+* Budget notifications don't need to be instant
+* 10 million users
+ * 10 budget categories per user = 100 million budget items
+ * Example categories:
+ * Housing = $1,000
+ * Food = $200
+ * Gas = $100
+ * Sellers are used to determine transaction category
+ * 50,000 sellers
+* 30 million financial accounts
+* 5 billion transactions per month
+* 500 million read requests per month
+* 10:1 write to read ratio
+ * Write-heavy, users make transactions daily, but few visit the site daily
+
+#### Calculate usage
+
+**Clarify with your interviewer if you should run back-of-the-envelope usage calculations.**
+
+* Size per transaction:
+ * `user_id` - 8 bytes
+ * `created_at` - 5 bytes
+ * `seller` - 32 bytes
+ * `amount` - 5 bytes
+ * Total: ~50 bytes
+* 250 GB of new transaction content per month
+ * 50 bytes per transaction * 5 billion transactions per month
+ * 9 TB of new transaction content in 3 years
+ * Assume most are new transactions instead of updates to existing ones
+* 2,000 transactions per second on average
+* 200 read requests per second on average
+
+Handy conversion guide:
+
+* 2.5 million seconds per month
+* 1 request per second = 2.5 million requests per month
+* 40 requests per second = 100 million requests per month
+* 400 requests per second = 1 billion requests per month
+
+## Step 2: Create a high level design
+
+> Outline a high level design with all important components.
+
+
+
+## Step 3: Design core components
+
+> Dive into details for each core component.
+
+### Use case: User connects to a financial account
+
+We could store info on the 10 million users in a [relational database](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms). We should discuss the [use cases and tradeoffs between choosing SQL or NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql).
+
+* The **Client** sends a request to the **Web Server**, running as a [reverse proxy](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* The **Web Server** forwards the request to the **Accounts API** server
+* The **Accounts API** server updates the **SQL Database** `accounts` table with the newly entered account info
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+The `accounts` table could have the following structure:
+
+```
+id int NOT NULL AUTO_INCREMENT
+created_at datetime NOT NULL
+last_update datetime NOT NULL
+account_url varchar(255) NOT NULL
+account_login varchar(32) NOT NULL
+account_password_hash char(64) NOT NULL
+user_id int NOT NULL
+PRIMARY KEY(id)
+FOREIGN KEY(user_id) REFERENCES users(id)
+```
+
+We'll create an [index](https://github.com/donnemartin/system-design-primer#use-good-indices) on `id`, `user_id `, and `created_at` to speed up lookups (log-time instead of scanning the entire table) and to keep the data in memory. Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.1
+
+We'll use a public [**REST API**](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest):
+
+```
+$ curl -X POST --data '{ "user_id": "foo", "account_url": "bar", \
+ "account_login": "baz", "account_password": "qux" }' \
+ https://mint.com/api/v1/account
+```
+
+For internal communications, we could use [Remote Procedure Calls](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc).
+
+Next, the service extracts transactions from the account.
+
+### Use case: Service extracts transactions from the account
+
+We'll want to extract information from an account in these cases:
+
+* The user first links the account
+* The user manually refreshes the account
+* Automatically each day for users who have been active in the past 30 days
+
+Data flow:
+
+* The **Client** sends a request to the **Web Server**
+* The **Web Server** forwards the request to the **Accounts API** server
+* The **Accounts API** server places a job on a **Queue** such as [Amazon SQS](https://aws.amazon.com/sqs/) or [RabbitMQ](https://www.rabbitmq.com/)
+ * Extracting transactions could take awhile, we'd probably want to do this [asynchronously with a queue](https://github.com/donnemartin/system-design-primer#asynchronism), although this introduces additional complexity
+* The **Transaction Extraction Service** does the following:
+ * Pulls from the **Queue** and extracts transactions for the given account from the financial institution, storing the results as raw log files in the **Object Store**
+ * Uses the **Category Service** to categorize each transaction
+ * Uses the **Budget Service** to calculate aggregate monthly spending by category
+ * The **Budget Service** uses the **Notification Service** to let users know if they are nearing or have exceeded their budget
+ * Updates the **SQL Database** `transactions` table with categorized transactions
+ * Updates the **SQL Database** `monthly_spending` table with aggregate monthly spending by category
+ * Notifies the user the transactions have completed through the **Notification Service**:
+ * Uses a **Queue** (not pictured) to asynchronously send out notifications
+
+The `transactions` table could have the following structure:
+
+```
+id int NOT NULL AUTO_INCREMENT
+created_at datetime NOT NULL
+seller varchar(32) NOT NULL
+amount decimal NOT NULL
+user_id int NOT NULL
+PRIMARY KEY(id)
+FOREIGN KEY(user_id) REFERENCES users(id)
+```
+
+We'll create an [index](https://github.com/donnemartin/system-design-primer#use-good-indices) on `id`, `user_id `, and `created_at`.
+
+The `monthly_spending` table could have the following structure:
+
+```
+id int NOT NULL AUTO_INCREMENT
+month_year date NOT NULL
+category varchar(32)
+amount decimal NOT NULL
+user_id int NOT NULL
+PRIMARY KEY(id)
+FOREIGN KEY(user_id) REFERENCES users(id)
+```
+
+We'll create an [index](https://github.com/donnemartin/system-design-primer#use-good-indices) on `id` and `user_id `.
+
+#### Category service
+
+For the **Category Service**, we can seed a seller-to-category dictionary with the most popular sellers. If we estimate 50,000 sellers and estimate each entry to take less than 255 bytes, the dictionary would only take about 12 MB of memory.
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+```python
+class DefaultCategories(Enum):
+
+ HOUSING = 0
+ FOOD = 1
+ GAS = 2
+ SHOPPING = 3
+ ...
+
+seller_category_map = {}
+seller_category_map['Exxon'] = DefaultCategories.GAS
+seller_category_map['Target'] = DefaultCategories.SHOPPING
+...
+```
+
+For sellers not initially seeded in the map, we could use a crowdsourcing effort by evaluating the manual category overrides our users provide. We could use a heap to quickly lookup the top manual override per seller in O(1) time.
+
+```python
+class Categorizer(object):
+
+ def __init__(self, seller_category_map, seller_category_crowd_overrides_map):
+ self.seller_category_map = seller_category_map
+ self.seller_category_crowd_overrides_map = \
+ seller_category_crowd_overrides_map
+
+ def categorize(self, transaction):
+ if transaction.seller in self.seller_category_map:
+ return self.seller_category_map[transaction.seller]
+ elif transaction.seller in self.seller_category_crowd_overrides_map:
+ self.seller_category_map[transaction.seller] = \
+ self.seller_category_crowd_overrides_map[transaction.seller].peek_min()
+ return self.seller_category_map[transaction.seller]
+ return None
+```
+
+Transaction implementation:
+
+```python
+class Transaction(object):
+
+ def __init__(self, created_at, seller, amount):
+ self.created_at = created_at
+ self.seller = seller
+ self.amount = amount
+```
+
+### Use case: Service recommends a budget
+
+To start, we could use a generic budget template that allocates category amounts based on income tiers. Using this approach, we would not have to store the 100 million budget items identified in the constraints, only those that the user overrides. If a user overrides a budget category, which we could store the override in the `TABLE budget_overrides`.
+
+```python
+class Budget(object):
+
+ def __init__(self, income):
+ self.income = income
+ self.categories_to_budget_map = self.create_budget_template()
+
+ def create_budget_template(self):
+ return {
+ DefaultCategories.HOUSING: self.income * .4,
+ DefaultCategories.FOOD: self.income * .2,
+ DefaultCategories.GAS: self.income * .1,
+ DefaultCategories.SHOPPING: self.income * .2,
+ ...
+ }
+
+ def override_category_budget(self, category, amount):
+ self.categories_to_budget_map[category] = amount
+```
+
+For the **Budget Service**, we can potentially run SQL queries on the `transactions` table to generate the `monthly_spending` aggregate table. The `monthly_spending` table would likely have much fewer rows than the total 5 billion transactions, since users typically have many transactions per month.
+
+As an alternative, we can run **MapReduce** jobs on the raw transaction files to:
+
+* Categorize each transaction
+* Generate aggregate monthly spending by category
+
+Running analyses on the transaction files could significantly reduce the load on the database.
+
+We could call the **Budget Service** to re-run the analysis if the user updates a category.
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+Sample log file format, tab delimited:
+
+```
+user_id timestamp seller amount
+```
+
+**MapReduce** implementation:
+
+```python
+class SpendingByCategory(MRJob):
+
+ def __init__(self, categorizer):
+ self.categorizer = categorizer
+ self.current_year_month = calc_current_year_month()
+ ...
+
+ def calc_current_year_month(self):
+ """Return the current year and month."""
+ ...
+
+ def extract_year_month(self, timestamp):
+ """Return the year and month portions of the timestamp."""
+ ...
+
+ def handle_budget_notifications(self, key, total):
+ """Call notification API if nearing or exceeded budget."""
+ ...
+
+ def mapper(self, _, line):
+ """Parse each log line, extract and transform relevant lines.
+
+ Argument line will be of the form:
+
+ user_id timestamp seller amount
+
+ Using the categorizer to convert seller to category,
+ emit key value pairs of the form:
+
+ (user_id, 2016-01, shopping), 25
+ (user_id, 2016-01, shopping), 100
+ (user_id, 2016-01, gas), 50
+ """
+ user_id, timestamp, seller, amount = line.split('\t')
+ category = self.categorizer.categorize(seller)
+ period = self.extract_year_month(timestamp)
+ if period == self.current_year_month:
+ yield (user_id, period, category), amount
+
+ def reducer(self, key, value):
+ """Sum values for each key.
+
+ (user_id, 2016-01, shopping), 125
+ (user_id, 2016-01, gas), 50
+ """
+ total = sum(values)
+ yield key, sum(values)
+```
+
+## Step 4: Scale the design
+
+> Identify and address bottlenecks, given the constraints.
+
+
+
+**Important: Do not simply jump right into the final design from the initial design!**
+
+State you would 1) **Benchmark/Load Test**, 2) **Profile** for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat. See [Design a system that scales to millions of users on AWS](../scaling_aws/README.md) as a sample on how to iteratively scale the initial design.
+
+It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them. For example, what issues are addressed by adding a **Load Balancer** with multiple **Web Servers**? **CDN**? **Master-Slave Replicas**? What are the alternatives and **Trade-Offs** for each?
+
+We'll introduce some components to complete the design and to address scalability issues. Internal load balancers are not shown to reduce clutter.
+
+*To avoid repeating discussions*, refer to the following [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) for main talking points, tradeoffs, and alternatives:
+
+* [DNS](https://github.com/donnemartin/system-design-primer#domain-name-system)
+* [CDN](https://github.com/donnemartin/system-design-primer#content-delivery-network)
+* [Load balancer](https://github.com/donnemartin/system-design-primer#load-balancer)
+* [Horizontal scaling](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+* [Web server (reverse proxy)](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* [API server (application layer)](https://github.com/donnemartin/system-design-primer#application-layer)
+* [Cache](https://github.com/donnemartin/system-design-primer#cache)
+* [Relational database management system (RDBMS)](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms)
+* [SQL write master-slave failover](https://github.com/donnemartin/system-design-primer#fail-over)
+* [Master-slave replication](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [Asynchronism](https://github.com/donnemartin/system-design-primer#asynchronism)
+* [Consistency patterns](https://github.com/donnemartin/system-design-primer#consistency-patterns)
+* [Availability patterns](https://github.com/donnemartin/system-design-primer#availability-patterns)
+
+We'll add an additional use case: **User** accesses summaries and transactions.
+
+User sessions, aggregate stats by category, and recent transactions could be placed in a **Memory Cache** such as Redis or Memcached.
+
+* The **Client** sends a read request to the **Web Server**
+* The **Web Server** forwards the request to the **Read API** server
+ * Static content can be served from the **Object Store** such as S3, which is cached on the **CDN**
+* The **Read API** server does the following:
+ * Checks the **Memory Cache** for the content
+ * If the url is in the **Memory Cache**, returns the cached contents
+ * Else
+ * If the url is in the **SQL Database**, fetches the contents
+ * Updates the **Memory Cache** with the contents
+
+Refer to [When to update the cache](https://github.com/donnemartin/system-design-primer#when-to-update-the-cache) for tradeoffs and alternatives. The approach above describes [cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside).
+
+Instead of keeping the `monthly_spending` aggregate table in the **SQL Database**, we could create a separate **Analytics Database** using a data warehousing solution such as Amazon Redshift or Google BigQuery.
+
+We might only want to store a month of `transactions` data in the database, while storing the rest in a data warehouse or in an **Object Store**. An **Object Store** such as Amazon S3 can comfortably handle the constraint of 250 GB of new content per month.
+
+To address the 200 *average* read requests per second (higher at peak), traffic for popular content should be handled by the **Memory Cache** instead of the database. The **Memory Cache** is also useful for handling the unevenly distributed traffic and traffic spikes. The **SQL Read Replicas** should be able to handle the cache misses, as long as the replicas are not bogged down with replicating writes.
+
+2,000 *average* transaction writes per second (higher at peak) might be tough for a single **SQL Write Master-Slave**. We might need to employ additional SQL scaling patterns:
+
+* [Federation](https://github.com/donnemartin/system-design-primer#federation)
+* [Sharding](https://github.com/donnemartin/system-design-primer#sharding)
+* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+We should also consider moving some data to a **NoSQL Database**.
+
+## Additional talking points
+
+> Additional topics to dive into, depending on the problem scope and time remaining.
+
+#### NoSQL
+
+* [Key-value store](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [Document store](https://github.com/donnemartin/system-design-primer#document-store)
+* [Wide column store](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [Graph database](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### Caching
+
+* Where to cache
+ * [Client caching](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN caching](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web server caching](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [Database caching](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [Application caching](https://github.com/donnemartin/system-design-primer#application-caching)
+* What to cache
+ * [Caching at the database query level](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [Caching at the object level](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* When to update the cache
+ * [Cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [Write-through](https://github.com/donnemartin/system-design-primer#write-through)
+ * [Write-behind (write-back)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [Refresh ahead](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### Asynchronism and microservices
+
+* [Message queues](https://github.com/donnemartin/system-design-primer#message-queues)
+* [Task queues](https://github.com/donnemartin/system-design-primer#task-queues)
+* [Back pressure](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [Microservices](https://github.com/donnemartin/system-design-primer#microservices)
+
+### Communications
+
+* Discuss tradeoffs:
+ * External communication with clients - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * Internal communications - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [Service discovery](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### Security
+
+Refer to the [security section](https://github.com/donnemartin/system-design-primer#security).
+
+### Latency numbers
+
+See [Latency numbers every programmer should know](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know).
+
+### Ongoing
+
+* Continue benchmarking and monitoring your system to address bottlenecks as they come up
+* Scaling is an iterative process
diff --git a/website/solutions/system_design/mint/__init__.py b/website/solutions/system_design/mint/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/system_design/mint/mint.graffle b/website/solutions/system_design/mint/mint.graffle
new file mode 100644
index 00000000000..5ba2e5f8680
Binary files /dev/null and b/website/solutions/system_design/mint/mint.graffle differ
diff --git a/website/solutions/system_design/mint/mint.png b/website/solutions/system_design/mint/mint.png
new file mode 100644
index 00000000000..0994195e812
Binary files /dev/null and b/website/solutions/system_design/mint/mint.png differ
diff --git a/website/solutions/system_design/mint/mint_basic.graffle b/website/solutions/system_design/mint/mint_basic.graffle
new file mode 100644
index 00000000000..95c2e81e34b
Binary files /dev/null and b/website/solutions/system_design/mint/mint_basic.graffle differ
diff --git a/website/solutions/system_design/mint/mint_basic.png b/website/solutions/system_design/mint/mint_basic.png
new file mode 100644
index 00000000000..f14a577ee90
Binary files /dev/null and b/website/solutions/system_design/mint/mint_basic.png differ
diff --git a/website/solutions/system_design/mint/mint_mapreduce.py b/website/solutions/system_design/mint/mint_mapreduce.py
new file mode 100644
index 00000000000..e3554243018
--- /dev/null
+++ b/website/solutions/system_design/mint/mint_mapreduce.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+from mrjob.job import MRJob
+
+
+class SpendingByCategory(MRJob):
+
+ def __init__(self, categorizer):
+ self.categorizer = categorizer
+ ...
+
+ def current_year_month(self):
+ """Return the current year and month."""
+ ...
+
+ def extract_year_month(self, timestamp):
+ """Return the year and month portions of the timestamp."""
+ ...
+
+ def handle_budget_notifications(self, key, total):
+ """Call notification API if nearing or exceeded budget."""
+ ...
+
+ def mapper(self, _, line):
+ """Parse each log line, extract and transform relevant lines.
+
+ Emit key value pairs of the form:
+
+ (2016-01, shopping), 25
+ (2016-01, shopping), 100
+ (2016-01, gas), 50
+ """
+ timestamp, category, amount = line.split('\t')
+ period = self. extract_year_month(timestamp)
+ if period == self.current_year_month():
+ yield (period, category), amount
+
+ def reducer(self, key, values):
+ """Sum values for each key.
+
+ (2016-01, shopping), 125
+ (2016-01, gas), 50
+ """
+ total = sum(values)
+ self.handle_budget_notifications(key, total)
+ yield key, sum(values)
+
+ def steps(self):
+ """Run the map and reduce steps."""
+ return [
+ self.mr(mapper=self.mapper,
+ reducer=self.reducer)
+ ]
+
+
+if __name__ == '__main__':
+ SpendingByCategory.run()
diff --git a/website/solutions/system_design/mint/mint_snippets.py b/website/solutions/system_design/mint/mint_snippets.py
new file mode 100644
index 00000000000..cc5d228b2a4
--- /dev/null
+++ b/website/solutions/system_design/mint/mint_snippets.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+from enum import Enum
+
+
+class DefaultCategories(Enum):
+
+ HOUSING = 0
+ FOOD = 1
+ GAS = 2
+ SHOPPING = 3
+ # ...
+
+
+seller_category_map = {}
+seller_category_map['Exxon'] = DefaultCategories.GAS
+seller_category_map['Target'] = DefaultCategories.SHOPPING
+
+
+class Categorizer(object):
+
+ def __init__(self, seller_category_map, seller_category_overrides_map):
+ self.seller_category_map = seller_category_map
+ self.seller_category_overrides_map = seller_category_overrides_map
+
+ def categorize(self, transaction):
+ if transaction.seller in self.seller_category_map:
+ return self.seller_category_map[transaction.seller]
+ if transaction.seller in self.seller_category_overrides_map:
+ seller_category_map[transaction.seller] = \
+ self.manual_overrides[transaction.seller].peek_min()
+ return self.seller_category_map[transaction.seller]
+ return None
+
+
+class Transaction(object):
+
+ def __init__(self, timestamp, seller, amount):
+ self.timestamp = timestamp
+ self.seller = seller
+ self.amount = amount
+
+
+class Budget(object):
+
+ def __init__(self, template_categories_to_budget_map):
+ self.categories_to_budget_map = template_categories_to_budget_map
+
+ def override_category_budget(self, category, amount):
+ self.categories_to_budget_map[category] = amount
diff --git a/website/solutions/system_design/pastebin/README-zh-Hans.md b/website/solutions/system_design/pastebin/README-zh-Hans.md
new file mode 100644
index 00000000000..d2946e97090
--- /dev/null
+++ b/website/solutions/system_design/pastebin/README-zh-Hans.md
@@ -0,0 +1,330 @@
+# 设计 Pastebin.com (或者 Bit.ly)
+
+**注意: 为了避免重复,当前文档会直接链接到[系统设计主题](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)的相关区域,请参考链接内容以获得综合的讨论点、权衡和替代方案。**
+
+**设计 Bit.ly** - 是一个类似的问题,区别是 pastebin 需要存储的是 paste 的内容,而不是原始的未短化的 url。
+
+## 第一步:概述用例和约束
+
+> 收集这个问题的需求和范畴。
+> 问相关问题来明确用例和约束。
+> 讨论一些假设。
+
+因为没有面试官来明确这些问题,所以我们自己将定义一些用例和约束。
+
+### 用例
+
+#### 我们将问题的范畴限定在如下用例
+
+* **用户** 输入一段文本,然后得到一个随机生成的链接
+ * 过期设置
+ * 默认的设置是不会过期的
+ * 可以选择设置一个过期的时间
+* **用户** 输入一个 paste 的 url 后,可以看到它存储的内容
+* **用户** 是匿名的
+* **Service** 跟踪页面分析
+ * 一个月的访问统计
+* **Service** 删除过期的 pastes
+* **Service** 需要高可用
+
+#### 超出范畴的用例
+
+* **用户** 可以注册一个账户
+ * **用户** 通过验证邮箱
+* **用户** 可以用注册的账户登录
+ * **用户** 可以编辑文档
+* **用户** 可以设置可见性
+* **用户** 可以设置短链接
+
+### 约束和假设
+
+#### 状态假设
+
+* 访问流量不是均匀分布的
+* 打开一个短链接应该是很快的
+* pastes 只能是文本
+* 页面访问分析数据可以不用实时
+* 一千万的用户量
+* 每个月一千万的 paste 写入量
+* 每个月一亿的 paste 读取量
+* 读写比例在 10:1
+
+#### 计算使用
+
+**向面试官说明你是否应该粗略计算一下使用情况。**
+
+* 每个 paste 的大小
+ * 每一个 paste 1 KB
+ * `shortlink` - 7 bytes
+ * `expiration_length_in_minutes` - 4 bytes
+ * `created_at` - 5 bytes
+ * `paste_path` - 255 bytes
+ * 总共 = ~1.27 KB
+* 每个月新的 paste 内容在 12.7GB
+ * (1.27 * 10000000)KB / 月的 paste
+ * 三年内将近 450GB 的新 paste 内容
+ * 三年内 3.6 亿短链接
+ * 假设大部分都是新的 paste,而不是需要更新已存在的 paste
+* 平均 4paste/s 的写入速度
+* 平均 40paste/s 的读取速度
+
+简单的转换指南:
+
+* 2.5 百万 req/s
+* 1 req/s = 2.5 百万 req/m
+* 40 req/s = 1 亿 req/m
+* 400 req/s = 10 亿 req/m
+
+## 第二步:创建一个高层次设计
+
+> 概述一个包括所有重要的组件的高层次设计
+
+
+
+## 第三步:设计核心组件
+
+> 深入每一个核心组件的细节
+
+### 用例:用户输入一段文本,然后得到一个随机生成的链接
+
+我们可以用一个 [关系型数据库](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#关系型数据库管理系统rdbms)作为一个大的哈希表,用来把生成的 url 映射到一个包含 paste 文件的文件服务器和路径上。
+
+为了避免托管一个文件服务器,我们可以用一个托管的**对象存储**,比如 Amazon 的 S3 或者[NoSQL 文档类型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#文档类型存储)。
+
+作为一个大的哈希表的关系型数据库的替代方案,我们可以用[NoSQL 键值存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#键-值存储)。我们需要讨论[选择 SQL 或 NoSQL 之间的权衡](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)。下面的讨论是使用关系型数据库方法。
+
+* **客户端** 发送一个创建 paste 的请求到作为一个[反向代理](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)启动的 **Web 服务器**。
+* **Web 服务器** 转发请求给 **写接口** 服务器
+* **写接口** 服务器执行如下操作:
+ * 生成一个唯一的 url
+ * 检查这个 url 在 **SQL 数据库** 里面是否是唯一的
+ * 如果这个 url 不是唯一的,生成另外一个 url
+ * 如果我们支持自定义 url,我们可以使用用户提供的 url(也需要检查是否重复)
+ * 把生成的 url 存储到 **SQL 数据库** 的 `pastes` 表里面
+ * 存储 paste 的内容数据到 **对象存储** 里面
+ * 返回生成的 url
+
+**向面试官阐明你需要写多少代码**
+
+`pastes` 表可以有如下结构:
+
+```sql
+shortlink char(7) NOT NULL
+expiration_length_in_minutes int NOT NULL
+created_at datetime NOT NULL
+paste_path varchar(255) NOT NULL
+PRIMARY KEY(shortlink)
+```
+
+我们将在 `shortlink` 字段和 `created_at` 字段上创建一个[数据库索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#使用正确的索引),用来提高查询的速度(避免因为扫描全表导致的长时间查询)并将数据保存在内存中,从内存里面顺序读取 1MB 的数据需要大概 250 微秒,而从 SSD 上读取则需要花费 4 倍的时间,从硬盘上则需要花费 80 倍的时间。 1
+
+为了生成唯一的 url,我们可以:
+
+* 使用 [**MD5**](https://en.wikipedia.org/wiki/MD5) 来哈希用户的 IP 地址 + 时间戳
+ * MD5 是一个普遍用来生成一个 128-bit 长度的哈希值的一种哈希方法
+ * MD5 是一致分布的
+ * 或者我们也可以用 MD5 哈希一个随机生成的数据
+* 用 [**Base 62**](https://www.kerstner.at/2012/07/shortening-strings-using-base-62-encoding/) 编码 MD5 哈希值
+ * 对于 urls,使用 Base 62 编码 `[a-zA-Z0-9]` 是比较合适的
+ * 对于每一个原始输入只会有一个 hash 结果,Base 62 是确定的(不涉及随机性)
+ * Base 64 是另外一个流行的编码方案,但是对于 urls,会因为额外的 `+` 和 `-` 字符串而产生一些问题
+ * 以下 [Base 62 伪代码](http://stackoverflow.com/questions/742013/how-to-code-a-url-shortener) 执行的时间复杂度是 O(k),k 是数字的数量 = 7:
+
+```python
+def base_encode(num, base=62):
+ digits = []
+ while num > 0
+ remainder = modulo(num, base)
+ digits.push(remainder)
+ num = divide(num, base)
+ digits = digits.reverse
+```
+
+* 取输出的前 7 个字符,结果会有 62^7 个可能的值,应该足以满足在 3 年内处理 3.6 亿个短链接的约束:
+
+```python
+url = base_encode(md5(ip_address+timestamp))[:URL_LENGTH]
+```
+
+我们将会用一个公开的 [**REST 风格接口**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest):
+
+```shell
+$ curl -X POST --data '{"expiration_length_in_minutes":"60", \"paste_contents":"Hello World!"}' https://pastebin.com/api/v1/paste
+```
+
+Response:
+
+```json
+{
+ "shortlink": "foobar"
+}
+```
+
+用于内部通信,我们可以用 [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)。
+
+### 用例:用户输入一个 paste 的 url 后可以看到它存储的内容
+
+* **客户端** 发送一个获取 paste 请求到 **Web Server**
+* **Web Server** 转发请求给 **读取接口** 服务器
+* **读取接口** 服务器执行如下操作:
+ * 在 **SQL 数据库** 检查这个生成的 url
+ * 如果这个 url 在 **SQL 数据库** 里面,则从 **对象存储** 获取这个 paste 的内容
+ * 否则,返回一个错误页面给用户
+
+REST API:
+
+```shell
+curl https://pastebin.com/api/v1/paste?shortlink=foobar
+```
+
+Response:
+
+```json
+{
+ "paste_contents": "Hello World",
+ "created_at": "YYYY-MM-DD HH:MM:SS",
+ "expiration_length_in_minutes": "60"
+}
+```
+
+### 用例: 服务跟踪分析页面
+
+因为实时分析不是必须的,所以我们可以简单的 **MapReduce** **Web Server** 的日志,用来生成点击次数。
+
+```python
+class HitCounts(MRJob):
+
+ def extract_url(self, line):
+ """Extract the generated url from the log line."""
+ ...
+
+ def extract_year_month(self, line):
+ """Return the year and month portions of the timestamp."""
+ ...
+
+ def mapper(self, _, line):
+ """Parse each log line, extract and transform relevant lines.
+
+ Emit key value pairs of the form:
+
+ (2016-01, url0), 1
+ (2016-01, url0), 1
+ (2016-01, url1), 1
+ """
+ url = self.extract_url(line)
+ period = self.extract_year_month(line)
+ yield (period, url), 1
+
+ def reducer(self, key, values):
+ """Sum values for each key.
+
+ (2016-01, url0), 2
+ (2016-01, url1), 1
+ """
+ yield key, sum(values)
+```
+
+### 用例: 服务删除过期的 pastes
+
+为了删除过期的 pastes,我们可以直接搜索 **SQL 数据库** 中所有的过期时间比当前时间更早的记录,
+所有过期的记录将从这张表里面删除(或者将其标记为过期)。
+
+## 第四步:扩展这个设计
+
+> 给定约束条件,识别和解决瓶颈。
+
+
+
+**重要提示: 不要简单的从最初的设计直接跳到最终的设计**
+
+说明您将迭代地执行这样的操作:1)**Benchmark/Load 测试**,2)**Profile** 出瓶颈,3)在评估替代方案和权衡时解决瓶颈,4)重复前面,可以参考[在 AWS 上设计一个可以支持百万用户的系统](../scaling_aws/README.md)这个用来解决如何迭代地扩展初始设计的例子。
+
+重要的是讨论在初始设计中可能遇到的瓶颈,以及如何解决每个瓶颈。比如,在多个 **Web 服务器** 上添加 **负载平衡器** 可以解决哪些问题? **CDN** 解决哪些问题?**Master-Slave Replicas** 解决哪些问题? 替代方案是什么和怎么对每一个替代方案进行权衡比较?
+
+我们将介绍一些组件来完成设计,并解决可伸缩性问题。内部的负载平衡器并不能减少杂乱。
+
+**为了避免重复的讨论**, 参考以下[系统设计主题](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)获取主要讨论要点、权衡和替代方案:
+
+* [DNS](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#域名系统)
+* [CDN](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#内容分发网络cdn)
+* [负载均衡器](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#负载均衡器)
+* [水平扩展](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#水平扩展)
+* [反向代理(web 服务器)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)
+* [应用层](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用层)
+* [缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存)
+* [关系型数据库管理系统 (RDBMS)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#关系型数据库管理系统rdbms)
+* [SQL write master-slave failover](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#故障切换)
+* [主从复制](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#主从复制)
+* [一致性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#一致性模式)
+* [可用性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#可用性模式)
+
+**分析存储数据库** 可以用比如 Amazon Redshift 或者 Google BigQuery 这样的数据仓库解决方案。
+
+一个像 Amazon S3 这样的 **对象存储**,可以轻松处理每月 12.7 GB 的新内容约束。
+
+要处理 *平均* 每秒 40 读请求(峰值更高),其中热点内容的流量应该由 **内存缓存** 处理,而不是数据库。**内存缓存** 对于处理分布不均匀的流量和流量峰值也很有用。只要副本没有陷入复制写的泥潭,**SQL Read Replicas** 应该能够处理缓存丢失。
+
+对于单个 **SQL Write Master-Slave**,*平均* 每秒 4paste 写入 (峰值更高) 应该是可以做到的。否则,我们需要使用额外的 SQL 扩展模式:
+
+* [联合](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#联合)
+* [分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#分片)
+* [非规范化](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#非规范化)
+* [SQL 调优](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#SQL调优)
+
+我们还应该考虑将一些数据移动到 **NoSQL 数据库**。
+
+## 额外的话题
+
+> 是否更深入探讨额外主题,取决于问题的范围和面试剩余的时间。
+
+### NoSQL
+
+* [键值存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#键-值存储)
+* [文档存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#文档类型存储)
+* [列型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#列型存储)
+* [图数据库](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#图数据库)
+* [sql 还是 nosql](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)
+
+### 缓存
+
+* 在哪缓存
+ * [客户端缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#客户端缓存)
+ * [CDN 缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#cdn-缓存)
+ * [Web 服务器缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#web-服务器缓存)
+ * [数据库缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库缓存)
+ * [应用缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用缓存)
+* 缓存什么
+ * [数据库查询级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库查询级别的缓存)
+ * [对象级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#对象级别的缓存)
+* 何时更新缓存
+ * [缓存模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存模式)
+ * [直写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#直写模式)
+ * [回写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#回写模式)
+ * [刷新](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#刷新)
+
+### 异步和微服务
+
+* [消息队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#消息队列)
+* [任务队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#任务队列)
+* [背压](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#背压)
+* [微服务](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#微服务)
+
+### 通信
+
+* 讨论权衡:
+ * 跟客户端之间的外部通信 - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest)
+ * 内部通信 - [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)
+* [服务发现](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#服务发现)
+
+### 安全
+
+参考[安全](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#安全)。
+
+### 延迟数字
+
+见[每个程序员都应该知道的延迟数](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#每个程序员都应该知道的延迟数)。
+
+### 持续进行
+
+* 继续对系统进行基准测试和监控,以在瓶颈出现时解决它们
+* 扩展是一个迭代的过程
diff --git a/website/solutions/system_design/pastebin/README.md b/website/solutions/system_design/pastebin/README.md
new file mode 100644
index 00000000000..2d87ddcc7e3
--- /dev/null
+++ b/website/solutions/system_design/pastebin/README.md
@@ -0,0 +1,332 @@
+# Design Pastebin.com (or Bit.ly)
+
+*Note: This document links directly to relevant areas found in the [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) to avoid duplication. Refer to the linked content for general talking points, tradeoffs, and alternatives.*
+
+**Design Bit.ly** - is a similar question, except pastebin requires storing the paste contents instead of the original unshortened url.
+
+## Step 1: Outline use cases and constraints
+
+> Gather requirements and scope the problem.
+> Ask questions to clarify use cases and constraints.
+> Discuss assumptions.
+
+Without an interviewer to address clarifying questions, we'll define some use cases and constraints.
+
+### Use cases
+
+#### We'll scope the problem to handle only the following use cases
+
+* **User** enters a block of text and gets a randomly generated link
+ * Expiration
+ * Default setting does not expire
+ * Can optionally set a timed expiration
+* **User** enters a paste's url and views the contents
+* **User** is anonymous
+* **Service** tracks analytics of pages
+ * Monthly visit stats
+* **Service** deletes expired pastes
+* **Service** has high availability
+
+#### Out of scope
+
+* **User** registers for an account
+ * **User** verifies email
+* **User** logs into a registered account
+ * **User** edits the document
+* **User** can set visibility
+* **User** can set the shortlink
+
+### Constraints and assumptions
+
+#### State assumptions
+
+* Traffic is not evenly distributed
+* Following a short link should be fast
+* Pastes are text only
+* Page view analytics do not need to be realtime
+* 10 million users
+* 10 million paste writes per month
+* 100 million paste reads per month
+* 10:1 read to write ratio
+
+#### Calculate usage
+
+**Clarify with your interviewer if you should run back-of-the-envelope usage calculations.**
+
+* Size per paste
+ * 1 KB content per paste
+ * `shortlink` - 7 bytes
+ * `expiration_length_in_minutes` - 4 bytes
+ * `created_at` - 5 bytes
+ * `paste_path` - 255 bytes
+ * total = ~1.27 KB
+* 12.7 GB of new paste content per month
+ * 1.27 KB per paste * 10 million pastes per month
+ * ~450 GB of new paste content in 3 years
+ * 360 million shortlinks in 3 years
+ * Assume most are new pastes instead of updates to existing ones
+* 4 paste writes per second on average
+* 40 read requests per second on average
+
+Handy conversion guide:
+
+* 2.5 million seconds per month
+* 1 request per second = 2.5 million requests per month
+* 40 requests per second = 100 million requests per month
+* 400 requests per second = 1 billion requests per month
+
+## Step 2: Create a high level design
+
+> Outline a high level design with all important components.
+
+
+
+## Step 3: Design core components
+
+> Dive into details for each core component.
+
+### Use case: User enters a block of text and gets a randomly generated link
+
+We could use a [relational database](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms) as a large hash table, mapping the generated url to a file server and path containing the paste file.
+
+Instead of managing a file server, we could use a managed **Object Store** such as Amazon S3 or a [NoSQL document store](https://github.com/donnemartin/system-design-primer#document-store).
+
+An alternative to a relational database acting as a large hash table, we could use a [NoSQL key-value store](https://github.com/donnemartin/system-design-primer#key-value-store). We should discuss the [tradeoffs between choosing SQL or NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql). The following discussion uses the relational database approach.
+
+* The **Client** sends a create paste request to the **Web Server**, running as a [reverse proxy](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* The **Web Server** forwards the request to the **Write API** server
+* The **Write API** server does the following:
+ * Generates a unique url
+ * Checks if the url is unique by looking at the **SQL Database** for a duplicate
+ * If the url is not unique, it generates another url
+ * If we supported a custom url, we could use the user-supplied (also check for a duplicate)
+ * Saves to the **SQL Database** `pastes` table
+ * Saves the paste data to the **Object Store**
+ * Returns the url
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+The `pastes` table could have the following structure:
+
+```
+shortlink char(7) NOT NULL
+expiration_length_in_minutes int NOT NULL
+created_at datetime NOT NULL
+paste_path varchar(255) NOT NULL
+PRIMARY KEY(shortlink)
+```
+
+Setting the primary key to be based on the `shortlink` column creates an [index](https://github.com/donnemartin/system-design-primer#use-good-indices) that the database uses to enforce uniqueness. We'll create an additional index on `created_at` to speed up lookups (log-time instead of scanning the entire table) and to keep the data in memory. Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.1
+
+To generate the unique url, we could:
+
+* Take the [**MD5**](https://en.wikipedia.org/wiki/MD5) hash of the user's ip_address + timestamp
+ * MD5 is a widely used hashing function that produces a 128-bit hash value
+ * MD5 is uniformly distributed
+ * Alternatively, we could also take the MD5 hash of randomly-generated data
+* [**Base 62**](https://www.kerstner.at/2012/07/shortening-strings-using-base-62-encoding/) encode the MD5 hash
+ * Base 62 encodes to `[a-zA-Z0-9]` which works well for urls, eliminating the need for escaping special characters
+ * There is only one hash result for the original input and Base 62 is deterministic (no randomness involved)
+ * Base 64 is another popular encoding but provides issues for urls because of the additional `+` and `/` characters
+ * The following [Base 62 pseudocode](http://stackoverflow.com/questions/742013/how-to-code-a-url-shortener) runs in O(k) time where k is the number of digits = 7:
+
+```python
+def base_encode(num, base=62):
+ digits = []
+ while num > 0
+ remainder = modulo(num, base)
+ digits.push(remainder)
+ num = divide(num, base)
+ digits = digits.reverse
+```
+
+* Take the first 7 characters of the output, which results in 62^7 possible values and should be sufficient to handle our constraint of 360 million shortlinks in 3 years:
+
+```python
+url = base_encode(md5(ip_address+timestamp))[:URL_LENGTH]
+```
+
+We'll use a public [**REST API**](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest):
+
+```
+$ curl -X POST --data '{ "expiration_length_in_minutes": "60", \
+ "paste_contents": "Hello World!" }' https://pastebin.com/api/v1/paste
+```
+
+Response:
+
+```
+{
+ "shortlink": "foobar"
+}
+```
+
+For internal communications, we could use [Remote Procedure Calls](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc).
+
+### Use case: User enters a paste's url and views the contents
+
+* The **Client** sends a get paste request to the **Web Server**
+* The **Web Server** forwards the request to the **Read API** server
+* The **Read API** server does the following:
+ * Checks the **SQL Database** for the generated url
+ * If the url is in the **SQL Database**, fetch the paste contents from the **Object Store**
+ * Else, return an error message for the user
+
+REST API:
+
+```
+$ curl https://pastebin.com/api/v1/paste?shortlink=foobar
+```
+
+Response:
+
+```
+{
+ "paste_contents": "Hello World"
+ "created_at": "YYYY-MM-DD HH:MM:SS"
+ "expiration_length_in_minutes": "60"
+}
+```
+
+### Use case: Service tracks analytics of pages
+
+Since realtime analytics are not a requirement, we could simply **MapReduce** the **Web Server** logs to generate hit counts.
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+```python
+class HitCounts(MRJob):
+
+ def extract_url(self, line):
+ """Extract the generated url from the log line."""
+ ...
+
+ def extract_year_month(self, line):
+ """Return the year and month portions of the timestamp."""
+ ...
+
+ def mapper(self, _, line):
+ """Parse each log line, extract and transform relevant lines.
+
+ Emit key value pairs of the form:
+
+ (2016-01, url0), 1
+ (2016-01, url0), 1
+ (2016-01, url1), 1
+ """
+ url = self.extract_url(line)
+ period = self.extract_year_month(line)
+ yield (period, url), 1
+
+ def reducer(self, key, values):
+ """Sum values for each key.
+
+ (2016-01, url0), 2
+ (2016-01, url1), 1
+ """
+ yield key, sum(values)
+```
+
+### Use case: Service deletes expired pastes
+
+To delete expired pastes, we could just scan the **SQL Database** for all entries whose expiration timestamp are older than the current timestamp. All expired entries would then be deleted (or marked as expired) from the table.
+
+## Step 4: Scale the design
+
+> Identify and address bottlenecks, given the constraints.
+
+
+
+**Important: Do not simply jump right into the final design from the initial design!**
+
+State you would do this iteratively: 1) **Benchmark/Load Test**, 2) **Profile** for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat. See [Design a system that scales to millions of users on AWS](../scaling_aws/README.md) as a sample on how to iteratively scale the initial design.
+
+It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them. For example, what issues are addressed by adding a **Load Balancer** with multiple **Web Servers**? **CDN**? **Master-Slave Replicas**? What are the alternatives and **Trade-Offs** for each?
+
+We'll introduce some components to complete the design and to address scalability issues. Internal load balancers are not shown to reduce clutter.
+
+*To avoid repeating discussions*, refer to the following [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) for main talking points, tradeoffs, and alternatives:
+
+* [DNS](https://github.com/donnemartin/system-design-primer#domain-name-system)
+* [CDN](https://github.com/donnemartin/system-design-primer#content-delivery-network)
+* [Load balancer](https://github.com/donnemartin/system-design-primer#load-balancer)
+* [Horizontal scaling](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+* [Web server (reverse proxy)](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* [API server (application layer)](https://github.com/donnemartin/system-design-primer#application-layer)
+* [Cache](https://github.com/donnemartin/system-design-primer#cache)
+* [Relational database management system (RDBMS)](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms)
+* [SQL write master-slave failover](https://github.com/donnemartin/system-design-primer#fail-over)
+* [Master-slave replication](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [Consistency patterns](https://github.com/donnemartin/system-design-primer#consistency-patterns)
+* [Availability patterns](https://github.com/donnemartin/system-design-primer#availability-patterns)
+
+The **Analytics Database** could use a data warehousing solution such as Amazon Redshift or Google BigQuery.
+
+An **Object Store** such as Amazon S3 can comfortably handle the constraint of 12.7 GB of new content per month.
+
+To address the 40 *average* read requests per second (higher at peak), traffic for popular content should be handled by the **Memory Cache** instead of the database. The **Memory Cache** is also useful for handling the unevenly distributed traffic and traffic spikes. The **SQL Read Replicas** should be able to handle the cache misses, as long as the replicas are not bogged down with replicating writes.
+
+4 *average* paste writes per second (with higher at peak) should be do-able for a single **SQL Write Master-Slave**. Otherwise, we'll need to employ additional SQL scaling patterns:
+
+* [Federation](https://github.com/donnemartin/system-design-primer#federation)
+* [Sharding](https://github.com/donnemartin/system-design-primer#sharding)
+* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+We should also consider moving some data to a **NoSQL Database**.
+
+## Additional talking points
+
+> Additional topics to dive into, depending on the problem scope and time remaining.
+
+#### NoSQL
+
+* [Key-value store](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [Document store](https://github.com/donnemartin/system-design-primer#document-store)
+* [Wide column store](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [Graph database](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### Caching
+
+* Where to cache
+ * [Client caching](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN caching](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web server caching](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [Database caching](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [Application caching](https://github.com/donnemartin/system-design-primer#application-caching)
+* What to cache
+ * [Caching at the database query level](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [Caching at the object level](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* When to update the cache
+ * [Cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [Write-through](https://github.com/donnemartin/system-design-primer#write-through)
+ * [Write-behind (write-back)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [Refresh ahead](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### Asynchronism and microservices
+
+* [Message queues](https://github.com/donnemartin/system-design-primer#message-queues)
+* [Task queues](https://github.com/donnemartin/system-design-primer#task-queues)
+* [Back pressure](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [Microservices](https://github.com/donnemartin/system-design-primer#microservices)
+
+### Communications
+
+* Discuss tradeoffs:
+ * External communication with clients - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * Internal communications - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [Service discovery](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### Security
+
+Refer to the [security section](https://github.com/donnemartin/system-design-primer#security).
+
+### Latency numbers
+
+See [Latency numbers every programmer should know](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know).
+
+### Ongoing
+
+* Continue benchmarking and monitoring your system to address bottlenecks as they come up
+* Scaling is an iterative process
diff --git a/website/solutions/system_design/pastebin/__init__.py b/website/solutions/system_design/pastebin/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/system_design/pastebin/pastebin.graffle b/website/solutions/system_design/pastebin/pastebin.graffle
new file mode 100644
index 00000000000..879a6275331
Binary files /dev/null and b/website/solutions/system_design/pastebin/pastebin.graffle differ
diff --git a/website/solutions/system_design/pastebin/pastebin.png b/website/solutions/system_design/pastebin/pastebin.png
new file mode 100644
index 00000000000..1841f0a489c
Binary files /dev/null and b/website/solutions/system_design/pastebin/pastebin.png differ
diff --git a/website/solutions/system_design/pastebin/pastebin.py b/website/solutions/system_design/pastebin/pastebin.py
new file mode 100644
index 00000000000..7e8d268a0ea
--- /dev/null
+++ b/website/solutions/system_design/pastebin/pastebin.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+from mrjob.job import MRJob
+
+
+class HitCounts(MRJob):
+
+ def extract_url(self, line):
+ """Extract the generated url from the log line."""
+ pass
+
+ def extract_year_month(self, line):
+ """Return the year and month portions of the timestamp."""
+ pass
+
+ def mapper(self, _, line):
+ """Parse each log line, extract and transform relevant lines.
+
+ Emit key value pairs of the form:
+
+ (2016-01, url0), 1
+ (2016-01, url0), 1
+ (2016-01, url1), 1
+ """
+ url = self.extract_url(line)
+ period = self.extract_year_month(line)
+ yield (period, url), 1
+
+ def reducer(self, key, values):
+ """Sum values for each key.
+
+ (2016-01, url0), 2
+ (2016-01, url1), 1
+ """
+ yield key, sum(values)
+
+ def steps(self):
+ """Run the map and reduce steps."""
+ return [
+ self.mr(mapper=self.mapper,
+ reducer=self.reducer)
+ ]
+
+
+if __name__ == '__main__':
+ HitCounts.run()
diff --git a/website/solutions/system_design/pastebin/pastebin_basic.graffle b/website/solutions/system_design/pastebin/pastebin_basic.graffle
new file mode 100644
index 00000000000..fad80c23150
Binary files /dev/null and b/website/solutions/system_design/pastebin/pastebin_basic.graffle differ
diff --git a/website/solutions/system_design/pastebin/pastebin_basic.png b/website/solutions/system_design/pastebin/pastebin_basic.png
new file mode 100644
index 00000000000..4cee694281e
Binary files /dev/null and b/website/solutions/system_design/pastebin/pastebin_basic.png differ
diff --git a/website/solutions/system_design/query_cache/README-zh-Hans.md b/website/solutions/system_design/query_cache/README-zh-Hans.md
new file mode 100644
index 00000000000..c6f4be7519b
--- /dev/null
+++ b/website/solutions/system_design/query_cache/README-zh-Hans.md
@@ -0,0 +1,306 @@
+# 设计一个键-值缓存来存储最近 web 服务查询的结果
+
+**注意:这个文档中的链接会直接指向[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)中的有关部分,以避免重复的内容。你可以参考链接的相关内容,来了解其总的要点、方案的权衡取舍以及可选的替代方案。**
+
+## 第一步:简述用例与约束条件
+
+> 搜集需求与问题的范围。
+> 提出问题来明确用例与约束条件。
+> 讨论假设。
+
+我们将在没有面试官明确说明问题的情况下,自己定义一些用例以及限制条件。
+
+### 用例
+
+#### 我们将把问题限定在仅处理以下用例的范围中
+
+* **用户**发送一个搜索请求,命中缓存
+* **用户**发送一个搜索请求,未命中缓存
+* **服务**有着高可用性
+
+### 限制条件与假设
+
+#### 提出假设
+
+* 网络流量不是均匀分布的
+ * 经常被查询的内容应该一直存于缓存中
+ * 需要确定如何规定缓存过期、缓存刷新规则
+* 缓存提供的服务查询速度要快
+* 机器间延迟较低
+* 缓存有内存限制
+ * 需要决定缓存什么、移除什么
+ * 需要缓存百万级的查询
+* 1000 万用户
+* 每个月 100 亿次查询
+
+#### 计算用量
+
+**如果你需要进行粗略的用量计算,请向你的面试官说明。**
+
+* 缓存存储的是键值对有序表,键为 `query`(查询),值为 `results`(结果)。
+ * `query` - 50 字节
+ * `title` - 20 字节
+ * `snippet` - 200 字节
+ * 总计:270 字节
+* 假如 100 亿次查询都是不同的,且全部需要存储,那么每个月需要 2.7 TB 的缓存空间
+ * 单次查询 270 字节 * 每月查询 100 亿次
+ * 假设内存大小有限制,需要决定如何制定缓存过期规则
+* 每秒 4,000 次请求
+
+便利换算指南:
+
+* 每个月有 250 万秒
+* 每秒一个请求 = 每个月 250 万次请求
+* 每秒 40 个请求 = 每个月 1 亿次请求
+* 每秒 400 个请求 = 每个月 10 亿次请求
+
+## 第二步:概要设计
+
+> 列出所有重要组件以规划概要设计。
+
+
+
+## 第三步:设计核心组件
+
+> 深入每个核心组件的细节。
+
+### 用例:用户发送了一次请求,命中了缓存
+
+常用的查询可以由例如 Redis 或者 Memcached 之类的**内存缓存**提供支持,以减少数据读取延迟,并且避免**反向索引服务**以及**文档服务**的过载。从内存读取 1 MB 连续数据大约要花 250 微秒,而从 SSD 读取同样大小的数据要花费 4 倍的时间,从机械硬盘读取需要花费 80 倍以上的时间。1
+
+由于缓存容量有限,我们将使用 LRU(近期最少使用算法)来控制缓存的过期。
+
+* **客户端**向运行[反向代理](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)的 **Web 服务器**发送一个请求
+* 这个 **Web 服务器**将请求转发给**查询 API** 服务
+* **查询 API** 服务将会做这些事情:
+ * 分析查询
+ * 移除多余的内容
+ * 将文本分割成词组
+ * 修正拼写错误
+ * 规范化字母的大小写
+ * 将查询转换为布尔运算
+ * 检测**内存缓存**是否有匹配查询的内容
+ * 如果命中**内存缓存**,**内存缓存**将会做以下事情:
+ * 将缓存入口的位置指向 LRU 链表的头部
+ * 返回缓存内容
+ * 否则,**查询 API** 将会做以下事情:
+ * 使用**反向索引服务**来查找匹配查询的文档
+ * **反向索引服务**对匹配到的结果进行排名,然后返回最符合的结果
+ * 使用**文档服务**返回文章标题与片段
+ * 更新**内存缓存**,存入内容,将**内存缓存**入口位置指向 LRU 链表的头部
+
+#### 缓存的实现
+
+缓存可以使用双向链表实现:新元素将会在头结点加入,过期的元素将会在尾节点被删除。我们使用哈希表以便能够快速查找每个链表节点。
+
+**向你的面试官告知你准备写多少代码**。
+
+实现**查询 API 服务**:
+
+```python
+class QueryApi(object):
+
+ def __init__(self, memory_cache, reverse_index_service):
+ self.memory_cache = memory_cache
+ self.reverse_index_service = reverse_index_service
+
+ def parse_query(self, query):
+ """移除多余内容,将文本分割成词组,修复拼写错误,
+ 规范化字母大小写,转换布尔运算。
+ """
+ ...
+
+ def process_query(self, query):
+ query = self.parse_query(query)
+ results = self.memory_cache.get(query)
+ if results is None:
+ results = self.reverse_index_service.process_search(query)
+ self.memory_cache.set(query, results)
+ return results
+```
+
+实现**节点**:
+
+```python
+class Node(object):
+
+ def __init__(self, query, results):
+ self.query = query
+ self.results = results
+```
+
+实现**链表**:
+
+```python
+class LinkedList(object):
+
+ def __init__(self):
+ self.head = None
+ self.tail = None
+
+ def move_to_front(self, node):
+ ...
+
+ def append_to_front(self, node):
+ ...
+
+ def remove_from_tail(self):
+ ...
+```
+
+实现**缓存**:
+
+```python
+class Cache(object):
+
+ def __init__(self, MAX_SIZE):
+ self.MAX_SIZE = MAX_SIZE
+ self.size = 0
+ self.lookup = {} # key: query, value: node
+ self.linked_list = LinkedList()
+
+ def get(self, query)
+ """从缓存取得存储的内容
+
+ 将入口节点位置更新为 LRU 链表的头部。
+ """
+ node = self.lookup[query]
+ if node is None:
+ return None
+ self.linked_list.move_to_front(node)
+ return node.results
+
+ def set(self, results, query):
+ """将所给查询键的结果存在缓存中。
+
+ 当更新缓存记录的时候,将它的位置指向 LRU 链表的头部。
+ 如果这个记录是新的记录,并且缓存空间已满,应该在加入新记录前
+ 删除最老的记录。
+ """
+ node = self.lookup[query]
+ if node is not None:
+ # 键存在于缓存中,更新它对应的值
+ node.results = results
+ self.linked_list.move_to_front(node)
+ else:
+ # 键不存在于缓存中
+ if self.size == self.MAX_SIZE:
+ # 在链表中查找并删除最老的记录
+ self.lookup.pop(self.linked_list.tail.query, None)
+ self.linked_list.remove_from_tail()
+ else:
+ self.size += 1
+ # 添加新的键值对
+ new_node = Node(query, results)
+ self.linked_list.append_to_front(new_node)
+ self.lookup[query] = new_node
+```
+
+#### 何时更新缓存
+
+缓存将会在以下几种情况更新:
+
+* 页面内容发生变化
+* 页面被移除或者加入了新页面
+* 页面的权值发生变动
+
+解决这些问题的最直接的方法,就是为缓存记录设置一个它在被更新前能留在缓存中的最长时间,这个时间简称为存活时间(TTL)。
+
+参考 [「何时更新缓存」](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#何时更新缓存)来了解其权衡取舍及替代方案。以上方法在[缓存模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存模式)一章中详细地进行了描述。
+
+## 第四步:架构扩展
+
+> 根据限制条件,找到并解决瓶颈。
+
+
+
+**重要提示:不要从最初设计直接跳到最终设计中!**
+
+现在你要 1) **基准测试、负载测试**。2) **分析、描述**性能瓶颈。3) 在解决瓶颈问题的同时,评估替代方案、权衡利弊。4) 重复以上步骤。请阅读[「设计一个系统,并将其扩大到为数以百万计的 AWS 用户服务」](../scaling_aws/README.md) 来了解如何逐步扩大初始设计。
+
+讨论初始设计可能遇到的瓶颈及相关解决方案是很重要的。例如加上一个配置多台 **Web 服务器**的**负载均衡器**是否能够解决问题?**CDN**呢?**主从复制**呢?它们各自的替代方案和需要**权衡**的利弊又有什么呢?
+
+我们将会介绍一些组件来完成设计,并解决架构扩张问题。内置的负载均衡器将不做讨论以节省篇幅。
+
+**为了避免重复讨论**,请参考[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)相关部分来了解其要点、方案的权衡取舍以及可选的替代方案。
+
+* [DNS](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#域名系统)
+* [负载均衡器](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#负载均衡器)
+* [水平拓展](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#水平扩展)
+* [反向代理(web 服务器)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)
+* [API 服务(应用层)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用层)
+* [缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存)
+* [一致性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#一致性模式)
+* [可用性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#可用性模式)
+
+### 将内存缓存扩大到多台机器
+
+为了解决庞大的请求负载以及巨大的内存需求,我们将要对架构进行水平拓展。如何在我们的**内存缓存**集群中存储数据呢?我们有以下三个主要可选方案:
+
+* **缓存集群中的每一台机器都有自己的缓存** - 简单,但是它会降低缓存命中率。
+* **缓存集群中的每一台机器都有缓存的拷贝** - 简单,但是它的内存使用效率太低了。
+* **对缓存进行[分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#分片),分别部署在缓存集群中的所有机器中** - 更加复杂,但是它是最佳的选择。我们可以使用哈希,用查询语句 `machine = hash(query)` 来确定哪台机器有需要缓存。当然我们也可以使用[一致性哈希](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#正在完善中)。
+
+## 其它要点
+
+> 是否深入这些额外的主题,取决于你的问题范围和剩下的时间。
+
+### SQL 缩放模式
+
+* [读取复制](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#主从复制)
+* [联合](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#联合)
+* [分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#分片)
+* [非规范化](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#非规范化)
+* [SQL 调优](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-调优)
+
+#### NoSQL
+
+* [键-值存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#键-值存储)
+* [文档类型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#文档类型存储)
+* [列型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#列型存储)
+* [图数据库](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#图数据库)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)
+
+### 缓存
+
+* 在哪缓存
+ * [客户端缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#客户端缓存)
+ * [CDN 缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#cdn-缓存)
+ * [Web 服务器缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#web-服务器缓存)
+ * [数据库缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库缓存)
+ * [应用缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用缓存)
+* 什么需要缓存
+ * [数据库查询级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库查询级别的缓存)
+ * [对象级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#对象级别的缓存)
+* 何时更新缓存
+ * [缓存模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存模式)
+ * [直写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#直写模式)
+ * [回写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#回写模式)
+ * [刷新](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#刷新)
+
+### 异步与微服务
+
+* [消息队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#消息队列)
+* [任务队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#任务队列)
+* [背压](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#背压)
+* [微服务](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#微服务)
+
+### 通信
+
+* 可权衡选择的方案:
+ * 与客户端的外部通信 - [使用 REST 作为 HTTP API](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest)
+ * 服务器内部通信 - [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)
+* [服务发现](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#服务发现)
+
+### 安全性
+
+请参阅[「安全」](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#安全)一章。
+
+### 延迟数值
+
+请参阅[「每个程序员都应该知道的延迟数」](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#每个程序员都应该知道的延迟数)。
+
+### 持续探讨
+
+* 持续进行基准测试并监控你的系统,以解决他们提出的瓶颈问题。
+* 架构拓展是一个迭代的过程。
diff --git a/website/solutions/system_design/query_cache/README.md b/website/solutions/system_design/query_cache/README.md
new file mode 100644
index 00000000000..032adf34abd
--- /dev/null
+++ b/website/solutions/system_design/query_cache/README.md
@@ -0,0 +1,306 @@
+# Design a key-value cache to save the results of the most recent web server queries
+
+*Note: This document links directly to relevant areas found in the [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) to avoid duplication. Refer to the linked content for general talking points, tradeoffs, and alternatives.*
+
+## Step 1: Outline use cases and constraints
+
+> Gather requirements and scope the problem.
+> Ask questions to clarify use cases and constraints.
+> Discuss assumptions.
+
+Without an interviewer to address clarifying questions, we'll define some use cases and constraints.
+
+### Use cases
+
+#### We'll scope the problem to handle only the following use cases
+
+* **User** sends a search request resulting in a cache hit
+* **User** sends a search request resulting in a cache miss
+* **Service** has high availability
+
+### Constraints and assumptions
+
+#### State assumptions
+
+* Traffic is not evenly distributed
+ * Popular queries should almost always be in the cache
+ * Need to determine how to expire/refresh
+* Serving from cache requires fast lookups
+* Low latency between machines
+* Limited memory in cache
+ * Need to determine what to keep/remove
+ * Need to cache millions of queries
+* 10 million users
+* 10 billion queries per month
+
+#### Calculate usage
+
+**Clarify with your interviewer if you should run back-of-the-envelope usage calculations.**
+
+* Cache stores ordered list of key: query, value: results
+ * `query` - 50 bytes
+ * `title` - 20 bytes
+ * `snippet` - 200 bytes
+ * Total: 270 bytes
+* 2.7 TB of cache data per month if all 10 billion queries are unique and all are stored
+ * 270 bytes per search * 10 billion searches per month
+ * Assumptions state limited memory, need to determine how to expire contents
+* 4,000 requests per second
+
+Handy conversion guide:
+
+* 2.5 million seconds per month
+* 1 request per second = 2.5 million requests per month
+* 40 requests per second = 100 million requests per month
+* 400 requests per second = 1 billion requests per month
+
+## Step 2: Create a high level design
+
+> Outline a high level design with all important components.
+
+
+
+## Step 3: Design core components
+
+> Dive into details for each core component.
+
+### Use case: User sends a request resulting in a cache hit
+
+Popular queries can be served from a **Memory Cache** such as Redis or Memcached to reduce read latency and to avoid overloading the **Reverse Index Service** and **Document Service**. Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.1
+
+Since the cache has limited capacity, we'll use a least recently used (LRU) approach to expire older entries.
+
+* The **Client** sends a request to the **Web Server**, running as a [reverse proxy](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* The **Web Server** forwards the request to the **Query API** server
+* The **Query API** server does the following:
+ * Parses the query
+ * Removes markup
+ * Breaks up the text into terms
+ * Fixes typos
+ * Normalizes capitalization
+ * Converts the query to use boolean operations
+ * Checks the **Memory Cache** for the content matching the query
+ * If there's a hit in the **Memory Cache**, the **Memory Cache** does the following:
+ * Updates the cached entry's position to the front of the LRU list
+ * Returns the cached contents
+ * Else, the **Query API** does the following:
+ * Uses the **Reverse Index Service** to find documents matching the query
+ * The **Reverse Index Service** ranks the matching results and returns the top ones
+ * Uses the **Document Service** to return titles and snippets
+ * Updates the **Memory Cache** with the contents, placing the entry at the front of the LRU list
+
+#### Cache implementation
+
+The cache can use a doubly-linked list: new items will be added to the head while items to expire will be removed from the tail. We'll use a hash table for fast lookups to each linked list node.
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+**Query API Server** implementation:
+
+```python
+class QueryApi(object):
+
+ def __init__(self, memory_cache, reverse_index_service):
+ self.memory_cache = memory_cache
+ self.reverse_index_service = reverse_index_service
+
+ def parse_query(self, query):
+ """Remove markup, break text into terms, deal with typos,
+ normalize capitalization, convert to use boolean operations.
+ """
+ ...
+
+ def process_query(self, query):
+ query = self.parse_query(query)
+ results = self.memory_cache.get(query)
+ if results is None:
+ results = self.reverse_index_service.process_search(query)
+ self.memory_cache.set(query, results)
+ return results
+```
+
+**Node** implementation:
+
+```python
+class Node(object):
+
+ def __init__(self, query, results):
+ self.query = query
+ self.results = results
+```
+
+**LinkedList** implementation:
+
+```python
+class LinkedList(object):
+
+ def __init__(self):
+ self.head = None
+ self.tail = None
+
+ def move_to_front(self, node):
+ ...
+
+ def append_to_front(self, node):
+ ...
+
+ def remove_from_tail(self):
+ ...
+```
+
+**Cache** implementation:
+
+```python
+class Cache(object):
+
+ def __init__(self, MAX_SIZE):
+ self.MAX_SIZE = MAX_SIZE
+ self.size = 0
+ self.lookup = {} # key: query, value: node
+ self.linked_list = LinkedList()
+
+ def get(self, query)
+ """Get the stored query result from the cache.
+
+ Accessing a node updates its position to the front of the LRU list.
+ """
+ node = self.lookup[query]
+ if node is None:
+ return None
+ self.linked_list.move_to_front(node)
+ return node.results
+
+ def set(self, results, query):
+ """Set the result for the given query key in the cache.
+
+ When updating an entry, updates its position to the front of the LRU list.
+ If the entry is new and the cache is at capacity, removes the oldest entry
+ before the new entry is added.
+ """
+ node = self.lookup[query]
+ if node is not None:
+ # Key exists in cache, update the value
+ node.results = results
+ self.linked_list.move_to_front(node)
+ else:
+ # Key does not exist in cache
+ if self.size == self.MAX_SIZE:
+ # Remove the oldest entry from the linked list and lookup
+ self.lookup.pop(self.linked_list.tail.query, None)
+ self.linked_list.remove_from_tail()
+ else:
+ self.size += 1
+ # Add the new key and value
+ new_node = Node(query, results)
+ self.linked_list.append_to_front(new_node)
+ self.lookup[query] = new_node
+```
+
+#### When to update the cache
+
+The cache should be updated when:
+
+* The page contents change
+* The page is removed or a new page is added
+* The page rank changes
+
+The most straightforward way to handle these cases is to simply set a max time that a cached entry can stay in the cache before it is updated, usually referred to as time to live (TTL).
+
+Refer to [When to update the cache](https://github.com/donnemartin/system-design-primer#when-to-update-the-cache) for tradeoffs and alternatives. The approach above describes [cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside).
+
+## Step 4: Scale the design
+
+> Identify and address bottlenecks, given the constraints.
+
+
+
+**Important: Do not simply jump right into the final design from the initial design!**
+
+State you would 1) **Benchmark/Load Test**, 2) **Profile** for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat. See [Design a system that scales to millions of users on AWS](../scaling_aws/README.md) as a sample on how to iteratively scale the initial design.
+
+It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them. For example, what issues are addressed by adding a **Load Balancer** with multiple **Web Servers**? **CDN**? **Master-Slave Replicas**? What are the alternatives and **Trade-Offs** for each?
+
+We'll introduce some components to complete the design and to address scalability issues. Internal load balancers are not shown to reduce clutter.
+
+*To avoid repeating discussions*, refer to the following [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) for main talking points, tradeoffs, and alternatives:
+
+* [DNS](https://github.com/donnemartin/system-design-primer#domain-name-system)
+* [Load balancer](https://github.com/donnemartin/system-design-primer#load-balancer)
+* [Horizontal scaling](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+* [Web server (reverse proxy)](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* [API server (application layer)](https://github.com/donnemartin/system-design-primer#application-layer)
+* [Cache](https://github.com/donnemartin/system-design-primer#cache)
+* [Consistency patterns](https://github.com/donnemartin/system-design-primer#consistency-patterns)
+* [Availability patterns](https://github.com/donnemartin/system-design-primer#availability-patterns)
+
+### Expanding the Memory Cache to many machines
+
+To handle the heavy request load and the large amount of memory needed, we'll scale horizontally. We have three main options on how to store the data on our **Memory Cache** cluster:
+
+* **Each machine in the cache cluster has its own cache** - Simple, although it will likely result in a low cache hit rate.
+* **Each machine in the cache cluster has a copy of the cache** - Simple, although it is an inefficient use of memory.
+* **The cache is [sharded](https://github.com/donnemartin/system-design-primer#sharding) across all machines in the cache cluster** - More complex, although it is likely the best option. We could use hashing to determine which machine could have the cached results of a query using `machine = hash(query)`. We'll likely want to use [consistent hashing](https://github.com/donnemartin/system-design-primer#under-development).
+
+## Additional talking points
+
+> Additional topics to dive into, depending on the problem scope and time remaining.
+
+### SQL scaling patterns
+
+* [Read replicas](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [Federation](https://github.com/donnemartin/system-design-primer#federation)
+* [Sharding](https://github.com/donnemartin/system-design-primer#sharding)
+* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+#### NoSQL
+
+* [Key-value store](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [Document store](https://github.com/donnemartin/system-design-primer#document-store)
+* [Wide column store](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [Graph database](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### Caching
+
+* Where to cache
+ * [Client caching](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN caching](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web server caching](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [Database caching](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [Application caching](https://github.com/donnemartin/system-design-primer#application-caching)
+* What to cache
+ * [Caching at the database query level](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [Caching at the object level](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* When to update the cache
+ * [Cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [Write-through](https://github.com/donnemartin/system-design-primer#write-through)
+ * [Write-behind (write-back)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [Refresh ahead](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### Asynchronism and microservices
+
+* [Message queues](https://github.com/donnemartin/system-design-primer#message-queues)
+* [Task queues](https://github.com/donnemartin/system-design-primer#task-queues)
+* [Back pressure](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [Microservices](https://github.com/donnemartin/system-design-primer#microservices)
+
+### Communications
+
+* Discuss tradeoffs:
+ * External communication with clients - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * Internal communications - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [Service discovery](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### Security
+
+Refer to the [security section](https://github.com/donnemartin/system-design-primer#security).
+
+### Latency numbers
+
+See [Latency numbers every programmer should know](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know).
+
+### Ongoing
+
+* Continue benchmarking and monitoring your system to address bottlenecks as they come up
+* Scaling is an iterative process
diff --git a/website/solutions/system_design/query_cache/__init__.py b/website/solutions/system_design/query_cache/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/system_design/query_cache/query_cache.graffle b/website/solutions/system_design/query_cache/query_cache.graffle
new file mode 100644
index 00000000000..bb122b4f057
Binary files /dev/null and b/website/solutions/system_design/query_cache/query_cache.graffle differ
diff --git a/website/solutions/system_design/query_cache/query_cache.png b/website/solutions/system_design/query_cache/query_cache.png
new file mode 100644
index 00000000000..55c93f402ec
Binary files /dev/null and b/website/solutions/system_design/query_cache/query_cache.png differ
diff --git a/website/solutions/system_design/query_cache/query_cache_basic.graffle b/website/solutions/system_design/query_cache/query_cache_basic.graffle
new file mode 100644
index 00000000000..27ebf47fb20
Binary files /dev/null and b/website/solutions/system_design/query_cache/query_cache_basic.graffle differ
diff --git a/website/solutions/system_design/query_cache/query_cache_basic.png b/website/solutions/system_design/query_cache/query_cache_basic.png
new file mode 100644
index 00000000000..a1128e541e6
Binary files /dev/null and b/website/solutions/system_design/query_cache/query_cache_basic.png differ
diff --git a/website/solutions/system_design/query_cache/query_cache_snippets.py b/website/solutions/system_design/query_cache/query_cache_snippets.py
new file mode 100644
index 00000000000..19d3f5cdd63
--- /dev/null
+++ b/website/solutions/system_design/query_cache/query_cache_snippets.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+
+
+class QueryApi(object):
+
+ def __init__(self, memory_cache, reverse_index_cluster):
+ self.memory_cache = memory_cache
+ self.reverse_index_cluster = reverse_index_cluster
+
+ def parse_query(self, query):
+ """Remove markup, break text into terms, deal with typos,
+ normalize capitalization, convert to use boolean operations.
+ """
+ ...
+
+ def process_query(self, query):
+ query = self.parse_query(query)
+ results = self.memory_cache.get(query)
+ if results is None:
+ results = self.reverse_index_cluster.process_search(query)
+ self.memory_cache.set(query, results)
+ return results
+
+
+class Node(object):
+
+ def __init__(self, query, results):
+ self.query = query
+ self.results = results
+
+
+class LinkedList(object):
+
+ def __init__(self):
+ self.head = None
+ self.tail = None
+
+ def move_to_front(self, node):
+ ...
+
+ def append_to_front(self, node):
+ ...
+
+ def remove_from_tail(self):
+ ...
+
+
+class Cache(object):
+
+ def __init__(self, MAX_SIZE):
+ self.MAX_SIZE = MAX_SIZE
+ self.size = 0
+ self.lookup = {}
+ self.linked_list = LinkedList()
+
+ def get(self, query):
+ """Get the stored query result from the cache.
+
+ Accessing a node updates its position to the front of the LRU list.
+ """
+ node = self.lookup[query]
+ if node is None:
+ return None
+ self.linked_list.move_to_front(node)
+ return node.results
+
+ def set(self, results, query):
+ """Set the result for the given query key in the cache.
+
+ When updating an entry, updates its position to the front of the LRU list.
+ If the entry is new and the cache is at capacity, removes the oldest entry
+ before the new entry is added.
+ """
+ node = self.map[query]
+ if node is not None:
+ # Key exists in cache, update the value
+ node.results = results
+ self.linked_list.move_to_front(node)
+ else:
+ # Key does not exist in cache
+ if self.size == self.MAX_SIZE:
+ # Remove the oldest entry from the linked list and lookup
+ self.lookup.pop(self.linked_list.tail.query, None)
+ self.linked_list.remove_from_tail()
+ else:
+ self.size += 1
+ # Add the new key and value
+ new_node = Node(query, results)
+ self.linked_list.append_to_front(new_node)
+ self.lookup[query] = new_node
diff --git a/website/solutions/system_design/sales_rank/README-zh-Hans.md b/website/solutions/system_design/sales_rank/README-zh-Hans.md
new file mode 100644
index 00000000000..960f9258f23
--- /dev/null
+++ b/website/solutions/system_design/sales_rank/README-zh-Hans.md
@@ -0,0 +1,338 @@
+# 为 Amazon 设计分类售卖排行
+
+**注意:这个文档中的链接会直接指向[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)中的有关部分,以避免重复的内容。你可以参考链接的相关内容,来了解其总的要点、方案的权衡取舍以及可选的替代方案。**
+
+## 第一步:简述用例与约束条件
+
+> 搜集需求与问题的范围。
+> 提出问题来明确用例与约束条件。
+> 讨论假设。
+
+我们将在没有面试官明确说明问题的情况下,自己定义一些用例以及限制条件。
+
+### 用例
+
+#### 我们将把问题限定在仅处理以下用例的范围中
+
+* **服务**根据分类计算过去一周中最受欢迎的商品
+* **用户**通过分类浏览过去一周中最受欢迎的商品
+* **服务**有着高可用性
+
+#### 不在用例范围内的有
+
+* 一般的电商网站
+ * 只为售卖排行榜设计组件
+
+### 限制条件与假设
+
+#### 提出假设
+
+* 网络流量不是均匀分布的
+* 一个商品可能存在于多个分类中
+* 商品不能够更改分类
+* 不会存在如 `foo/bar/baz` 之类的子分类
+* 每小时更新一次结果
+ * 受欢迎的商品越多,就需要更频繁地更新
+* 1000 万个商品
+* 1000 个分类
+* 每个月 10 亿次交易
+* 每个月 1000 亿次读取请求
+* 100:1 的读写比例
+
+#### 计算用量
+
+**如果你需要进行粗略的用量计算,请向你的面试官说明。**
+
+* 每笔交易的用量:
+ * `created_at` - 5 字节
+ * `product_id` - 8 字节
+ * `category_id` - 4 字节
+ * `seller_id` - 8 字节
+ * `buyer_id` - 8 字节
+ * `quantity` - 4 字节
+ * `total_price` - 5 字节
+ * 总计:大约 40 字节
+* 每个月的交易内容会产生 40 GB 的记录
+ * 每次交易 40 字节 * 每个月 10 亿次交易
+ * 3年内产生了 1.44 TB 的新交易内容记录
+ * 假定大多数的交易都是新交易而不是更改以前进行完的交易
+* 平均每秒 400 次交易次数
+* 平均每秒 40,000 次读取请求
+
+便利换算指南:
+
+* 每个月有 250 万秒
+* 每秒一个请求 = 每个月 250 万次请求
+* 每秒 40 个请求 = 每个月 1 亿次请求
+* 每秒 400 个请求 = 每个月 10 亿次请求
+
+## 第二步:概要设计
+
+> 列出所有重要组件以规划概要设计。
+
+
+
+## 第三步:设计核心组件
+
+> 深入每个核心组件的细节。
+
+### 用例:服务需要根据分类计算上周最受欢迎的商品
+
+我们可以在现成的**对象存储**系统(例如 Amazon S3 服务)中存储 **售卖 API** 服务产生的日志文本, 因此不需要我们自己搭建分布式文件系统了。
+
+**向你的面试官告知你准备写多少代码**。
+
+假设下面是一个用 tab 分割的简易的日志记录:
+
+```
+timestamp product_id category_id qty total_price seller_id buyer_id
+t1 product1 category1 2 20.00 1 1
+t2 product1 category2 2 20.00 2 2
+t2 product1 category2 1 10.00 2 3
+t3 product2 category1 3 7.00 3 4
+t4 product3 category2 7 2.00 4 5
+t5 product4 category1 1 5.00 5 6
+...
+```
+
+**售卖排行服务** 需要用到 **MapReduce**,并使用 **售卖 API** 服务进行日志记录,同时将结果写入 **SQL 数据库**中的总表 `sales_rank` 中。我们也可以讨论一下[究竟是用 SQL 还是用 NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)。
+
+我们需要通过以下步骤使用 **MapReduce**:
+
+* **第 1 步** - 将数据转换为 `(category, product_id), sum(quantity)` 的形式
+* **第 2 步** - 执行分布式排序
+
+```python
+class SalesRanker(MRJob):
+
+ def within_past_week(self, timestamp):
+ """如果时间戳属于过去的一周则返回 True,
+ 否则返回 False。"""
+ ...
+
+ def mapper(self, _ line):
+ """解析日志的每一行,提取并转换相关行,
+
+ 将键值对设定为如下形式:
+
+ (category1, product1), 2
+ (category2, product1), 2
+ (category2, product1), 1
+ (category1, product2), 3
+ (category2, product3), 7
+ (category1, product4), 1
+ """
+ timestamp, product_id, category_id, quantity, total_price, seller_id, \
+ buyer_id = line.split('\t')
+ if self.within_past_week(timestamp):
+ yield (category_id, product_id), quantity
+
+ def reducer(self, key, value):
+ """将每个 key 的值加起来。
+
+ (category1, product1), 2
+ (category2, product1), 3
+ (category1, product2), 3
+ (category2, product3), 7
+ (category1, product4), 1
+ """
+ yield key, sum(values)
+
+ def mapper_sort(self, key, value):
+ """构造 key 以确保正确的排序。
+
+ 将键值对转换成如下形式:
+
+ (category1, 2), product1
+ (category2, 3), product1
+ (category1, 3), product2
+ (category2, 7), product3
+ (category1, 1), product4
+
+ MapReduce 的随机排序步骤会将键
+ 值的排序打乱,变成下面这样:
+
+ (category1, 1), product4
+ (category1, 2), product1
+ (category1, 3), product2
+ (category2, 3), product1
+ (category2, 7), product3
+ """
+ category_id, product_id = key
+ quantity = value
+ yield (category_id, quantity), product_id
+
+ def reducer_identity(self, key, value):
+ yield key, value
+
+ def steps(self):
+ """ 此处为 map reduce 步骤"""
+ return [
+ self.mr(mapper=self.mapper,
+ reducer=self.reducer),
+ self.mr(mapper=self.mapper_sort,
+ reducer=self.reducer_identity),
+ ]
+```
+
+得到的结果将会是如下的排序列,我们将其插入 `sales_rank` 表中:
+
+```
+(category1, 1), product4
+(category1, 2), product1
+(category1, 3), product2
+(category2, 3), product1
+(category2, 7), product3
+```
+
+`sales_rank` 表的数据结构如下:
+
+```
+id int NOT NULL AUTO_INCREMENT
+category_id int NOT NULL
+total_sold int NOT NULL
+product_id int NOT NULL
+PRIMARY KEY(id)
+FOREIGN KEY(category_id) REFERENCES Categories(id)
+FOREIGN KEY(product_id) REFERENCES Products(id)
+```
+
+我们会以 `id`、`category_id` 与 `product_id` 创建一个 [索引](https://github.com/donnemartin/system-design-primer#use-good-indices)以加快查询速度(只需要使用读取日志的时间,不再需要每次都扫描整个数据表)并让数据常驻内存。从内存读取 1 MB 连续数据大约要花 250 微秒,而从 SSD 读取同样大小的数据要花费 4 倍的时间,从机械硬盘读取需要花费 80 倍以上的时间。1
+
+### 用例:用户需要根据分类浏览上周中最受欢迎的商品
+
+* **客户端**向运行[反向代理](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)的 **Web 服务器**发送一个请求
+* 这个 **Web 服务器**将请求转发给**查询 API** 服务
+* The **查询 API** 服务将从 **SQL 数据库**的 `sales_rank` 表中读取数据
+
+我们可以调用一个公共的 [REST API](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest):
+
+```
+$ curl https://amazon.com/api/v1/popular?category_id=1234
+```
+
+返回:
+
+```
+{
+ "id": "100",
+ "category_id": "1234",
+ "total_sold": "100000",
+ "product_id": "50",
+},
+{
+ "id": "53",
+ "category_id": "1234",
+ "total_sold": "90000",
+ "product_id": "200",
+},
+{
+ "id": "75",
+ "category_id": "1234",
+ "total_sold": "80000",
+ "product_id": "3",
+},
+```
+
+而对于服务器内部的通信,我们可以使用 [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)。
+
+## 第四步:架构扩展
+
+> 根据限制条件,找到并解决瓶颈。
+
+
+
+**重要提示:不要从最初设计直接跳到最终设计中!**
+
+现在你要 1) **基准测试、负载测试**。2) **分析、描述**性能瓶颈。3) 在解决瓶颈问题的同时,评估替代方案、权衡利弊。4) 重复以上步骤。请阅读[「设计一个系统,并将其扩大到为数以百万计的 AWS 用户服务」](../scaling_aws/README.md) 来了解如何逐步扩大初始设计。
+
+讨论初始设计可能遇到的瓶颈及相关解决方案是很重要的。例如加上一个配置多台 **Web 服务器**的**负载均衡器**是否能够解决问题?**CDN**呢?**主从复制**呢?它们各自的替代方案和需要**权衡**的利弊又有什么呢?
+
+我们将会介绍一些组件来完成设计,并解决架构扩张问题。内置的负载均衡器将不做讨论以节省篇幅。
+
+**为了避免重复讨论**,请参考[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)相关部分来了解其要点、方案的权衡取舍以及可选的替代方案。
+
+* [DNS](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#域名系统)
+* [负载均衡器](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#负载均衡器)
+* [水平拓展](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#水平扩展)
+* [反向代理(web 服务器)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)
+* [API 服务(应用层)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用层)
+* [缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存)
+* [关系型数据库管理系统 (RDBMS)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#关系型数据库管理系统rdbms)
+* [SQL 故障主从切换](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#故障切换)
+* [主从复制](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#主从复制)
+* [一致性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#一致性模式)
+* [可用性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#可用性模式)
+
+**分析数据库** 可以用现成的数据仓储系统,例如使用 Amazon Redshift 或者 Google BigQuery 的解决方案。
+
+当使用数据仓储技术或者**对象存储**系统时,我们只想在数据库中存储有限时间段的数据。Amazon S3 的**对象存储**系统可以很方便地设置每个月限制只允许新增 40 GB 的存储内容。
+
+平均每秒 40,000 次的读取请求(峰值将会更高), 可以通过扩展 **内存缓存** 来处理热点内容的读取流量,这对于处理不均匀分布的流量和流量峰值也很有用。由于读取量非常大,**SQL Read 副本** 可能会遇到处理缓存未命中的问题,我们可能需要使用额外的 SQL 扩展模式。
+
+平均每秒 400 次写操作(峰值将会更高)可能对于单个 **SQL 写主-从** 模式来说比较很困难,因此同时还需要更多的扩展技术
+
+SQL 缩放模式包括:
+
+* [联合](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#联合)
+* [分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#分片)
+* [非规范化](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#非规范化)
+* [SQL 调优](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-调优)
+
+我们也可以考虑将一些数据移至 **NoSQL 数据库**。
+
+## 其它要点
+
+> 是否深入这些额外的主题,取决于你的问题范围和剩下的时间。
+
+#### NoSQL
+
+* [键-值存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#键-值存储)
+* [文档类型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#文档类型存储)
+* [列型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#列型存储)
+* [图数据库](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#图数据库)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)
+
+### 缓存
+
+* 在哪缓存
+ * [客户端缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#客户端缓存)
+ * [CDN 缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#cdn-缓存)
+ * [Web 服务器缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#web-服务器缓存)
+ * [数据库缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库缓存)
+ * [应用缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用缓存)
+* 什么需要缓存
+ * [数据库查询级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库查询级别的缓存)
+ * [对象级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#对象级别的缓存)
+* 何时更新缓存
+ * [缓存模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存模式)
+ * [直写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#直写模式)
+ * [回写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#回写模式)
+ * [刷新](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#刷新)
+
+### 异步与微服务
+
+* [消息队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#消息队列)
+* [任务队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#任务队列)
+* [背压](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#背压)
+* [微服务](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#微服务)
+
+### 通信
+
+* 可权衡选择的方案:
+ * 与客户端的外部通信 - [使用 REST 作为 HTTP API](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest)
+ * 服务器内部通信 - [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)
+* [服务发现](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#服务发现)
+
+### 安全性
+
+请参阅[「安全」](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#安全)一章。
+
+### 延迟数值
+
+请参阅[「每个程序员都应该知道的延迟数」](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#每个程序员都应该知道的延迟数)。
+
+### 持续探讨
+
+* 持续进行基准测试并监控你的系统,以解决他们提出的瓶颈问题。
+* 架构拓展是一个迭代的过程。
diff --git a/website/solutions/system_design/sales_rank/README.md b/website/solutions/system_design/sales_rank/README.md
new file mode 100644
index 00000000000..71ad1c7d202
--- /dev/null
+++ b/website/solutions/system_design/sales_rank/README.md
@@ -0,0 +1,338 @@
+# Design Amazon's sales rank by category feature
+
+*Note: This document links directly to relevant areas found in the [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) to avoid duplication. Refer to the linked content for general talking points, tradeoffs, and alternatives.*
+
+## Step 1: Outline use cases and constraints
+
+> Gather requirements and scope the problem.
+> Ask questions to clarify use cases and constraints.
+> Discuss assumptions.
+
+Without an interviewer to address clarifying questions, we'll define some use cases and constraints.
+
+### Use cases
+
+#### We'll scope the problem to handle only the following use case
+
+* **Service** calculates the past week's most popular products by category
+* **User** views the past week's most popular products by category
+* **Service** has high availability
+
+#### Out of scope
+
+* The general e-commerce site
+ * Design components only for calculating sales rank
+
+### Constraints and assumptions
+
+#### State assumptions
+
+* Traffic is not evenly distributed
+* Items can be in multiple categories
+* Items cannot change categories
+* There are no subcategories ie `foo/bar/baz`
+* Results must be updated hourly
+ * More popular products might need to be updated more frequently
+* 10 million products
+* 1000 categories
+* 1 billion transactions per month
+* 100 billion read requests per month
+* 100:1 read to write ratio
+
+#### Calculate usage
+
+**Clarify with your interviewer if you should run back-of-the-envelope usage calculations.**
+
+* Size per transaction:
+ * `created_at` - 5 bytes
+ * `product_id` - 8 bytes
+ * `category_id` - 4 bytes
+ * `seller_id` - 8 bytes
+ * `buyer_id` - 8 bytes
+ * `quantity` - 4 bytes
+ * `total_price` - 5 bytes
+ * Total: ~40 bytes
+* 40 GB of new transaction content per month
+ * 40 bytes per transaction * 1 billion transactions per month
+ * 1.44 TB of new transaction content in 3 years
+ * Assume most are new transactions instead of updates to existing ones
+* 400 transactions per second on average
+* 40,000 read requests per second on average
+
+Handy conversion guide:
+
+* 2.5 million seconds per month
+* 1 request per second = 2.5 million requests per month
+* 40 requests per second = 100 million requests per month
+* 400 requests per second = 1 billion requests per month
+
+## Step 2: Create a high level design
+
+> Outline a high level design with all important components.
+
+
+
+## Step 3: Design core components
+
+> Dive into details for each core component.
+
+### Use case: Service calculates the past week's most popular products by category
+
+We could store the raw **Sales API** server log files on a managed **Object Store** such as Amazon S3, rather than managing our own distributed file system.
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+We'll assume this is a sample log entry, tab delimited:
+
+```
+timestamp product_id category_id qty total_price seller_id buyer_id
+t1 product1 category1 2 20.00 1 1
+t2 product1 category2 2 20.00 2 2
+t2 product1 category2 1 10.00 2 3
+t3 product2 category1 3 7.00 3 4
+t4 product3 category2 7 2.00 4 5
+t5 product4 category1 1 5.00 5 6
+...
+```
+
+The **Sales Rank Service** could use **MapReduce**, using the **Sales API** server log files as input and writing the results to an aggregate table `sales_rank` in a **SQL Database**. We should discuss the [use cases and tradeoffs between choosing SQL or NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql).
+
+We'll use a multi-step **MapReduce**:
+
+* **Step 1** - Transform the data to `(category, product_id), sum(quantity)`
+* **Step 2** - Perform a distributed sort
+
+```python
+class SalesRanker(MRJob):
+
+ def within_past_week(self, timestamp):
+ """Return True if timestamp is within past week, False otherwise."""
+ ...
+
+ def mapper(self, _ line):
+ """Parse each log line, extract and transform relevant lines.
+
+ Emit key value pairs of the form:
+
+ (category1, product1), 2
+ (category2, product1), 2
+ (category2, product1), 1
+ (category1, product2), 3
+ (category2, product3), 7
+ (category1, product4), 1
+ """
+ timestamp, product_id, category_id, quantity, total_price, seller_id, \
+ buyer_id = line.split('\t')
+ if self.within_past_week(timestamp):
+ yield (category_id, product_id), quantity
+
+ def reducer(self, key, value):
+ """Sum values for each key.
+
+ (category1, product1), 2
+ (category2, product1), 3
+ (category1, product2), 3
+ (category2, product3), 7
+ (category1, product4), 1
+ """
+ yield key, sum(values)
+
+ def mapper_sort(self, key, value):
+ """Construct key to ensure proper sorting.
+
+ Transform key and value to the form:
+
+ (category1, 2), product1
+ (category2, 3), product1
+ (category1, 3), product2
+ (category2, 7), product3
+ (category1, 1), product4
+
+ The shuffle/sort step of MapReduce will then do a
+ distributed sort on the keys, resulting in:
+
+ (category1, 1), product4
+ (category1, 2), product1
+ (category1, 3), product2
+ (category2, 3), product1
+ (category2, 7), product3
+ """
+ category_id, product_id = key
+ quantity = value
+ yield (category_id, quantity), product_id
+
+ def reducer_identity(self, key, value):
+ yield key, value
+
+ def steps(self):
+ """Run the map and reduce steps."""
+ return [
+ self.mr(mapper=self.mapper,
+ reducer=self.reducer),
+ self.mr(mapper=self.mapper_sort,
+ reducer=self.reducer_identity),
+ ]
+```
+
+The result would be the following sorted list, which we could insert into the `sales_rank` table:
+
+```
+(category1, 1), product4
+(category1, 2), product1
+(category1, 3), product2
+(category2, 3), product1
+(category2, 7), product3
+```
+
+The `sales_rank` table could have the following structure:
+
+```
+id int NOT NULL AUTO_INCREMENT
+category_id int NOT NULL
+total_sold int NOT NULL
+product_id int NOT NULL
+PRIMARY KEY(id)
+FOREIGN KEY(category_id) REFERENCES Categories(id)
+FOREIGN KEY(product_id) REFERENCES Products(id)
+```
+
+We'll create an [index](https://github.com/donnemartin/system-design-primer#use-good-indices) on `id `, `category_id`, and `product_id` to speed up lookups (log-time instead of scanning the entire table) and to keep the data in memory. Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.1
+
+### Use case: User views the past week's most popular products by category
+
+* The **Client** sends a request to the **Web Server**, running as a [reverse proxy](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* The **Web Server** forwards the request to the **Read API** server
+* The **Read API** server reads from the **SQL Database** `sales_rank` table
+
+We'll use a public [**REST API**](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest):
+
+```
+$ curl https://amazon.com/api/v1/popular?category_id=1234
+```
+
+Response:
+
+```
+{
+ "id": "100",
+ "category_id": "1234",
+ "total_sold": "100000",
+ "product_id": "50",
+},
+{
+ "id": "53",
+ "category_id": "1234",
+ "total_sold": "90000",
+ "product_id": "200",
+},
+{
+ "id": "75",
+ "category_id": "1234",
+ "total_sold": "80000",
+ "product_id": "3",
+},
+```
+
+For internal communications, we could use [Remote Procedure Calls](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc).
+
+## Step 4: Scale the design
+
+> Identify and address bottlenecks, given the constraints.
+
+
+
+**Important: Do not simply jump right into the final design from the initial design!**
+
+State you would 1) **Benchmark/Load Test**, 2) **Profile** for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat. See [Design a system that scales to millions of users on AWS](../scaling_aws/README.md) as a sample on how to iteratively scale the initial design.
+
+It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them. For example, what issues are addressed by adding a **Load Balancer** with multiple **Web Servers**? **CDN**? **Master-Slave Replicas**? What are the alternatives and **Trade-Offs** for each?
+
+We'll introduce some components to complete the design and to address scalability issues. Internal load balancers are not shown to reduce clutter.
+
+*To avoid repeating discussions*, refer to the following [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) for main talking points, tradeoffs, and alternatives:
+
+* [DNS](https://github.com/donnemartin/system-design-primer#domain-name-system)
+* [CDN](https://github.com/donnemartin/system-design-primer#content-delivery-network)
+* [Load balancer](https://github.com/donnemartin/system-design-primer#load-balancer)
+* [Horizontal scaling](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+* [Web server (reverse proxy)](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* [API server (application layer)](https://github.com/donnemartin/system-design-primer#application-layer)
+* [Cache](https://github.com/donnemartin/system-design-primer#cache)
+* [Relational database management system (RDBMS)](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms)
+* [SQL write master-slave failover](https://github.com/donnemartin/system-design-primer#fail-over)
+* [Master-slave replication](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [Consistency patterns](https://github.com/donnemartin/system-design-primer#consistency-patterns)
+* [Availability patterns](https://github.com/donnemartin/system-design-primer#availability-patterns)
+
+The **Analytics Database** could use a data warehousing solution such as Amazon Redshift or Google BigQuery.
+
+We might only want to store a limited time period of data in the database, while storing the rest in a data warehouse or in an **Object Store**. An **Object Store** such as Amazon S3 can comfortably handle the constraint of 40 GB of new content per month.
+
+To address the 40,000 *average* read requests per second (higher at peak), traffic for popular content (and their sales rank) should be handled by the **Memory Cache** instead of the database. The **Memory Cache** is also useful for handling the unevenly distributed traffic and traffic spikes. With the large volume of reads, the **SQL Read Replicas** might not be able to handle the cache misses. We'll probably need to employ additional SQL scaling patterns.
+
+400 *average* writes per second (higher at peak) might be tough for a single **SQL Write Master-Slave**, also pointing to a need for additional scaling techniques.
+
+SQL scaling patterns include:
+
+* [Federation](https://github.com/donnemartin/system-design-primer#federation)
+* [Sharding](https://github.com/donnemartin/system-design-primer#sharding)
+* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+We should also consider moving some data to a **NoSQL Database**.
+
+## Additional talking points
+
+> Additional topics to dive into, depending on the problem scope and time remaining.
+
+#### NoSQL
+
+* [Key-value store](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [Document store](https://github.com/donnemartin/system-design-primer#document-store)
+* [Wide column store](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [Graph database](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### Caching
+
+* Where to cache
+ * [Client caching](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN caching](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web server caching](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [Database caching](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [Application caching](https://github.com/donnemartin/system-design-primer#application-caching)
+* What to cache
+ * [Caching at the database query level](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [Caching at the object level](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* When to update the cache
+ * [Cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [Write-through](https://github.com/donnemartin/system-design-primer#write-through)
+ * [Write-behind (write-back)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [Refresh ahead](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### Asynchronism and microservices
+
+* [Message queues](https://github.com/donnemartin/system-design-primer#message-queues)
+* [Task queues](https://github.com/donnemartin/system-design-primer#task-queues)
+* [Back pressure](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [Microservices](https://github.com/donnemartin/system-design-primer#microservices)
+
+### Communications
+
+* Discuss tradeoffs:
+ * External communication with clients - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * Internal communications - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [Service discovery](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### Security
+
+Refer to the [security section](https://github.com/donnemartin/system-design-primer#security).
+
+### Latency numbers
+
+See [Latency numbers every programmer should know](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know).
+
+### Ongoing
+
+* Continue benchmarking and monitoring your system to address bottlenecks as they come up
+* Scaling is an iterative process
diff --git a/website/solutions/system_design/sales_rank/__init__.py b/website/solutions/system_design/sales_rank/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/system_design/sales_rank/sales_rank.graffle b/website/solutions/system_design/sales_rank/sales_rank.graffle
new file mode 100644
index 00000000000..0a491db54f8
Binary files /dev/null and b/website/solutions/system_design/sales_rank/sales_rank.graffle differ
diff --git a/website/solutions/system_design/sales_rank/sales_rank.png b/website/solutions/system_design/sales_rank/sales_rank.png
new file mode 100644
index 00000000000..dd0af6193aa
Binary files /dev/null and b/website/solutions/system_design/sales_rank/sales_rank.png differ
diff --git a/website/solutions/system_design/sales_rank/sales_rank_basic.graffle b/website/solutions/system_design/sales_rank/sales_rank_basic.graffle
new file mode 100644
index 00000000000..19eb85ff4e3
Binary files /dev/null and b/website/solutions/system_design/sales_rank/sales_rank_basic.graffle differ
diff --git a/website/solutions/system_design/sales_rank/sales_rank_basic.png b/website/solutions/system_design/sales_rank/sales_rank_basic.png
new file mode 100644
index 00000000000..225bb9b19de
Binary files /dev/null and b/website/solutions/system_design/sales_rank/sales_rank_basic.png differ
diff --git a/website/solutions/system_design/sales_rank/sales_rank_mapreduce.py b/website/solutions/system_design/sales_rank/sales_rank_mapreduce.py
new file mode 100644
index 00000000000..6eeeb525035
--- /dev/null
+++ b/website/solutions/system_design/sales_rank/sales_rank_mapreduce.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+
+from mrjob.job import MRJob
+
+
+class SalesRanker(MRJob):
+
+ def within_past_week(self, timestamp):
+ """Return True if timestamp is within past week, False otherwise."""
+ ...
+
+ def mapper(self, _, line):
+ """Parse each log line, extract and transform relevant lines.
+
+ Emit key value pairs of the form:
+
+ (foo, p1), 2
+ (bar, p1), 2
+ (bar, p1), 1
+ (foo, p2), 3
+ (bar, p3), 10
+ (foo, p4), 1
+ """
+ timestamp, product_id, category, quantity = line.split('\t')
+ if self.within_past_week(timestamp):
+ yield (category, product_id), quantity
+
+ def reducer(self, key, values):
+ """Sum values for each key.
+
+ (foo, p1), 2
+ (bar, p1), 3
+ (foo, p2), 3
+ (bar, p3), 10
+ (foo, p4), 1
+ """
+ yield key, sum(values)
+
+ def mapper_sort(self, key, value):
+ """Construct key to ensure proper sorting.
+
+ Transform key and value to the form:
+
+ (foo, 2), p1
+ (bar, 3), p1
+ (foo, 3), p2
+ (bar, 10), p3
+ (foo, 1), p4
+
+ The shuffle/sort step of MapReduce will then do a
+ distributed sort on the keys, resulting in:
+
+ (category1, 1), product4
+ (category1, 2), product1
+ (category1, 3), product2
+ (category2, 3), product1
+ (category2, 7), product3
+ """
+ category, product_id = key
+ quantity = value
+ yield (category, quantity), product_id
+
+ def reducer_identity(self, key, value):
+ yield key, value
+
+ def steps(self):
+ """Run the map and reduce steps."""
+ return [
+ self.mr(mapper=self.mapper,
+ reducer=self.reducer),
+ self.mr(mapper=self.mapper_sort,
+ reducer=self.reducer_identity),
+ ]
+
+
+if __name__ == '__main__':
+ SalesRanker.run()
diff --git a/website/solutions/system_design/scaling_aws/README-zh-Hans.md b/website/solutions/system_design/scaling_aws/README-zh-Hans.md
new file mode 100644
index 00000000000..c071c70eb08
--- /dev/null
+++ b/website/solutions/system_design/scaling_aws/README-zh-Hans.md
@@ -0,0 +1,403 @@
+# 在 AWS 上设计支持百万级到千万级用户的系统
+
+**注释:为了避免重复,这篇文章的链接直接关联到 [系统设计主题](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) 的相关章节。为一讨论要点、折中方案和可选方案做参考。**
+
+## 第 1 步:用例和约束概要
+
+> 收集需求并调查问题。
+> 通过提问清晰用例和约束。
+> 讨论假设。
+
+如果没有面试官提出明确的问题,我们将自己定义一些用例和约束条件。
+
+### 用例
+
+解决这个问题是一个循序渐进的过程:1) **基准/负载 测试**, 2) 瓶颈 **概述**, 3) 当评估可选和折中方案时定位瓶颈,4) 重复,这是向可扩展的设计发展基础设计的好模式。
+
+除非你有 AWS 的背景或者正在申请需要 AWS 知识的相关职位,否则不要求了解 AWS 的相关细节。并且,这个练习中讨论的许多原则可以更广泛地应用于AWS生态系统之外。
+
+#### 我们就处理以下用例讨论这一问题
+
+* **用户** 进行读或写请求
+ * **服务** 进行处理,存储用户数据,然后返回结果
+* **服务** 需要从支持小规模用户开始到百万用户
+ * 在我们演化架构来处理大量的用户和请求时,讨论一般的扩展模式
+* **服务** 高可用
+
+### 约束和假设
+
+#### 状态假设
+
+* 流量不均匀分布
+* 需要关系数据
+* 从一个用户扩展到千万用户
+ * 表示用户量的增长
+ * 用户量+
+ * 用户量++
+ * 用户量+++
+ * ...
+ * 1000 万用户
+ * 每月 10 亿次写入
+ * 每月 1000 亿次读出
+ * 100:1 读写比率
+ * 每次写入 1 KB 内容
+
+#### 计算使用
+
+**向你的面试官厘清你是否应该做粗略的使用计算**
+
+* 1 TB 新内容 / 月
+ * 1 KB 每次写入 * 10 亿 写入 / 月
+ * 36 TB 新内容 / 3 年
+ * 假设大多数写入都是新内容而不是更新已有内容
+* 平均每秒 400 次写入
+* 平均每秒 40,000 次读取
+
+便捷的转换指南:
+
+* 250 万秒 / 月
+* 1 次请求 / 秒 = 250 万次请求 / 月
+* 40 次请求 / 秒 = 1 亿次请求 / 月
+* 400 次请求 / 秒 = 10 亿请求 / 月
+
+## 第 2 步:创建高级设计方案
+
+> 用所有重要组件概述高水平设计
+
+
+
+## 第 3 步:设计核心组件
+
+> 深入每个核心组件的细节。
+
+### 用例:用户进行读写请求
+
+#### 目标
+
+* 只有 1-2 个用户时,你只需要基础配置
+ * 为简单起见,只需要一台服务器
+ * 必要时进行纵向扩展
+ * 监控以确定瓶颈
+
+#### 以单台服务器开始
+
+* **Web 服务器** 在 EC2 上
+ * 存储用户数据
+ * [**MySQL 数据库**](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms)
+
+运用 **纵向扩展**:
+
+* 选择一台更大容量的服务器
+* 密切关注指标,确定如何扩大规模
+ * 使用基本监控来确定瓶颈:CPU、内存、IO、网络等
+ * CloudWatch, top, nagios, statsd, graphite等
+* 纵向扩展的代价将变得更昂贵
+* 无冗余/容错
+
+**折中方案, 可选方案, 和其他细节:**
+
+* **纵向扩展** 的可选方案是 [**横向扩展**](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+
+#### 自 SQL 开始,但认真考虑 NoSQL
+
+约束条件假设需要关系型数据。我们可以开始时在单台服务器上使用 **MySQL 数据库**。
+
+**折中方案, 可选方案, 和其他细节:**
+
+* 查阅 [关系型数据库管理系统 (RDBMS)](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms) 章节
+* 讨论使用 [SQL 或 NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql) 的原因
+
+#### 分配公共静态 IP
+
+* 弹性 IP 提供了一个公共端点,不会在重启时改变 IP。
+* 故障转移时只需要把域名指向新 IP。
+
+#### 使用 DNS 服务
+
+添加 **DNS** 服务,比如 Route 53([Amazon Route 53](https://aws.amazon.com/cn/route53/) - 译者注),将域映射到实例的公共 IP 中。
+
+**折中方案, 可选方案, 和其他细节:**
+
+* 查阅 [域名系统](https://github.com/donnemartin/system-design-primer#domain-name-system) 章节
+
+#### 安全的 Web 服务器
+
+* 只开放必要的端口
+ * 允许 Web 服务器响应来自以下端口的请求
+ * HTTP 80
+ * HTTPS 443
+ * SSH IP 白名单 22
+ * 防止 Web 服务器启动外链
+
+**折中方案, 可选方案, 和其他细节:**
+
+* 查阅 [安全](https://github.com/donnemartin/system-design-primer#security) 章节
+
+## 第 4 步:扩展设计
+
+> 在给定约束条件下,定义和确认瓶颈。
+
+### 用户+
+
+
+
+#### 假设
+
+我们的用户数量开始上升,并且单台服务器的负载上升。**基准/负载测试** 和 **分析** 指出 **MySQL 数据库** 占用越来越多的内存和 CPU 资源,同时用户数据将填满硬盘空间。
+
+目前,我们尚能在纵向扩展时解决这些问题。不幸的是,解决这些问题的代价变得相当昂贵,并且原来的系统并不能允许在 **MySQL 数据库** 和 **Web 服务器** 的基础上进行独立扩展。
+
+#### 目标
+
+* 减轻单台服务器负载并且允许独立扩展
+ * 在 **对象存储** 中单独存储静态内容
+ * 将 **MySQL 数据库** 迁移到单独的服务器上
+* 缺点
+ * 这些变化会增加复杂性,并要求对 **Web服务器** 进行更改,以指向 **对象存储** 和 **MySQL 数据库**
+ * 必须采取额外的安全措施来确保新组件的安全
+ * AWS 的成本也会增加,但应该与自身管理类似系统的成本做比较
+
+#### 独立保存静态内容
+
+* 考虑使用像 S3 这样可管理的 **对象存储** 服务来存储静态内容
+ * 高扩展性和可靠性
+ * 服务器端加密
+* 迁移静态内容到 S3
+ * 用户文件
+ * JS
+ * CSS
+ * 图片
+ * 视频
+
+#### 迁移 MySQL 数据库到独立机器上
+
+* 考虑使用类似 RDS 的服务来管理 **MySQL 数据库**
+ * 简单的管理,扩展
+ * 多个可用区域
+ * 空闲时加密
+
+#### 系统安全
+
+* 在传输和空闲时对数据进行加密
+* 使用虚拟私有云
+ * 为单个 **Web 服务器** 创建一个公共子网,这样就可以发送和接收来自 internet 的流量
+ * 为其他内容创建一个私有子网,禁止外部访问
+ * 在每个组件上只为白名单 IP 打开端口
+* 这些相同的模式应当在新的组件的实现中实践
+
+**折中方案, 可选方案, 和其他细节:**
+
+* 查阅 [安全](https://github.com/donnemartin/system-design-primer#security) 章节
+
+### 用户+++
+
+
+
+#### 假设
+
+我们的 **基准/负载测试** 和 **性能测试** 显示,在高峰时段,我们的单一 **Web服务器** 存在瓶颈,导致响应缓慢,在某些情况下还会宕机。随着服务的成熟,我们也希望朝着更高的可用性和冗余发展。
+
+#### 目标
+
+* 下面的目标试图用 **Web服务器** 解决扩展问题
+ * 基于 **基准/负载测试** 和 **分析**,你可能只需要实现其中的一两个技术
+* 使用 [**横向扩展**](https://github.com/donnemartin/system-design-primer#horizontal-scaling) 来处理增加的负载和单点故障
+ * 添加 [**负载均衡器**](https://github.com/donnemartin/system-design-primer#load-balancer) 例如 Amazon 的 ELB 或 HAProxy
+ * ELB 是高可用的
+ * 如果你正在配置自己的 **负载均衡器**, 在多个可用区域中设置多台服务器用于 [双活](https://github.com/donnemartin/system-design-primer#active-active) 或 [主被](https://github.com/donnemartin/system-design-primer#active-passive) 将提高可用性
+ * 终止在 **负载平衡器** 上的SSL,以减少后端服务器上的计算负载,并简化证书管理
+ * 在多个可用区域中使用多台 **Web服务器**
+ * 在多个可用区域的 [**主-从 故障转移**](https://github.com/donnemartin/system-design-primer#master-slave-replication) 模式中使用多个 **MySQL** 实例来改进冗余
+* 分离 **Web 服务器** 和 [**应用服务器**](https://github.com/donnemartin/system-design-primer#application-layer)
+ * 独立扩展和配置每一层
+ * **Web 服务器** 可以作为 [**反向代理**](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+ * 例如, 你可以添加 **应用服务器** 处理 **读 API** 而另外一些处理 **写 API**
+* 将静态(和一些动态)内容转移到 [**内容分发网络 (CDN)**](https://github.com/donnemartin/system-design-primer#content-delivery-network) 例如 CloudFront 以减少负载和延迟
+
+**折中方案, 可选方案, 和其他细节:**
+
+* 查阅以上链接获得更多细节
+
+### 用户+++
+
+
+
+**注意:** **内部负载均衡** 不显示以减少混乱
+
+#### 假设
+
+我们的 **性能/负载测试** 和 **性能测试** 显示我们读操作频繁(100:1 的读写比率),并且数据库在高读请求时表现很糟糕。
+
+#### 目标
+
+* 下面的目标试图解决 **MySQL数据库** 的伸缩性问题
+ * * 基于 **基准/负载测试** 和 **分析**,你可能只需要实现其中的一两个技术
+* 将下列数据移动到一个 [**内存缓存**](https://github.com/donnemartin/system-design-primer#cache),例如弹性缓存,以减少负载和延迟:
+ * **MySQL** 中频繁访问的内容
+ * 首先, 尝试配置 **MySQL 数据库** 缓存以查看是否足以在实现 **内存缓存** 之前缓解瓶颈
+ * 来自 **Web 服务器** 的会话数据
+ * **Web 服务器** 变成无状态的, 允许 **自动伸缩**
+ * 从内存中读取 1 MB 内存需要大约 250 微秒,而从SSD中读取时间要长 4 倍,从磁盘读取的时间要长 80 倍。1
+* 添加 [**MySQL 读取副本**](https://github.com/donnemartin/system-design-primer#master-slave-replication) 来减少写主线程的负载
+* 添加更多 **Web 服务器** and **应用服务器** 来提高响应
+
+**折中方案, 可选方案, 和其他细节:**
+
+* 查阅以上链接获得更多细节
+
+#### 添加 MySQL 读取副本
+
+* 除了添加和扩展 **内存缓存**,**MySQL 读副本服务器** 也能够帮助缓解在 **MySQL 写主服务器** 的负载。
+* 添加逻辑到 **Web 服务器** 来区分读和写操作
+* 在 **MySQL 读副本服务器** 之上添加 **负载均衡器** (不是为了减少混乱)
+* 大多数服务都是读取负载大于写入负载
+
+**折中方案, 可选方案, 和其他细节:**
+
+* 查阅 [关系型数据库管理系统 (RDBMS)](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms) 章节
+
+### 用户++++
+
+
+
+#### 假设
+
+**基准/负载测试** 和 **分析** 显示,在美国,正常工作时间存在流量峰值,当用户离开办公室时,流量骤降。我们认为,可以通过真实负载自动转换服务器数量来降低成本。我们是一家小商店,所以我们希望 DevOps 尽量自动化地进行 **自动伸缩** 和通用操作。
+
+#### 目标
+
+* 根据需要添加 **自动扩展**
+ * 跟踪流量高峰
+ * 通过关闭未使用的实例来降低成本
+* 自动化 DevOps
+ * Chef, Puppet, Ansible 工具等
+* 继续监控指标以解决瓶颈
+ * **主机水平** - 检查一个 EC2 实例
+ * **总水平** - 检查负载均衡器统计数据
+ * **日志分析** - CloudWatch, CloudTrail, Loggly, Splunk, Sumo
+ * **外部站点的性能** - Pingdom or New Relic
+ * **处理通知和事件** - PagerDuty
+ * **错误报告** - Sentry
+
+#### 添加自动扩展
+
+* 考虑使用一个托管服务,比如AWS **自动扩展**
+ * 为每个 **Web 服务器** 创建一个组,并为每个 **应用服务器** 类型创建一个组,将每个组放置在多个可用区域中
+ * 设置最小和最大实例数
+ * 通过 CloudWatch 来扩展或收缩
+ * 可预测负载的简单时间度量
+ * 一段时间内的指标:
+ * CPU 负载
+ * 延迟
+ * 网络流量
+ * 自定义指标
+ * 缺点
+ * 自动扩展会引入复杂性
+ * 可能需要一段时间才能适当扩大规模,以满足增加的需求,或者在需求下降时缩减规模
+
+### 用户+++++
+
+
+
+**注释:** **自动伸缩** 组不显示以减少混乱
+
+#### 假设
+
+当服务继续向着限制条件概述的方向发展,我们反复地运行 **基准/负载测试** 和 **分析** 来进一步发现和定位新的瓶颈。
+
+#### 目标
+
+由于问题的约束,我们将继续提出扩展性的问题:
+
+* 如果我们的 **MySQL 数据库** 开始变得过于庞大, 我们可能只考虑把数据在数据库中存储一段有限的时间, 同时在例如 Redshift 这样的数据仓库中存储其余的数据
+ * 像 Redshift 这样的数据仓库能够轻松处理每月 1TB 的新内容
+* 平均每秒 40,000 次的读取请求, 可以通过扩展 **内存缓存** 来处理热点内容的读取流量,这对于处理不均匀分布的流量和流量峰值也很有用
+ * **SQL读取副本** 可能会遇到处理缓存未命中的问题, 我们可能需要使用额外的 SQL 扩展模式
+* 对于单个 **SQL 写主-从** 模式来说,平均每秒 400 次写操作(明显更高)可能会很困难,同时还需要更多的扩展技术
+
+SQL 扩展模型包括:
+
+* [集合](https://github.com/donnemartin/system-design-primer#federation)
+* [分片](https://github.com/donnemartin/system-design-primer#sharding)
+* [反范式](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL 调优](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+为了进一步处理高读和写请求,我们还应该考虑将适当的数据移动到一个 [**NoSQL数据库**](https://github.com/donnemartin/system-design-primer#nosql) ,例如 DynamoDB。
+
+我们可以进一步分离我们的 [**应用服务器**](https://github.com/donnemartin/system-design-primer#application-layer) 以允许独立扩展。不需要实时完成的批处理任务和计算可以通过 Queues 和 Workers 异步完成:
+
+* 以照片服务为例,照片上传和缩略图的创建可以分开进行
+ * **客户端** 上传图片
+ * **应用服务器** 推送一个任务到 **队列** 例如 SQS
+ * EC2 上的 **Worker 服务** 或者 Lambda 从 **队列** 拉取 work,然后:
+ * 创建缩略图
+ * 更新 **数据库**
+ * 在 **对象存储** 中存储缩略图
+
+**折中方案, 可选方案, 和其他细节:**
+
+* 查阅以上链接获得更多细节
+
+## 额外的话题
+
+> 根据问题的范围和剩余时间,还需要深入讨论其他问题。
+
+### SQL 扩展模式
+
+* [读取副本](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [集合](https://github.com/donnemartin/system-design-primer#federation)
+* [分区](https://github.com/donnemartin/system-design-primer#sharding)
+* [反规范化](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL 调优](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+#### NoSQL
+
+* [键值存储](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [文档存储](https://github.com/donnemartin/system-design-primer#document-store)
+* [宽表存储](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [图数据库](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### 缓存
+
+* 缓存到哪里
+ * [客户端缓存](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN 缓存](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web 服务缓存](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [数据库缓存](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [应用缓存](https://github.com/donnemartin/system-design-primer#application-caching)
+* 缓存什么
+ * [数据库请求层缓存](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [对象层缓存](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* 何时更新缓存
+ * [预留缓存](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [完全写入](https://github.com/donnemartin/system-design-primer#write-through)
+ * [延迟写 (写回)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [事先更新](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### 异步性和微服务
+
+* [消息队列](https://github.com/donnemartin/system-design-primer#message-queues)
+* [任务队列](https://github.com/donnemartin/system-design-primer#task-queues)
+* [回退压力](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [微服务](https://github.com/donnemartin/system-design-primer#microservices)
+
+### 沟通
+
+* 关于折中方案的讨论:
+ * 客户端的外部通讯 - [遵循 REST 的 HTTP APIs](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * 内部通讯 - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [服务探索](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### 安全性
+
+参考 [安全章节](https://github.com/donnemartin/system-design-primer#security)
+
+### 延迟数字指标
+
+查阅 [每个程序员必懂的延迟数字](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know)
+
+### 正在进行
+
+* 继续基准测试并监控你的系统以解决出现的瓶颈问题
+* 扩展是一个迭代的过程
diff --git a/website/solutions/system_design/scaling_aws/README.md b/website/solutions/system_design/scaling_aws/README.md
new file mode 100644
index 00000000000..99af0cfff8d
--- /dev/null
+++ b/website/solutions/system_design/scaling_aws/README.md
@@ -0,0 +1,403 @@
+# Design a system that scales to millions of users on AWS
+
+*Note: This document links directly to relevant areas found in the [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) to avoid duplication. Refer to the linked content for general talking points, tradeoffs, and alternatives.*
+
+## Step 1: Outline use cases and constraints
+
+> Gather requirements and scope the problem.
+> Ask questions to clarify use cases and constraints.
+> Discuss assumptions.
+
+Without an interviewer to address clarifying questions, we'll define some use cases and constraints.
+
+### Use cases
+
+Solving this problem takes an iterative approach of: 1) **Benchmark/Load Test**, 2) **Profile** for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat, which is good pattern for evolving basic designs to scalable designs.
+
+Unless you have a background in AWS or are applying for a position that requires AWS knowledge, AWS-specific details are not a requirement. However, **much of the principles discussed in this exercise can apply more generally outside of the AWS ecosystem.**
+
+#### We'll scope the problem to handle only the following use cases
+
+* **User** makes a read or write request
+ * **Service** does processing, stores user data, then returns the results
+* **Service** needs to evolve from serving a small amount of users to millions of users
+ * Discuss general scaling patterns as we evolve an architecture to handle a large number of users and requests
+* **Service** has high availability
+
+### Constraints and assumptions
+
+#### State assumptions
+
+* Traffic is not evenly distributed
+* Need for relational data
+* Scale from 1 user to tens of millions of users
+ * Denote increase of users as:
+ * Users+
+ * Users++
+ * Users+++
+ * ...
+ * 10 million users
+ * 1 billion writes per month
+ * 100 billion reads per month
+ * 100:1 read to write ratio
+ * 1 KB content per write
+
+#### Calculate usage
+
+**Clarify with your interviewer if you should run back-of-the-envelope usage calculations.**
+
+* 1 TB of new content per month
+ * 1 KB per write * 1 billion writes per month
+ * 36 TB of new content in 3 years
+ * Assume most writes are from new content instead of updates to existing ones
+* 400 writes per second on average
+* 40,000 reads per second on average
+
+Handy conversion guide:
+
+* 2.5 million seconds per month
+* 1 request per second = 2.5 million requests per month
+* 40 requests per second = 100 million requests per month
+* 400 requests per second = 1 billion requests per month
+
+## Step 2: Create a high level design
+
+> Outline a high level design with all important components.
+
+
+
+## Step 3: Design core components
+
+> Dive into details for each core component.
+
+### Use case: User makes a read or write request
+
+#### Goals
+
+* With only 1-2 users, you only need a basic setup
+ * Single box for simplicity
+ * Vertical scaling when needed
+ * Monitor to determine bottlenecks
+
+#### Start with a single box
+
+* **Web server** on EC2
+ * Storage for user data
+ * [**MySQL Database**](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms)
+
+Use **Vertical Scaling**:
+
+* Simply choose a bigger box
+* Keep an eye on metrics to determine how to scale up
+ * Use basic monitoring to determine bottlenecks: CPU, memory, IO, network, etc
+ * CloudWatch, top, nagios, statsd, graphite, etc
+* Scaling vertically can get very expensive
+* No redundancy/failover
+
+*Trade-offs, alternatives, and additional details:*
+
+* The alternative to **Vertical Scaling** is [**Horizontal scaling**](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+
+#### Start with SQL, consider NoSQL
+
+The constraints assume there is a need for relational data. We can start off using a **MySQL Database** on the single box.
+
+*Trade-offs, alternatives, and additional details:*
+
+* See the [Relational database management system (RDBMS)](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms) section
+* Discuss reasons to use [SQL or NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+#### Assign a public static IP
+
+* Elastic IPs provide a public endpoint whose IP doesn't change on reboot
+* Helps with failover, just point the domain to a new IP
+
+#### Use a DNS
+
+Add a **DNS** such as Route 53 to map the domain to the instance's public IP.
+
+*Trade-offs, alternatives, and additional details:*
+
+* See the [Domain name system](https://github.com/donnemartin/system-design-primer#domain-name-system) section
+
+#### Secure the web server
+
+* Open up only necessary ports
+ * Allow the web server to respond to incoming requests from:
+ * 80 for HTTP
+ * 443 for HTTPS
+ * 22 for SSH to only whitelisted IPs
+ * Prevent the web server from initiating outbound connections
+
+*Trade-offs, alternatives, and additional details:*
+
+* See the [Security](https://github.com/donnemartin/system-design-primer#security) section
+
+## Step 4: Scale the design
+
+> Identify and address bottlenecks, given the constraints.
+
+### Users+
+
+
+
+#### Assumptions
+
+Our user count is starting to pick up and the load is increasing on our single box. Our **Benchmarks/Load Tests** and **Profiling** are pointing to the **MySQL Database** taking up more and more memory and CPU resources, while the user content is filling up disk space.
+
+We've been able to address these issues with **Vertical Scaling** so far. Unfortunately, this has become quite expensive and it doesn't allow for independent scaling of the **MySQL Database** and **Web Server**.
+
+#### Goals
+
+* Lighten load on the single box and allow for independent scaling
+ * Store static content separately in an **Object Store**
+ * Move the **MySQL Database** to a separate box
+* Disadvantages
+ * These changes would increase complexity and would require changes to the **Web Server** to point to the **Object Store** and the **MySQL Database**
+ * Additional security measures must be taken to secure the new components
+ * AWS costs could also increase, but should be weighed with the costs of managing similar systems on your own
+
+#### Store static content separately
+
+* Consider using a managed **Object Store** like S3 to store static content
+ * Highly scalable and reliable
+ * Server side encryption
+* Move static content to S3
+ * User files
+ * JS
+ * CSS
+ * Images
+ * Videos
+
+#### Move the MySQL database to a separate box
+
+* Consider using a service like RDS to manage the **MySQL Database**
+ * Simple to administer, scale
+ * Multiple availability zones
+ * Encryption at rest
+
+#### Secure the system
+
+* Encrypt data in transit and at rest
+* Use a Virtual Private Cloud
+ * Create a public subnet for the single **Web Server** so it can send and receive traffic from the internet
+ * Create a private subnet for everything else, preventing outside access
+ * Only open ports from whitelisted IPs for each component
+* These same patterns should be implemented for new components in the remainder of the exercise
+
+*Trade-offs, alternatives, and additional details:*
+
+* See the [Security](https://github.com/donnemartin/system-design-primer#security) section
+
+### Users++
+
+
+
+#### Assumptions
+
+Our **Benchmarks/Load Tests** and **Profiling** show that our single **Web Server** bottlenecks during peak hours, resulting in slow responses and in some cases, downtime. As the service matures, we'd also like to move towards higher availability and redundancy.
+
+#### Goals
+
+* The following goals attempt to address the scaling issues with the **Web Server**
+ * Based on the **Benchmarks/Load Tests** and **Profiling**, you might only need to implement one or two of these techniques
+* Use [**Horizontal Scaling**](https://github.com/donnemartin/system-design-primer#horizontal-scaling) to handle increasing loads and to address single points of failure
+ * Add a [**Load Balancer**](https://github.com/donnemartin/system-design-primer#load-balancer) such as Amazon's ELB or HAProxy
+ * ELB is highly available
+ * If you are configuring your own **Load Balancer**, setting up multiple servers in [active-active](https://github.com/donnemartin/system-design-primer#active-active) or [active-passive](https://github.com/donnemartin/system-design-primer#active-passive) in multiple availability zones will improve availability
+ * Terminate SSL on the **Load Balancer** to reduce computational load on backend servers and to simplify certificate administration
+ * Use multiple **Web Servers** spread out over multiple availability zones
+ * Use multiple **MySQL** instances in [**Master-Slave Failover**](https://github.com/donnemartin/system-design-primer#master-slave-replication) mode across multiple availability zones to improve redundancy
+* Separate out the **Web Servers** from the [**Application Servers**](https://github.com/donnemartin/system-design-primer#application-layer)
+ * Scale and configure both layers independently
+ * **Web Servers** can run as a [**Reverse Proxy**](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+ * For example, you can add **Application Servers** handling **Read APIs** while others handle **Write APIs**
+* Move static (and some dynamic) content to a [**Content Delivery Network (CDN)**](https://github.com/donnemartin/system-design-primer#content-delivery-network) such as CloudFront to reduce load and latency
+
+*Trade-offs, alternatives, and additional details:*
+
+* See the linked content above for details
+
+### Users+++
+
+
+
+**Note:** **Internal Load Balancers** not shown to reduce clutter
+
+#### Assumptions
+
+Our **Benchmarks/Load Tests** and **Profiling** show that we are read-heavy (100:1 with writes) and our database is suffering from poor performance from the high read requests.
+
+#### Goals
+
+* The following goals attempt to address the scaling issues with the **MySQL Database**
+ * Based on the **Benchmarks/Load Tests** and **Profiling**, you might only need to implement one or two of these techniques
+* Move the following data to a [**Memory Cache**](https://github.com/donnemartin/system-design-primer#cache) such as Elasticache to reduce load and latency:
+ * Frequently accessed content from **MySQL**
+ * First, try to configure the **MySQL Database** cache to see if that is sufficient to relieve the bottleneck before implementing a **Memory Cache**
+ * Session data from the **Web Servers**
+ * The **Web Servers** become stateless, allowing for **Autoscaling**
+ * Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.1
+* Add [**MySQL Read Replicas**](https://github.com/donnemartin/system-design-primer#master-slave-replication) to reduce load on the write master
+* Add more **Web Servers** and **Application Servers** to improve responsiveness
+
+*Trade-offs, alternatives, and additional details:*
+
+* See the linked content above for details
+
+#### Add MySQL read replicas
+
+* In addition to adding and scaling a **Memory Cache**, **MySQL Read Replicas** can also help relieve load on the **MySQL Write Master**
+* Add logic to **Web Server** to separate out writes and reads
+* Add **Load Balancers** in front of **MySQL Read Replicas** (not pictured to reduce clutter)
+* Most services are read-heavy vs write-heavy
+
+*Trade-offs, alternatives, and additional details:*
+
+* See the [Relational database management system (RDBMS)](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms) section
+
+### Users++++
+
+
+
+#### Assumptions
+
+Our **Benchmarks/Load Tests** and **Profiling** show that our traffic spikes during regular business hours in the U.S. and drop significantly when users leave the office. We think we can cut costs by automatically spinning up and down servers based on actual load. We're a small shop so we'd like to automate as much of the DevOps as possible for **Autoscaling** and for the general operations.
+
+#### Goals
+
+* Add **Autoscaling** to provision capacity as needed
+ * Keep up with traffic spikes
+ * Reduce costs by powering down unused instances
+* Automate DevOps
+ * Chef, Puppet, Ansible, etc
+* Continue monitoring metrics to address bottlenecks
+ * **Host level** - Review a single EC2 instance
+ * **Aggregate level** - Review load balancer stats
+ * **Log analysis** - CloudWatch, CloudTrail, Loggly, Splunk, Sumo
+ * **External site performance** - Pingdom or New Relic
+ * **Handle notifications and incidents** - PagerDuty
+ * **Error Reporting** - Sentry
+
+#### Add autoscaling
+
+* Consider a managed service such as AWS **Autoscaling**
+ * Create one group for each **Web Server** and one for each **Application Server** type, place each group in multiple availability zones
+ * Set a min and max number of instances
+ * Trigger to scale up and down through CloudWatch
+ * Simple time of day metric for predictable loads or
+ * Metrics over a time period:
+ * CPU load
+ * Latency
+ * Network traffic
+ * Custom metric
+ * Disadvantages
+ * Autoscaling can introduce complexity
+ * It could take some time before a system appropriately scales up to meet increased demand, or to scale down when demand drops
+
+### Users+++++
+
+
+
+**Note:** **Autoscaling** groups not shown to reduce clutter
+
+#### Assumptions
+
+As the service continues to grow towards the figures outlined in the constraints, we iteratively run **Benchmarks/Load Tests** and **Profiling** to uncover and address new bottlenecks.
+
+#### Goals
+
+We'll continue to address scaling issues due to the problem's constraints:
+
+* If our **MySQL Database** starts to grow too large, we might consider only storing a limited time period of data in the database, while storing the rest in a data warehouse such as Redshift
+ * A data warehouse such as Redshift can comfortably handle the constraint of 1 TB of new content per month
+* With 40,000 average read requests per second, read traffic for popular content can be addressed by scaling the **Memory Cache**, which is also useful for handling the unevenly distributed traffic and traffic spikes
+ * The **SQL Read Replicas** might have trouble handling the cache misses, we'll probably need to employ additional SQL scaling patterns
+* 400 average writes per second (with presumably significantly higher peaks) might be tough for a single **SQL Write Master-Slave**, also pointing to a need for additional scaling techniques
+
+SQL scaling patterns include:
+
+* [Federation](https://github.com/donnemartin/system-design-primer#federation)
+* [Sharding](https://github.com/donnemartin/system-design-primer#sharding)
+* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+To further address the high read and write requests, we should also consider moving appropriate data to a [**NoSQL Database**](https://github.com/donnemartin/system-design-primer#nosql) such as DynamoDB.
+
+We can further separate out our [**Application Servers**](https://github.com/donnemartin/system-design-primer#application-layer) to allow for independent scaling. Batch processes or computations that do not need to be done in real-time can be done [**Asynchronously**](https://github.com/donnemartin/system-design-primer#asynchronism) with **Queues** and **Workers**:
+
+* For example, in a photo service, the photo upload and the thumbnail creation can be separated:
+ * **Client** uploads photo
+ * **Application Server** puts a job in a **Queue** such as SQS
+ * The **Worker Service** on EC2 or Lambda pulls work off the **Queue** then:
+ * Creates a thumbnail
+ * Updates a **Database**
+ * Stores the thumbnail in the **Object Store**
+
+*Trade-offs, alternatives, and additional details:*
+
+* See the linked content above for details
+
+## Additional talking points
+
+> Additional topics to dive into, depending on the problem scope and time remaining.
+
+### SQL scaling patterns
+
+* [Read replicas](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [Federation](https://github.com/donnemartin/system-design-primer#federation)
+* [Sharding](https://github.com/donnemartin/system-design-primer#sharding)
+* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+#### NoSQL
+
+* [Key-value store](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [Document store](https://github.com/donnemartin/system-design-primer#document-store)
+* [Wide column store](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [Graph database](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### Caching
+
+* Where to cache
+ * [Client caching](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN caching](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web server caching](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [Database caching](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [Application caching](https://github.com/donnemartin/system-design-primer#application-caching)
+* What to cache
+ * [Caching at the database query level](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [Caching at the object level](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* When to update the cache
+ * [Cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [Write-through](https://github.com/donnemartin/system-design-primer#write-through)
+ * [Write-behind (write-back)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [Refresh ahead](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### Asynchronism and microservices
+
+* [Message queues](https://github.com/donnemartin/system-design-primer#message-queues)
+* [Task queues](https://github.com/donnemartin/system-design-primer#task-queues)
+* [Back pressure](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [Microservices](https://github.com/donnemartin/system-design-primer#microservices)
+
+### Communications
+
+* Discuss tradeoffs:
+ * External communication with clients - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * Internal communications - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [Service discovery](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### Security
+
+Refer to the [security section](https://github.com/donnemartin/system-design-primer#security).
+
+### Latency numbers
+
+See [Latency numbers every programmer should know](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know).
+
+### Ongoing
+
+* Continue benchmarking and monitoring your system to address bottlenecks as they come up
+* Scaling is an iterative process
diff --git a/website/solutions/system_design/scaling_aws/scaling_aws.graffle b/website/solutions/system_design/scaling_aws/scaling_aws.graffle
new file mode 100644
index 00000000000..460d1debfc0
Binary files /dev/null and b/website/solutions/system_design/scaling_aws/scaling_aws.graffle differ
diff --git a/website/solutions/system_design/scaling_aws/scaling_aws.png b/website/solutions/system_design/scaling_aws/scaling_aws.png
new file mode 100644
index 00000000000..dee8994d99c
Binary files /dev/null and b/website/solutions/system_design/scaling_aws/scaling_aws.png differ
diff --git a/website/solutions/system_design/scaling_aws/scaling_aws_1.png b/website/solutions/system_design/scaling_aws/scaling_aws_1.png
new file mode 100644
index 00000000000..e4fae5d5928
Binary files /dev/null and b/website/solutions/system_design/scaling_aws/scaling_aws_1.png differ
diff --git a/website/solutions/system_design/scaling_aws/scaling_aws_2.png b/website/solutions/system_design/scaling_aws/scaling_aws_2.png
new file mode 100644
index 00000000000..34342b01358
Binary files /dev/null and b/website/solutions/system_design/scaling_aws/scaling_aws_2.png differ
diff --git a/website/solutions/system_design/scaling_aws/scaling_aws_3.png b/website/solutions/system_design/scaling_aws/scaling_aws_3.png
new file mode 100644
index 00000000000..766932d5c81
Binary files /dev/null and b/website/solutions/system_design/scaling_aws/scaling_aws_3.png differ
diff --git a/website/solutions/system_design/scaling_aws/scaling_aws_4.png b/website/solutions/system_design/scaling_aws/scaling_aws_4.png
new file mode 100644
index 00000000000..958a10e6a60
Binary files /dev/null and b/website/solutions/system_design/scaling_aws/scaling_aws_4.png differ
diff --git a/website/solutions/system_design/scaling_aws/scaling_aws_5.png b/website/solutions/system_design/scaling_aws/scaling_aws_5.png
new file mode 100644
index 00000000000..703d4137573
Binary files /dev/null and b/website/solutions/system_design/scaling_aws/scaling_aws_5.png differ
diff --git a/website/solutions/system_design/scaling_aws/scaling_aws_6.png b/website/solutions/system_design/scaling_aws/scaling_aws_6.png
new file mode 100644
index 00000000000..e663d950b1c
Binary files /dev/null and b/website/solutions/system_design/scaling_aws/scaling_aws_6.png differ
diff --git a/website/solutions/system_design/scaling_aws/scaling_aws_7.png b/website/solutions/system_design/scaling_aws/scaling_aws_7.png
new file mode 100644
index 00000000000..8498cb547d2
Binary files /dev/null and b/website/solutions/system_design/scaling_aws/scaling_aws_7.png differ
diff --git a/website/solutions/system_design/social_graph/README-zh-Hans.md b/website/solutions/system_design/social_graph/README-zh-Hans.md
new file mode 100644
index 00000000000..07b8e3e7886
--- /dev/null
+++ b/website/solutions/system_design/social_graph/README-zh-Hans.md
@@ -0,0 +1,348 @@
+# 为社交网络设计数据结构
+
+**注释:为了避免重复,这篇文章的链接直接关联到 [系统设计主题](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) 的相关章节。为一讨论要点、折中方案和可选方案做参考。**
+
+## 第 1 步:用例和约束概要
+
+> 收集需求并调查问题。
+> 通过提问清晰用例和约束。
+> 讨论假设。
+
+如果没有面试官提出明确的问题,我们将自己定义一些用例和约束条件。
+
+### 用例
+
+#### 我们就处理以下用例审视这一问题
+
+* **用户** 寻找某人并显示与被寻人之间的最短路径
+* **服务** 高可用
+
+### 约束和假设
+
+#### 状态假设
+
+* 流量分布不均
+ * 某些搜索比别的更热门,同时某些搜索仅执行一次
+* 图数据不适用单一机器
+* 图的边没有权重
+* 1 千万用户
+* 每个用户平均有 50 个朋友
+* 每月 10 亿次朋友搜索
+
+训练使用更传统的系统 - 别用图特有的解决方案例如 [GraphQL](http://graphql.org/) 或图数据库如 [Neo4j](https://neo4j.com/)。
+
+#### 计算使用
+
+**向你的面试官厘清你是否应该做粗略的使用计算**
+
+* 50 亿朋友关系
+ * 1 亿用户 * 平均每人 50 个朋友
+* 每秒 400 次搜索请求
+
+便捷的转换指南:
+
+* 每月 250 万秒
+* 每秒 1 个请求 = 每月 250 万次请求
+* 每秒 40 个请求 = 每月 1 亿次请求
+* 每秒 400 个请求 = 每月 10 亿次请求
+
+## 第 2 步:创建高级设计方案
+
+> 用所有重要组件概述高水平设计
+
+
+
+## 第 3 步:设计核心组件
+
+> 深入每个核心组件的细节。
+
+### 用例: 用户搜索某人并查看到被搜人的最短路径
+
+**和你的面试官说清你期望的代码量**
+
+没有百万用户(点)的和十亿朋友关系(边)的限制,我们能够用一般 BFS 方法解决无权重最短路径任务:
+
+```python
+class Graph(Graph):
+
+ def shortest_path(self, source, dest):
+ if source is None or dest is None:
+ return None
+ if source is dest:
+ return [source.key]
+ prev_node_keys = self._shortest_path(source, dest)
+ if prev_node_keys is None:
+ return None
+ else:
+ path_ids = [dest.key]
+ prev_node_key = prev_node_keys[dest.key]
+ while prev_node_key is not None:
+ path_ids.append(prev_node_key)
+ prev_node_key = prev_node_keys[prev_node_key]
+ return path_ids[::-1]
+
+ def _shortest_path(self, source, dest):
+ queue = deque()
+ queue.append(source)
+ prev_node_keys = {source.key: None}
+ source.visit_state = State.visited
+ while queue:
+ node = queue.popleft()
+ if node is dest:
+ return prev_node_keys
+ prev_node = node
+ for adj_node in node.adj_nodes.values():
+ if adj_node.visit_state == State.unvisited:
+ queue.append(adj_node)
+ prev_node_keys[adj_node.key] = prev_node.key
+ adj_node.visit_state = State.visited
+ return None
+```
+
+我们不能在同一台机器上满足所有用户,我们需要通过 **人员服务器** [拆分](https://github.com/donnemartin/system-design-primer#sharding) 用户并且通过 **查询服务** 访问。
+
+* **客户端** 向 **服务器** 发送请求,**服务器** 作为 [反向代理](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* **搜索 API** 服务器向 **用户图服务** 转发请求
+* **用户图服务** 有以下功能:
+ * 使用 **查询服务** 找到当前用户信息存储的 **人员服务器**
+ * 找到适当的 **人员服务器** 检索当前用户的 `friend_ids` 列表
+ * 把当前用户作为 `source` 运行 BFS 搜索算法同时 当前用户的 `friend_ids` 作为每个 `adjacent_node` 的 ids
+ * 给定 id 获取 `adjacent_node`:
+ * **用户图服务** 将 **再次** 和 **查询服务** 通讯,最后判断出和给定 id 相匹配的存储 `adjacent_node` 的 **人员服务器**(有待优化)
+
+**和你的面试官说清你应该写的代码量**
+
+**注释**:简易版错误处理执行如下。询问你是否需要编写适当的错误处理方法。
+
+**查询服务** 实现:
+
+```python
+class LookupService(object):
+
+ def __init__(self):
+ self.lookup = self._init_lookup() # key: person_id, value: person_server
+
+ def _init_lookup(self):
+ ...
+
+ def lookup_person_server(self, person_id):
+ return self.lookup[person_id]
+```
+
+**人员服务器** 实现:
+
+```python
+class PersonServer(object):
+
+ def __init__(self):
+ self.people = {} # key: person_id, value: person
+
+ def add_person(self, person):
+ ...
+
+ def people(self, ids):
+ results = []
+ for id in ids:
+ if id in self.people:
+ results.append(self.people[id])
+ return results
+```
+
+**用户** 实现:
+
+```python
+class Person(object):
+
+ def __init__(self, id, name, friend_ids):
+ self.id = id
+ self.name = name
+ self.friend_ids = friend_ids
+```
+
+**用户图服务** 实现:
+
+```python
+class UserGraphService(object):
+
+ def __init__(self, lookup_service):
+ self.lookup_service = lookup_service
+
+ def person(self, person_id):
+ person_server = self.lookup_service.lookup_person_server(person_id)
+ return person_server.people([person_id])
+
+ def shortest_path(self, source_key, dest_key):
+ if source_key is None or dest_key is None:
+ return None
+ if source_key is dest_key:
+ return [source_key]
+ prev_node_keys = self._shortest_path(source_key, dest_key)
+ if prev_node_keys is None:
+ return None
+ else:
+ # Iterate through the path_ids backwards, starting at dest_key
+ path_ids = [dest_key]
+ prev_node_key = prev_node_keys[dest_key]
+ while prev_node_key is not None:
+ path_ids.append(prev_node_key)
+ prev_node_key = prev_node_keys[prev_node_key]
+ # Reverse the list since we iterated backwards
+ return path_ids[::-1]
+
+ def _shortest_path(self, source_key, dest_key, path):
+ # Use the id to get the Person
+ source = self.person(source_key)
+ # Update our bfs queue
+ queue = deque()
+ queue.append(source)
+ # prev_node_keys keeps track of each hop from
+ # the source_key to the dest_key
+ prev_node_keys = {source_key: None}
+ # We'll use visited_ids to keep track of which nodes we've
+ # visited, which can be different from a typical bfs where
+ # this can be stored in the node itself
+ visited_ids = set()
+ visited_ids.add(source.id)
+ while queue:
+ node = queue.popleft()
+ if node.key is dest_key:
+ return prev_node_keys
+ prev_node = node
+ for friend_id in node.friend_ids:
+ if friend_id not in visited_ids:
+ friend_node = self.person(friend_id)
+ queue.append(friend_node)
+ prev_node_keys[friend_id] = prev_node.key
+ visited_ids.add(friend_id)
+ return None
+```
+
+我们用的是公共的 [**REST API**](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest):
+
+```
+$ curl https://social.com/api/v1/friend_search?person_id=1234
+```
+
+响应:
+
+```
+{
+ "person_id": "100",
+ "name": "foo",
+ "link": "https://social.com/foo",
+},
+{
+ "person_id": "53",
+ "name": "bar",
+ "link": "https://social.com/bar",
+},
+{
+ "person_id": "1234",
+ "name": "baz",
+ "link": "https://social.com/baz",
+},
+```
+
+内部通信使用 [远端过程调用](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)。
+
+## 第 4 步:扩展设计
+
+> 在给定约束条件下,定义和确认瓶颈。
+
+
+
+**重要:别简化从最初设计到最终设计的过程!**
+
+你将要做的是:1) **基准/负载 测试**, 2) 瓶颈 **概述**, 3) 当评估可选和折中方案时定位瓶颈,4) 重复。以 [在 AWS 上设计支持百万级到千万级用户的系统](../scaling_aws/README.md) 为参考迭代地扩展最初设计。
+
+讨论最初设计可能遇到的瓶颈和处理方法十分重要。例如,什么问题可以通过添加多台 **Web 服务器** 作为 **负载均衡** 解决?**CDN**?**主从副本**?每个问题都有哪些替代和 **折中** 方案?
+
+我们即将介绍一些组件来完成设计和解决扩展性问题。内部负载均衡不显示以减少混乱。
+
+**避免重复讨论**,以下网址链接到 [系统设计主题](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) 相关的主流方案、折中方案和替代方案。
+
+* [DNS](https://github.com/donnemartin/system-design-primer#domain-name-system)
+* [负载均衡](https://github.com/donnemartin/system-design-primer#load-balancer)
+* [横向扩展](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+* [Web 服务器(反向代理)](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* [API 服务器(应用层)](https://github.com/donnemartin/system-design-primer#application-layer)
+* [缓存](https://github.com/donnemartin/system-design-primer#cache)
+* [一致性模式](https://github.com/donnemartin/system-design-primer#consistency-patterns)
+* [可用性模式](https://github.com/donnemartin/system-design-primer#availability-patterns)
+
+解决 **平均** 每秒 400 次请求的限制(峰值),人员数据可以存在例如 Redis 或 Memcached 这样的 **内存** 中以减少响应次数和下游流量通信服务。这尤其在用户执行多次连续查询和查询哪些广泛连接的人时十分有用。从内存中读取 1MB 数据大约要 250 微秒,从 SSD 中读取同样大小的数据时间要长 4 倍,从硬盘要长 80 倍。1
+
+以下是进一步优化方案:
+
+* 在 **内存** 中存储完整的或部分的BFS遍历加快后续查找
+* 在 **NoSQL 数据库** 中批量离线计算并存储完整的或部分的BFS遍历加快后续查找
+* 在同一台 **人员服务器** 上托管批处理同一批朋友查找减少机器跳转
+ * 通过地理位置 [拆分](https://github.com/donnemartin/system-design-primer#sharding) **人员服务器** 来进一步优化,因为朋友通常住得都比较近
+* 同时进行两个 BFS 查找,一个从 source 开始,一个从 destination 开始,然后合并两个路径
+* 从有庞大朋友圈的人开始找起,这样更有可能减小当前用户和搜索目标之间的 [离散度数](https://en.wikipedia.org/wiki/Six_degrees_of_separation)
+* 在询问用户是否继续查询之前设置基于时间或跳跃数阈值,当在某些案例中搜索耗费时间过长时。
+* 使用类似 [Neo4j](https://neo4j.com/) 的 **图数据库** 或图特定查询语法,例如 [GraphQL](http://graphql.org/)(如果没有禁止使用 **图数据库** 的限制的话)
+
+## 额外的话题
+
+> 根据问题的范围和剩余时间,还需要深入讨论其他问题。
+
+### SQL 扩展模式
+
+* [读取副本](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [集合](https://github.com/donnemartin/system-design-primer#federation)
+* [分区](https://github.com/donnemartin/system-design-primer#sharding)
+* [反规范化](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL 调优](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+#### NoSQL
+
+* [键值存储](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [文档存储](https://github.com/donnemartin/system-design-primer#document-store)
+* [宽表存储](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [图数据库](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### 缓存
+
+* 缓存到哪里
+ * [客户端缓存](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN 缓存](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web 服务缓存](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [数据库缓存](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [应用缓存](https://github.com/donnemartin/system-design-primer#application-caching)
+* 缓存什么
+ * [数据库请求层缓存](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [对象层缓存](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* 何时更新缓存
+ * [预留缓存](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [完全写入](https://github.com/donnemartin/system-design-primer#write-through)
+ * [延迟写 (写回)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [事先更新](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### 异步性和微服务
+
+* [消息队列](https://github.com/donnemartin/system-design-primer#message-queues)
+* [任务队列](https://github.com/donnemartin/system-design-primer#task-queues)
+* [回退压力](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [微服务](https://github.com/donnemartin/system-design-primer#microservices)
+
+### 沟通
+
+* 关于折中方案的讨论:
+ * 客户端的外部通讯 - [遵循 REST 的 HTTP APIs](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * 内部通讯 - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [服务探索](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### 安全性
+
+参考 [安全章节](https://github.com/donnemartin/system-design-primer#security)
+
+### 延迟数字指标
+
+查阅 [每个程序员必懂的延迟数字](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know)
+
+### 正在进行
+
+* 继续基准测试并监控你的系统以解决出现的瓶颈问题
+* 扩展是一个迭代的过程
diff --git a/website/solutions/system_design/social_graph/README.md b/website/solutions/system_design/social_graph/README.md
new file mode 100644
index 00000000000..f7dfd4efe8d
--- /dev/null
+++ b/website/solutions/system_design/social_graph/README.md
@@ -0,0 +1,349 @@
+# Design the data structures for a social network
+
+*Note: This document links directly to relevant areas found in the [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) to avoid duplication. Refer to the linked content for general talking points, tradeoffs, and alternatives.*
+
+## Step 1: Outline use cases and constraints
+
+> Gather requirements and scope the problem.
+> Ask questions to clarify use cases and constraints.
+> Discuss assumptions.
+
+Without an interviewer to address clarifying questions, we'll define some use cases and constraints.
+
+### Use cases
+
+#### We'll scope the problem to handle only the following use cases
+
+* **User** searches for someone and sees the shortest path to the searched person
+* **Service** has high availability
+
+### Constraints and assumptions
+
+#### State assumptions
+
+* Traffic is not evenly distributed
+ * Some searches are more popular than others, while others are only executed once
+* Graph data won't fit on a single machine
+* Graph edges are unweighted
+* 100 million users
+* 50 friends per user average
+* 1 billion friend searches per month
+
+Exercise the use of more traditional systems - don't use graph-specific solutions such as [GraphQL](http://graphql.org/) or a graph database like [Neo4j](https://neo4j.com/)
+
+#### Calculate usage
+
+**Clarify with your interviewer if you should run back-of-the-envelope usage calculations.**
+
+* 5 billion friend relationships
+ * 100 million users * 50 friends per user average
+* 400 search requests per second
+
+Handy conversion guide:
+
+* 2.5 million seconds per month
+* 1 request per second = 2.5 million requests per month
+* 40 requests per second = 100 million requests per month
+* 400 requests per second = 1 billion requests per month
+
+## Step 2: Create a high level design
+
+> Outline a high level design with all important components.
+
+
+
+## Step 3: Design core components
+
+> Dive into details for each core component.
+
+### Use case: User searches for someone and sees the shortest path to the searched person
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+Without the constraint of millions of users (vertices) and billions of friend relationships (edges), we could solve this unweighted shortest path task with a general BFS approach:
+
+```python
+class Graph(Graph):
+
+ def shortest_path(self, source, dest):
+ if source is None or dest is None:
+ return None
+ if source is dest:
+ return [source.key]
+ prev_node_keys = self._shortest_path(source, dest)
+ if prev_node_keys is None:
+ return None
+ else:
+ path_ids = [dest.key]
+ prev_node_key = prev_node_keys[dest.key]
+ while prev_node_key is not None:
+ path_ids.append(prev_node_key)
+ prev_node_key = prev_node_keys[prev_node_key]
+ return path_ids[::-1]
+
+ def _shortest_path(self, source, dest):
+ queue = deque()
+ queue.append(source)
+ prev_node_keys = {source.key: None}
+ source.visit_state = State.visited
+ while queue:
+ node = queue.popleft()
+ if node is dest:
+ return prev_node_keys
+ prev_node = node
+ for adj_node in node.adj_nodes.values():
+ if adj_node.visit_state == State.unvisited:
+ queue.append(adj_node)
+ prev_node_keys[adj_node.key] = prev_node.key
+ adj_node.visit_state = State.visited
+ return None
+```
+
+We won't be able to fit all users on the same machine, we'll need to [shard](https://github.com/donnemartin/system-design-primer#sharding) users across **Person Servers** and access them with a **Lookup Service**.
+
+* The **Client** sends a request to the **Web Server**, running as a [reverse proxy](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* The **Web Server** forwards the request to the **Search API** server
+* The **Search API** server forwards the request to the **User Graph Service**
+* The **User Graph Service** does the following:
+ * Uses the **Lookup Service** to find the **Person Server** where the current user's info is stored
+ * Finds the appropriate **Person Server** to retrieve the current user's list of `friend_ids`
+ * Runs a BFS search using the current user as the `source` and the current user's `friend_ids` as the ids for each `adjacent_node`
+ * To get the `adjacent_node` from a given id:
+ * The **User Graph Service** will *again* need to communicate with the **Lookup Service** to determine which **Person Server** stores the`adjacent_node` matching the given id (potential for optimization)
+
+**Clarify with your interviewer how much code you should be writing**.
+
+**Note**: Error handling is excluded below for simplicity. Ask if you should code proper error handing.
+
+**Lookup Service** implementation:
+
+```python
+class LookupService(object):
+
+ def __init__(self):
+ self.lookup = self._init_lookup() # key: person_id, value: person_server
+
+ def _init_lookup(self):
+ ...
+
+ def lookup_person_server(self, person_id):
+ return self.lookup[person_id]
+```
+
+**Person Server** implementation:
+
+```python
+class PersonServer(object):
+
+ def __init__(self):
+ self.people = {} # key: person_id, value: person
+
+ def add_person(self, person):
+ ...
+
+ def people(self, ids):
+ results = []
+ for id in ids:
+ if id in self.people:
+ results.append(self.people[id])
+ return results
+```
+
+**Person** implementation:
+
+```python
+class Person(object):
+
+ def __init__(self, id, name, friend_ids):
+ self.id = id
+ self.name = name
+ self.friend_ids = friend_ids
+```
+
+**User Graph Service** implementation:
+
+```python
+class UserGraphService(object):
+
+ def __init__(self, lookup_service):
+ self.lookup_service = lookup_service
+
+ def person(self, person_id):
+ person_server = self.lookup_service.lookup_person_server(person_id)
+ return person_server.people([person_id])
+
+ def shortest_path(self, source_key, dest_key):
+ if source_key is None or dest_key is None:
+ return None
+ if source_key is dest_key:
+ return [source_key]
+ prev_node_keys = self._shortest_path(source_key, dest_key)
+ if prev_node_keys is None:
+ return None
+ else:
+ # Iterate through the path_ids backwards, starting at dest_key
+ path_ids = [dest_key]
+ prev_node_key = prev_node_keys[dest_key]
+ while prev_node_key is not None:
+ path_ids.append(prev_node_key)
+ prev_node_key = prev_node_keys[prev_node_key]
+ # Reverse the list since we iterated backwards
+ return path_ids[::-1]
+
+ def _shortest_path(self, source_key, dest_key, path):
+ # Use the id to get the Person
+ source = self.person(source_key)
+ # Update our bfs queue
+ queue = deque()
+ queue.append(source)
+ # prev_node_keys keeps track of each hop from
+ # the source_key to the dest_key
+ prev_node_keys = {source_key: None}
+ # We'll use visited_ids to keep track of which nodes we've
+ # visited, which can be different from a typical bfs where
+ # this can be stored in the node itself
+ visited_ids = set()
+ visited_ids.add(source.id)
+ while queue:
+ node = queue.popleft()
+ if node.key is dest_key:
+ return prev_node_keys
+ prev_node = node
+ for friend_id in node.friend_ids:
+ if friend_id not in visited_ids:
+ friend_node = self.person(friend_id)
+ queue.append(friend_node)
+ prev_node_keys[friend_id] = prev_node.key
+ visited_ids.add(friend_id)
+ return None
+```
+
+We'll use a public [**REST API**](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest):
+
+```
+$ curl https://social.com/api/v1/friend_search?person_id=1234
+```
+
+Response:
+
+```
+{
+ "person_id": "100",
+ "name": "foo",
+ "link": "https://social.com/foo",
+},
+{
+ "person_id": "53",
+ "name": "bar",
+ "link": "https://social.com/bar",
+},
+{
+ "person_id": "1234",
+ "name": "baz",
+ "link": "https://social.com/baz",
+},
+```
+
+For internal communications, we could use [Remote Procedure Calls](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc).
+
+## Step 4: Scale the design
+
+> Identify and address bottlenecks, given the constraints.
+
+
+
+**Important: Do not simply jump right into the final design from the initial design!**
+
+State you would 1) **Benchmark/Load Test**, 2) **Profile** for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat. See [Design a system that scales to millions of users on AWS](../scaling_aws/README.md) as a sample on how to iteratively scale the initial design.
+
+It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them. For example, what issues are addressed by adding a **Load Balancer** with multiple **Web Servers**? **CDN**? **Master-Slave Replicas**? What are the alternatives and **Trade-Offs** for each?
+
+We'll introduce some components to complete the design and to address scalability issues. Internal load balancers are not shown to reduce clutter.
+
+*To avoid repeating discussions*, refer to the following [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) for main talking points, tradeoffs, and alternatives:
+
+* [DNS](https://github.com/donnemartin/system-design-primer#domain-name-system)
+* [Load balancer](https://github.com/donnemartin/system-design-primer#load-balancer)
+* [Horizontal scaling](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+* [Web server (reverse proxy)](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* [API server (application layer)](https://github.com/donnemartin/system-design-primer#application-layer)
+* [Cache](https://github.com/donnemartin/system-design-primer#cache)
+* [Consistency patterns](https://github.com/donnemartin/system-design-primer#consistency-patterns)
+* [Availability patterns](https://github.com/donnemartin/system-design-primer#availability-patterns)
+
+To address the constraint of 400 *average* read requests per second (higher at peak), person data can be served from a **Memory Cache** such as Redis or Memcached to reduce response times and to reduce traffic to downstream services. This could be especially useful for people who do multiple searches in succession and for people who are well-connected. Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.1
+
+Below are further optimizations:
+
+* Store complete or partial BFS traversals to speed up subsequent lookups in the **Memory Cache**
+* Batch compute offline then store complete or partial BFS traversals to speed up subsequent lookups in a **NoSQL Database**
+* Reduce machine jumps by batching together friend lookups hosted on the same **Person Server**
+ * [Shard](https://github.com/donnemartin/system-design-primer#sharding) **Person Servers** by location to further improve this, as friends generally live closer to each other
+* Do two BFS searches at the same time, one starting from the source, and one from the destination, then merge the two paths
+* Start the BFS search from people with large numbers of friends, as they are more likely to reduce the number of [degrees of separation](https://en.wikipedia.org/wiki/Six_degrees_of_separation) between the current user and the search target
+* Set a limit based on time or number of hops before asking the user if they want to continue searching, as searching could take a considerable amount of time in some cases
+* Use a **Graph Database** such as [Neo4j](https://neo4j.com/) or a graph-specific query language such as [GraphQL](http://graphql.org/) (if there were no constraint preventing the use of **Graph Databases**)
+
+## Additional talking points
+
+> Additional topics to dive into, depending on the problem scope and time remaining.
+
+### SQL scaling patterns
+
+* [Read replicas](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [Federation](https://github.com/donnemartin/system-design-primer#federation)
+* [Sharding](https://github.com/donnemartin/system-design-primer#sharding)
+* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+#### NoSQL
+
+* [Key-value store](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [Document store](https://github.com/donnemartin/system-design-primer#document-store)
+* [Wide column store](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [Graph database](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### Caching
+
+* Where to cache
+ * [Client caching](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN caching](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web server caching](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [Database caching](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [Application caching](https://github.com/donnemartin/system-design-primer#application-caching)
+* What to cache
+ * [Caching at the database query level](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [Caching at the object level](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* When to update the cache
+ * [Cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [Write-through](https://github.com/donnemartin/system-design-primer#write-through)
+ * [Write-behind (write-back)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [Refresh ahead](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### Asynchronism and microservices
+
+* [Message queues](https://github.com/donnemartin/system-design-primer#message-queues)
+* [Task queues](https://github.com/donnemartin/system-design-primer#task-queues)
+* [Back pressure](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [Microservices](https://github.com/donnemartin/system-design-primer#microservices)
+
+### Communications
+
+* Discuss tradeoffs:
+ * External communication with clients - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * Internal communications - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [Service discovery](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### Security
+
+Refer to the [security section](https://github.com/donnemartin/system-design-primer#security).
+
+### Latency numbers
+
+See [Latency numbers every programmer should know](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know).
+
+### Ongoing
+
+* Continue benchmarking and monitoring your system to address bottlenecks as they come up
+* Scaling is an iterative process
diff --git a/website/solutions/system_design/social_graph/__init__.py b/website/solutions/system_design/social_graph/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/system_design/social_graph/social_graph.graffle b/website/solutions/system_design/social_graph/social_graph.graffle
new file mode 100644
index 00000000000..a245d5a66c6
Binary files /dev/null and b/website/solutions/system_design/social_graph/social_graph.graffle differ
diff --git a/website/solutions/system_design/social_graph/social_graph.png b/website/solutions/system_design/social_graph/social_graph.png
new file mode 100644
index 00000000000..86b730bb1e3
Binary files /dev/null and b/website/solutions/system_design/social_graph/social_graph.png differ
diff --git a/website/solutions/system_design/social_graph/social_graph_basic.graffle b/website/solutions/system_design/social_graph/social_graph_basic.graffle
new file mode 100644
index 00000000000..a753b39c782
Binary files /dev/null and b/website/solutions/system_design/social_graph/social_graph_basic.graffle differ
diff --git a/website/solutions/system_design/social_graph/social_graph_basic.png b/website/solutions/system_design/social_graph/social_graph_basic.png
new file mode 100644
index 00000000000..d6f4255e840
Binary files /dev/null and b/website/solutions/system_design/social_graph/social_graph_basic.png differ
diff --git a/website/solutions/system_design/social_graph/social_graph_snippets.py b/website/solutions/system_design/social_graph/social_graph_snippets.py
new file mode 100644
index 00000000000..f0ea4c4b932
--- /dev/null
+++ b/website/solutions/system_design/social_graph/social_graph_snippets.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+from collections import deque
+from enum import Enum
+
+
+class State(Enum):
+ unvisited = 0
+ visited = 1
+
+
+class Graph(object):
+
+ def bfs(self, source, dest):
+ if source is None:
+ return False
+ queue = deque()
+ queue.append(source)
+ source.visit_state = State.visited
+ while queue:
+ node = queue.popleft()
+ print(node)
+ if dest is node:
+ return True
+ for adjacent_node in node.adj_nodes.values():
+ if adjacent_node.visit_state == State.unvisited:
+ queue.append(adjacent_node)
+ adjacent_node.visit_state = State.visited
+ return False
+
+
+class Person(object):
+
+ def __init__(self, id, name):
+ self.id = id
+ self.name = name
+ self.friend_ids = []
+
+
+class LookupService(object):
+
+ def __init__(self):
+ self.lookup = {} # key: person_id, value: person_server
+
+ def get_person(self, person_id):
+ person_server = self.lookup[person_id]
+ return person_server.people[person_id]
+
+
+class PersonServer(object):
+
+ def __init__(self):
+ self.people = {} # key: person_id, value: person
+
+ def get_people(self, ids):
+ results = []
+ for id in ids:
+ if id in self.people:
+ results.append(self.people[id])
+ return results
+
+
+class UserGraphService(object):
+
+ def __init__(self, person_ids, lookup):
+ self.lookup = lookup
+ self.person_ids = person_ids
+ self.visited_ids = set()
+
+ def bfs(self, source, dest):
+ # Use self.visited_ids to track visited nodes
+ # Use self.lookup to translate a person_id to a Person
+ pass
diff --git a/website/solutions/system_design/template/template.graffle b/website/solutions/system_design/template/template.graffle
new file mode 100644
index 00000000000..6908231eee3
Binary files /dev/null and b/website/solutions/system_design/template/template.graffle differ
diff --git a/website/solutions/system_design/twitter/README-zh-Hans.md b/website/solutions/system_design/twitter/README-zh-Hans.md
new file mode 100644
index 00000000000..1853444dbc8
--- /dev/null
+++ b/website/solutions/system_design/twitter/README-zh-Hans.md
@@ -0,0 +1,331 @@
+# 设计推特时间轴与搜索功能
+
+**注意:这个文档中的链接会直接指向[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)中的有关部分,以避免重复的内容。你可以参考链接的相关内容,来了解其总的要点、方案的权衡取舍以及可选的替代方案。**
+
+**设计 Facebook 的 feed** 与**设计 Facebook 搜索**与此为同一类型问题。
+
+## 第一步:简述用例与约束条件
+
+> 搜集需求与问题的范围。
+> 提出问题来明确用例与约束条件。
+> 讨论假设。
+
+我们将在没有面试官明确说明问题的情况下,自己定义一些用例以及限制条件。
+
+### 用例
+
+#### 我们将把问题限定在仅处理以下用例的范围中
+
+* **用户**发布了一篇推特
+ * **服务**将推特推送给关注者,给他们发送消息通知与邮件
+* **用户**浏览用户时间轴(用户最近的活动)
+* **用户**浏览主页时间轴(用户关注的人最近的活动)
+* **用户**搜索关键词
+* **服务**需要有高可用性
+
+#### 不在用例范围内的有
+
+* **服务**向 Firehose 与其它流数据接口推送推特
+* **服务**根据用户的”是否可见“选项排除推特
+ * 隐藏未关注者的 @回复
+ * 关心”隐藏转发“设置
+* 数据分析
+
+### 限制条件与假设
+
+#### 提出假设
+
+普遍情况
+
+* 网络流量不是均匀分布的
+* 发布推特的速度需要足够快速
+ * 除非有上百万的关注者,否则将推特推送给粉丝的速度要足够快
+* 1 亿个活跃用户
+* 每天新发布 5 亿条推特,每月新发布 150 亿条推特
+ * 平均每条推特需要推送给 5 个人
+ * 每天需要进行 50 亿次推送
+ * 每月需要进行 1500 亿次推送
+* 每月需要处理 2500 亿次读取请求
+* 每月需要处理 100 亿次搜索
+
+时间轴功能
+
+* 浏览时间轴需要足够快
+* 推特的读取负载要大于写入负载
+ * 需要为推特的快速读取进行优化
+* 存入推特是高写入负载功能
+
+搜索功能
+
+* 搜索速度需要足够快
+* 搜索是高负载读取功能
+
+#### 计算用量
+
+**如果你需要进行粗略的用量计算,请向你的面试官说明。**
+
+* 每条推特的大小:
+ * `tweet_id` - 8 字节
+ * `user_id` - 32 字节
+ * `text` - 140 字节
+ * `media` - 平均 10 KB
+ * 总计: 大约 10 KB
+* 每月产生新推特的内容为 150 TB
+ * 每条推特 10 KB * 每天 5 亿条推特 * 每月 30 天
+ * 3 年产生新推特的内容为 5.4 PB
+* 每秒需要处理 10 万次读取请求
+ * 每个月需要处理 2500 亿次请求 * (每秒 400 次请求 / 每月 10 亿次请求)
+* 每秒发布 6000 条推特
+ * 每月发布 150 亿条推特 * (每秒 400 次请求 / 每月 10 次请求)
+* 每秒推送 6 万条推特
+ * 每月推送 1500 亿条推特 * (每秒 400 次请求 / 每月 10 亿次请求)
+* 每秒 4000 次搜索请求
+
+便利换算指南:
+
+* 每个月有 250 万秒
+* 每秒一个请求 = 每个月 250 万次请求
+* 每秒 40 个请求 = 每个月 1 亿次请求
+* 每秒 400 个请求 = 每个月 10 亿次请求
+
+## 第二步:概要设计
+
+> 列出所有重要组件以规划概要设计。
+
+
+
+## 第三步:设计核心组件
+
+> 深入每个核心组件的细节。
+
+### 用例:用户发表了一篇推特
+
+我们可以将用户自己发表的推特存储在[关系数据库](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms)中。我们也可以讨论一下[究竟是用 SQL 还是用 NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)。
+
+构建用户主页时间轴(查看关注用户的活动)以及推送推特是件麻烦事。将特推传播给所有关注者(每秒约递送 6 万条推特)这一操作有可能会使传统的[关系数据库](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms)超负载。因此,我们可以使用 **NoSQL 数据库**或**内存数据库**之类的更快的数据存储方式。从内存读取 1 MB 连续数据大约要花 250 微秒,而从 SSD 读取同样大小的数据要花费 4 倍的时间,从机械硬盘读取需要花费 80 倍以上的时间。1
+
+我们可以将照片、视频之类的媒体存储于**对象存储**中。
+
+* **客户端**向应用[反向代理](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)的**Web 服务器**发送一条推特
+* **Web 服务器**将请求转发给**写 API**服务器
+* **写 API**服务器将推特使用 **SQL 数据库**存储于用户时间轴中
+* **写 API**调用**消息输出服务**,进行以下操作:
+ * 查询**用户 图 服务**找到存储于**内存缓存**中的此用户的粉丝
+ * 将推特存储于**内存缓存**中的**此用户的粉丝的主页时间轴**中
+ * O(n) 复杂度操作: 1000 名粉丝 = 1000 次查找与插入
+ * 将特推存储在**搜索索引服务**中,以加快搜索
+ * 将媒体存储于**对象存储**中
+ * 使用**通知服务**向粉丝发送推送:
+ * 使用**队列**异步推送通知
+
+**向你的面试官告知你准备写多少代码**。
+
+如果我们用 Redis 作为**内存缓存**,那可以用 Redis 原生的 list 作为其数据结构。结构如下:
+
+```
+ tweet n+2 tweet n+1 tweet n
+| 8 bytes 8 bytes 1 byte | 8 bytes 8 bytes 1 byte | 8 bytes 8 bytes 1 byte |
+| tweet_id user_id meta | tweet_id user_id meta | tweet_id user_id meta |
+```
+
+新发布的推特将被存储在对应用户(关注且活跃的用户)的主页时间轴的**内存缓存**中。
+
+我们可以调用一个公共的 [REST API](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest):
+
+```
+$ curl -X POST --data '{ "user_id": "123", "auth_token": "ABC123", \
+ "status": "hello world!", "media_ids": "ABC987" }' \
+ https://twitter.com/api/v1/tweet
+```
+
+返回:
+
+```
+{
+ "created_at": "Wed Sep 05 00:37:15 +0000 2012",
+ "status": "hello world!",
+ "tweet_id": "987",
+ "user_id": "123",
+ ...
+}
+```
+
+而对于服务器内部的通信,我们可以使用 [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)。
+
+### 用例:用户浏览主页时间轴
+
+* **客户端**向 **Web 服务器**发起一次读取主页时间轴的请求
+* **Web 服务器**将请求转发给**读取 API**服务器
+* **读取 API**服务器调用**时间轴服务**进行以下操作:
+ * 从**内存缓存**读取时间轴数据,其中包括推特 id 与用户 id - O(1)
+ * 通过 [multiget](http://redis.io/commands/mget) 向**推特信息服务**进行查询,以获取相关 id 推特的额外信息 - O(n)
+ * 通过 muiltiget 向**用户信息服务**进行查询,以获取相关 id 用户的额外信息 - O(n)
+
+REST API:
+
+```
+$ curl https://twitter.com/api/v1/home_timeline?user_id=123
+```
+
+返回:
+
+```
+{
+ "user_id": "456",
+ "tweet_id": "123",
+ "status": "foo"
+},
+{
+ "user_id": "789",
+ "tweet_id": "456",
+ "status": "bar"
+},
+{
+ "user_id": "789",
+ "tweet_id": "579",
+ "status": "baz"
+},
+```
+
+### 用例:用户浏览用户时间轴
+
+* **客户端**向**Web 服务器**发起获得用户时间线的请求
+* **Web 服务器**将请求转发给**读取 API**服务器
+* **读取 API**从 **SQL 数据库**中取出用户的时间轴
+
+REST API 与前面的主页时间轴类似,区别只在于取出的推特是由用户自己发送而不是关注人发送。
+
+### 用例:用户搜索关键词
+
+* **客户端**将搜索请求发给**Web 服务器**
+* **Web 服务器**将请求转发给**搜索 API**服务器
+* **搜索 API**调用**搜索服务**进行以下操作:
+ * 对输入进行转换与分词,弄明白需要搜索什么东西
+ * 移除标点等额外内容
+ * 将文本打散为词组
+ * 修正拼写错误
+ * 规范字母大小写
+ * 将查询转换为布尔操作
+ * 查询**搜索集群**(例如[Lucene](https://lucene.apache.org/))检索结果:
+ * 对集群内的所有服务器进行查询,将有结果的查询进行[发散聚合(Scatter gathers)](https://github.com/donnemartin/system-design-primer#under-development)
+ * 合并取到的条目,进行评分与排序,最终返回结果
+
+REST API:
+
+```
+$ curl https://twitter.com/api/v1/search?query=hello+world
+```
+
+返回结果与前面的主页时间轴类似,只不过返回的是符合查询条件的推特。
+
+## 第四步:架构扩展
+
+> 根据限制条件,找到并解决瓶颈。
+
+
+
+**重要提示:不要从最初设计直接跳到最终设计中!**
+
+现在你要 1) **基准测试、负载测试**。2) **分析、描述**性能瓶颈。3) 在解决瓶颈问题的同时,评估替代方案、权衡利弊。4) 重复以上步骤。请阅读[「设计一个系统,并将其扩大到为数以百万计的 AWS 用户服务」](../scaling_aws/README.md) 来了解如何逐步扩大初始设计。
+
+讨论初始设计可能遇到的瓶颈及相关解决方案是很重要的。例如加上一个配置多台 **Web 服务器**的**负载均衡器**是否能够解决问题?**CDN**呢?**主从复制**呢?它们各自的替代方案和需要**权衡**的利弊又有什么呢?
+
+我们将会介绍一些组件来完成设计,并解决架构扩张问题。内置的负载均衡器将不做讨论以节省篇幅。
+
+**为了避免重复讨论**,请参考[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)相关部分来了解其要点、方案的权衡取舍以及可选的替代方案。
+
+* [DNS](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#域名系统)
+* [负载均衡器](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#负载均衡器)
+* [水平拓展](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#水平扩展)
+* [反向代理(web 服务器)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)
+* [API 服务(应用层)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用层)
+* [缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存)
+* [关系型数据库管理系统 (RDBMS)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#关系型数据库管理系统rdbms)
+* [SQL 故障主从切换](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#故障切换)
+* [主从复制](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#主从复制)
+* [一致性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#一致性模式)
+* [可用性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#可用性模式)
+
+**消息输出服务**有可能成为性能瓶颈。那些有着百万数量关注着的用户可能发一条推特就需要好几分钟才能完成消息输出进程。这有可能使 @回复 这种推特时出现竞争条件,因此需要根据服务时间对此推特进行重排序来降低影响。
+
+我们还可以避免从高关注量的用户输出推特。相反,我们可以通过搜索来找到高关注量用户的推特,并将搜索结果与用户的主页时间轴合并,再根据时间对其进行排序。
+
+此外,还可以通过以下内容进行优化:
+
+* 仅为每个主页时间轴在**内存缓存**中存储数百条推特
+* 仅在**内存缓存**中存储活动用户的主页时间轴
+ * 如果某个用户在过去 30 天都没有产生活动,那我们可以使用 **SQL 数据库**重新构建他的时间轴
+ * 使用**用户 图 服务**来查询并确定用户关注的人
+ * 从 **SQL 数据库**中取出推特,并将它们存入**内存缓存**
+* 仅在**推特信息服务**中存储一个月的推特
+* 仅在**用户信息服务**中存储活动用户的信息
+* **搜索集群**需要将推特保留在内存中,以降低延迟
+
+我们还可以考虑优化 **SQL 数据库** 来解决一些瓶颈问题。
+
+**内存缓存**能减小一些数据库的负载,靠 **SQL Read 副本**已经足够处理缓存未命中情况。我们还可以考虑使用一些额外的 SQL 性能拓展技术。
+
+高容量的写入将淹没单个的 **SQL 写主从**模式,因此需要更多的拓展技术。
+
+* [联合](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#联合)
+* [分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#分片)
+* [非规范化](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#非规范化)
+* [SQL 调优](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-调优)
+
+我们也可以考虑将一些数据移至 **NoSQL 数据库**。
+
+## 其它要点
+
+> 是否深入这些额外的主题,取决于你的问题范围和剩下的时间。
+
+#### NoSQL
+
+* [键-值存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#键-值存储)
+* [文档类型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#文档类型存储)
+* [列型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#列型存储)
+* [图数据库](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#图数据库)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)
+
+### 缓存
+
+* 在哪缓存
+ * [客户端缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#客户端缓存)
+ * [CDN 缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#cdn-缓存)
+ * [Web 服务器缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#web-服务器缓存)
+ * [数据库缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库缓存)
+ * [应用缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用缓存)
+* 什么需要缓存
+ * [数据库查询级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库查询级别的缓存)
+ * [对象级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#对象级别的缓存)
+* 何时更新缓存
+ * [缓存模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存模式)
+ * [直写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#直写模式)
+ * [回写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#回写模式)
+ * [刷新](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#刷新)
+
+### 异步与微服务
+
+* [消息队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#消息队列)
+* [任务队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#任务队列)
+* [背压](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#背压)
+* [微服务](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#微服务)
+
+### 通信
+
+* 可权衡选择的方案:
+ * 与客户端的外部通信 - [使用 REST 作为 HTTP API](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest)
+ * 服务器内部通信 - [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)
+* [服务发现](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#服务发现)
+
+### 安全性
+
+请参阅[「安全」](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#安全)一章。
+
+### 延迟数值
+
+请参阅[「每个程序员都应该知道的延迟数」](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#每个程序员都应该知道的延迟数)。
+
+### 持续探讨
+
+* 持续进行基准测试并监控你的系统,以解决他们提出的瓶颈问题。
+* 架构拓展是一个迭代的过程。
diff --git a/website/solutions/system_design/twitter/README.md b/website/solutions/system_design/twitter/README.md
new file mode 100644
index 00000000000..d14996f1526
--- /dev/null
+++ b/website/solutions/system_design/twitter/README.md
@@ -0,0 +1,333 @@
+# Design the Twitter timeline and search
+
+*Note: This document links directly to relevant areas found in the [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) to avoid duplication. Refer to the linked content for general talking points, tradeoffs, and alternatives.*
+
+**Design the Facebook feed** and **Design Facebook search** are similar questions.
+
+## Step 1: Outline use cases and constraints
+
+> Gather requirements and scope the problem.
+> Ask questions to clarify use cases and constraints.
+> Discuss assumptions.
+
+Without an interviewer to address clarifying questions, we'll define some use cases and constraints.
+
+### Use cases
+
+#### We'll scope the problem to handle only the following use cases
+
+* **User** posts a tweet
+ * **Service** pushes tweets to followers, sending push notifications and emails
+* **User** views the user timeline (activity from the user)
+* **User** views the home timeline (activity from people the user is following)
+* **User** searches keywords
+* **Service** has high availability
+
+#### Out of scope
+
+* **Service** pushes tweets to the Twitter Firehose and other streams
+* **Service** strips out tweets based on users' visibility settings
+ * Hide @reply if the user is not also following the person being replied to
+ * Respect 'hide retweets' setting
+* Analytics
+
+### Constraints and assumptions
+
+#### State assumptions
+
+General
+
+* Traffic is not evenly distributed
+* Posting a tweet should be fast
+ * Fanning out a tweet to all of your followers should be fast, unless you have millions of followers
+* 100 million active users
+* 500 million tweets per day or 15 billion tweets per month
+ * Each tweet averages a fanout of 10 deliveries
+ * 5 billion total tweets delivered on fanout per day
+ * 150 billion tweets delivered on fanout per month
+* 250 billion read requests per month
+* 10 billion searches per month
+
+Timeline
+
+* Viewing the timeline should be fast
+* Twitter is more read heavy than write heavy
+ * Optimize for fast reads of tweets
+* Ingesting tweets is write heavy
+
+Search
+
+* Searching should be fast
+* Search is read-heavy
+
+#### Calculate usage
+
+**Clarify with your interviewer if you should run back-of-the-envelope usage calculations.**
+
+* Size per tweet:
+ * `tweet_id` - 8 bytes
+ * `user_id` - 32 bytes
+ * `text` - 140 bytes
+ * `media` - 10 KB average
+ * Total: ~10 KB
+* 150 TB of new tweet content per month
+ * 10 KB per tweet * 500 million tweets per day * 30 days per month
+ * 5.4 PB of new tweet content in 3 years
+* 100 thousand read requests per second
+ * 250 billion read requests per month * (400 requests per second / 1 billion requests per month)
+* 6,000 tweets per second
+ * 15 billion tweets per month * (400 requests per second / 1 billion requests per month)
+* 60 thousand tweets delivered on fanout per second
+ * 150 billion tweets delivered on fanout per month * (400 requests per second / 1 billion requests per month)
+* 4,000 search requests per second
+ * 10 billion searches per month * (400 requests per second / 1 billion requests per month)
+
+Handy conversion guide:
+
+* 2.5 million seconds per month
+* 1 request per second = 2.5 million requests per month
+* 40 requests per second = 100 million requests per month
+* 400 requests per second = 1 billion requests per month
+
+## Step 2: Create a high level design
+
+> Outline a high level design with all important components.
+
+
+
+## Step 3: Design core components
+
+> Dive into details for each core component.
+
+### Use case: User posts a tweet
+
+We could store the user's own tweets to populate the user timeline (activity from the user) in a [relational database](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms). We should discuss the [use cases and tradeoffs between choosing SQL or NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql).
+
+Delivering tweets and building the home timeline (activity from people the user is following) is trickier. Fanning out tweets to all followers (60 thousand tweets delivered on fanout per second) will overload a traditional [relational database](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms). We'll probably want to choose a data store with fast writes such as a **NoSQL database** or **Memory Cache**. Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.1
+
+We could store media such as photos or videos on an **Object Store**.
+
+* The **Client** posts a tweet to the **Web Server**, running as a [reverse proxy](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* The **Web Server** forwards the request to the **Write API** server
+* The **Write API** stores the tweet in the user's timeline on a **SQL database**
+* The **Write API** contacts the **Fan Out Service**, which does the following:
+ * Queries the **User Graph Service** to find the user's followers stored in the **Memory Cache**
+ * Stores the tweet in the *home timeline of the user's followers* in a **Memory Cache**
+ * O(n) operation: 1,000 followers = 1,000 lookups and inserts
+ * Stores the tweet in the **Search Index Service** to enable fast searching
+ * Stores media in the **Object Store**
+ * Uses the **Notification Service** to send out push notifications to followers:
+ * Uses a **Queue** (not pictured) to asynchronously send out notifications
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+If our **Memory Cache** is Redis, we could use a native Redis list with the following structure:
+
+```
+ tweet n+2 tweet n+1 tweet n
+| 8 bytes 8 bytes 1 byte | 8 bytes 8 bytes 1 byte | 8 bytes 8 bytes 1 byte |
+| tweet_id user_id meta | tweet_id user_id meta | tweet_id user_id meta |
+```
+
+The new tweet would be placed in the **Memory Cache**, which populates the user's home timeline (activity from people the user is following).
+
+We'll use a public [**REST API**](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest):
+
+```
+$ curl -X POST --data '{ "user_id": "123", "auth_token": "ABC123", \
+ "status": "hello world!", "media_ids": "ABC987" }' \
+ https://twitter.com/api/v1/tweet
+```
+
+Response:
+
+```
+{
+ "created_at": "Wed Sep 05 00:37:15 +0000 2012",
+ "status": "hello world!",
+ "tweet_id": "987",
+ "user_id": "123",
+ ...
+}
+```
+
+For internal communications, we could use [Remote Procedure Calls](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc).
+
+### Use case: User views the home timeline
+
+* The **Client** posts a home timeline request to the **Web Server**
+* The **Web Server** forwards the request to the **Read API** server
+* The **Read API** server contacts the **Timeline Service**, which does the following:
+ * Gets the timeline data stored in the **Memory Cache**, containing tweet ids and user ids - O(1)
+ * Queries the **Tweet Info Service** with a [multiget](http://redis.io/commands/mget) to obtain additional info about the tweet ids - O(n)
+ * Queries the **User Info Service** with a multiget to obtain additional info about the user ids - O(n)
+
+REST API:
+
+```
+$ curl https://twitter.com/api/v1/home_timeline?user_id=123
+```
+
+Response:
+
+```
+{
+ "user_id": "456",
+ "tweet_id": "123",
+ "status": "foo"
+},
+{
+ "user_id": "789",
+ "tweet_id": "456",
+ "status": "bar"
+},
+{
+ "user_id": "789",
+ "tweet_id": "579",
+ "status": "baz"
+},
+```
+
+### Use case: User views the user timeline
+
+* The **Client** posts a user timeline request to the **Web Server**
+* The **Web Server** forwards the request to the **Read API** server
+* The **Read API** retrieves the user timeline from the **SQL Database**
+
+The REST API would be similar to the home timeline, except all tweets would come from the user as opposed to the people the user is following.
+
+### Use case: User searches keywords
+
+* The **Client** sends a search request to the **Web Server**
+* The **Web Server** forwards the request to the **Search API** server
+* The **Search API** contacts the **Search Service**, which does the following:
+ * Parses/tokenizes the input query, determining what needs to be searched
+ * Removes markup
+ * Breaks up the text into terms
+ * Fixes typos
+ * Normalizes capitalization
+ * Converts the query to use boolean operations
+ * Queries the **Search Cluster** (ie [Lucene](https://lucene.apache.org/)) for the results:
+ * [Scatter gathers](https://github.com/donnemartin/system-design-primer#under-development) each server in the cluster to determine if there are any results for the query
+ * Merges, ranks, sorts, and returns the results
+
+REST API:
+
+```
+$ curl https://twitter.com/api/v1/search?query=hello+world
+```
+
+The response would be similar to that of the home timeline, except for tweets matching the given query.
+
+## Step 4: Scale the design
+
+> Identify and address bottlenecks, given the constraints.
+
+
+
+**Important: Do not simply jump right into the final design from the initial design!**
+
+State you would 1) **Benchmark/Load Test**, 2) **Profile** for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat. See [Design a system that scales to millions of users on AWS](../scaling_aws/README.md) as a sample on how to iteratively scale the initial design.
+
+It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them. For example, what issues are addressed by adding a **Load Balancer** with multiple **Web Servers**? **CDN**? **Master-Slave Replicas**? What are the alternatives and **Trade-Offs** for each?
+
+We'll introduce some components to complete the design and to address scalability issues. Internal load balancers are not shown to reduce clutter.
+
+*To avoid repeating discussions*, refer to the following [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) for main talking points, tradeoffs, and alternatives:
+
+* [DNS](https://github.com/donnemartin/system-design-primer#domain-name-system)
+* [CDN](https://github.com/donnemartin/system-design-primer#content-delivery-network)
+* [Load balancer](https://github.com/donnemartin/system-design-primer#load-balancer)
+* [Horizontal scaling](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+* [Web server (reverse proxy)](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* [API server (application layer)](https://github.com/donnemartin/system-design-primer#application-layer)
+* [Cache](https://github.com/donnemartin/system-design-primer#cache)
+* [Relational database management system (RDBMS)](https://github.com/donnemartin/system-design-primer#relational-database-management-system-rdbms)
+* [SQL write master-slave failover](https://github.com/donnemartin/system-design-primer#fail-over)
+* [Master-slave replication](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [Consistency patterns](https://github.com/donnemartin/system-design-primer#consistency-patterns)
+* [Availability patterns](https://github.com/donnemartin/system-design-primer#availability-patterns)
+
+The **Fanout Service** is a potential bottleneck. Twitter users with millions of followers could take several minutes to have their tweets go through the fanout process. This could lead to race conditions with @replies to the tweet, which we could mitigate by re-ordering the tweets at serve time.
+
+We could also avoid fanning out tweets from highly-followed users. Instead, we could search to find tweets for highly-followed users, merge the search results with the user's home timeline results, then re-order the tweets at serve time.
+
+Additional optimizations include:
+
+* Keep only several hundred tweets for each home timeline in the **Memory Cache**
+* Keep only active users' home timeline info in the **Memory Cache**
+ * If a user was not previously active in the past 30 days, we could rebuild the timeline from the **SQL Database**
+ * Query the **User Graph Service** to determine who the user is following
+ * Get the tweets from the **SQL Database** and add them to the **Memory Cache**
+* Store only a month of tweets in the **Tweet Info Service**
+* Store only active users in the **User Info Service**
+* The **Search Cluster** would likely need to keep the tweets in memory to keep latency low
+
+We'll also want to address the bottleneck with the **SQL Database**.
+
+Although the **Memory Cache** should reduce the load on the database, it is unlikely the **SQL Read Replicas** alone would be enough to handle the cache misses. We'll probably need to employ additional SQL scaling patterns.
+
+The high volume of writes would overwhelm a single **SQL Write Master-Slave**, also pointing to a need for additional scaling techniques.
+
+* [Federation](https://github.com/donnemartin/system-design-primer#federation)
+* [Sharding](https://github.com/donnemartin/system-design-primer#sharding)
+* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+We should also consider moving some data to a **NoSQL Database**.
+
+## Additional talking points
+
+> Additional topics to dive into, depending on the problem scope and time remaining.
+
+#### NoSQL
+
+* [Key-value store](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [Document store](https://github.com/donnemartin/system-design-primer#document-store)
+* [Wide column store](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [Graph database](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### Caching
+
+* Where to cache
+ * [Client caching](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN caching](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web server caching](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [Database caching](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [Application caching](https://github.com/donnemartin/system-design-primer#application-caching)
+* What to cache
+ * [Caching at the database query level](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [Caching at the object level](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* When to update the cache
+ * [Cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [Write-through](https://github.com/donnemartin/system-design-primer#write-through)
+ * [Write-behind (write-back)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [Refresh ahead](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### Asynchronism and microservices
+
+* [Message queues](https://github.com/donnemartin/system-design-primer#message-queues)
+* [Task queues](https://github.com/donnemartin/system-design-primer#task-queues)
+* [Back pressure](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [Microservices](https://github.com/donnemartin/system-design-primer#microservices)
+
+### Communications
+
+* Discuss tradeoffs:
+ * External communication with clients - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * Internal communications - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [Service discovery](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### Security
+
+Refer to the [security section](https://github.com/donnemartin/system-design-primer#security).
+
+### Latency numbers
+
+See [Latency numbers every programmer should know](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know).
+
+### Ongoing
+
+* Continue benchmarking and monitoring your system to address bottlenecks as they come up
+* Scaling is an iterative process
diff --git a/website/solutions/system_design/twitter/twitter.graffle b/website/solutions/system_design/twitter/twitter.graffle
new file mode 100644
index 00000000000..f93f4eac7b4
Binary files /dev/null and b/website/solutions/system_design/twitter/twitter.graffle differ
diff --git a/website/solutions/system_design/twitter/twitter.png b/website/solutions/system_design/twitter/twitter.png
new file mode 100644
index 00000000000..e7de2883260
Binary files /dev/null and b/website/solutions/system_design/twitter/twitter.png differ
diff --git a/website/solutions/system_design/twitter/twitter_basic.graffle b/website/solutions/system_design/twitter/twitter_basic.graffle
new file mode 100644
index 00000000000..c754b9e8844
Binary files /dev/null and b/website/solutions/system_design/twitter/twitter_basic.graffle differ
diff --git a/website/solutions/system_design/twitter/twitter_basic.png b/website/solutions/system_design/twitter/twitter_basic.png
new file mode 100644
index 00000000000..7c67f7644f8
Binary files /dev/null and b/website/solutions/system_design/twitter/twitter_basic.png differ
diff --git a/website/solutions/system_design/web_crawler/README-zh-Hans.md b/website/solutions/system_design/web_crawler/README-zh-Hans.md
new file mode 100644
index 00000000000..2ad0938e5d9
--- /dev/null
+++ b/website/solutions/system_design/web_crawler/README-zh-Hans.md
@@ -0,0 +1,356 @@
+# 设计一个网页爬虫
+
+**注意:这个文档中的链接会直接指向[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)中的有关部分,以避免重复的内容。你可以参考链接的相关内容,来了解其总的要点、方案的权衡取舍以及可选的替代方案。**
+
+## 第一步:简述用例与约束条件
+
+> 把所有需要的东西聚集在一起,审视问题。不停的提问,以至于我们可以明确使用场景和约束。讨论假设。
+
+我们将在没有面试官明确说明问题的情况下,自己定义一些用例以及限制条件。
+
+### 用例
+
+#### 我们把问题限定在仅处理以下用例的范围中
+
+* **服务** 抓取一系列链接:
+ * 生成包含搜索词的网页倒排索引
+ * 生成页面的标题和摘要信息
+ * 页面标题和摘要都是静态的,它们不会根据搜索词改变
+* **用户** 输入搜索词后,可以看到相关的搜索结果列表,列表每一项都包含由网页爬虫生成的页面标题及摘要
+ * 只给该用例绘制出概要组件和交互说明,无需讨论细节
+* **服务** 具有高可用性
+
+#### 无需考虑
+
+* 搜索分析
+* 个性化搜索结果
+* 页面排名
+
+### 限制条件与假设
+
+#### 提出假设
+
+* 搜索流量分布不均
+ * 有些搜索词非常热门,有些则非常冷门
+* 只支持匿名用户
+* 用户很快就能看到搜索结果
+* 网页爬虫不应该陷入死循环
+ * 当爬虫路径包含环的时候,将会陷入死循环
+* 抓取 10 亿个链接
+ * 要定期重新抓取页面以确保新鲜度
+ * 平均每周重新抓取一次,网站越热门,那么重新抓取的频率越高
+ * 每月抓取 40 亿个链接
+ * 每个页面的平均存储大小:500 KB
+ * 简单起见,重新抓取的页面算作新页面
+* 每月搜索量 1000 亿次
+
+用更传统的系统来练习 —— 不要使用 [solr](http://lucene.apache.org/solr/) 、[nutch](http://nutch.apache.org/) 之类的现成系统。
+
+#### 计算用量
+
+**如果你需要进行粗略的用量计算,请向你的面试官说明。**
+
+* 每月存储 2 PB 页面
+ * 每月抓取 40 亿个页面,每个页面 500 KB
+ * 三年存储 72 PB 页面
+* 每秒 1600 次写请求
+* 每秒 40000 次搜索请求
+
+简便换算指南:
+
+* 一个月有 250 万秒
+* 每秒 1 个请求,即每月 250 万个请求
+* 每秒 40 个请求,即每月 1 亿个请求
+* 每秒 400 个请求,即每月 10 亿个请求
+
+## 第二步: 概要设计
+
+> 列出所有重要组件以规划概要设计。
+
+
+
+## 第三步:设计核心组件
+
+> 对每一个核心组件进行详细深入的分析。
+
+### 用例:爬虫服务抓取一系列网页
+
+假设我们有一个初始列表 `links_to_crawl`(待抓取链接),它最初基于网站整体的知名度来排序。当然如果这个假设不合理,我们可以使用 [Yahoo](https://www.yahoo.com/)、[DMOZ](http://www.dmoz.org/) 等知名门户网站作为种子链接来进行扩散 。
+
+我们将用表 `crawled_links` (已抓取链接 )来记录已经处理过的链接以及相应的页面签名。
+
+我们可以将 `links_to_crawl` 和 `crawled_links` 记录在键-值型 **NoSQL 数据库**中。对于 `crawled_links` 中已排序的链接,我们可以使用 [Redis](https://redis.io/) 的有序集合来维护网页链接的排名。我们应当在 [选择 SQL 还是 NoSQL 的问题上,讨论有关使用场景以及利弊 ](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)。
+
+* **爬虫服务**按照以下流程循环处理每一个页面链接:
+ * 选取排名最靠前的待抓取链接
+ * 在 **NoSQL 数据库**的 `crawled_links` 中,检查待抓取页面的签名是否与某个已抓取页面的签名相似
+ * 若存在,则降低该页面链接的优先级
+ * 这样做可以避免陷入死循环
+ * 继续(进入下一次循环)
+ * 若不存在,则抓取该链接
+ * 在**倒排索引服务**任务队列中,新增一个生成[倒排索引](https://en.wikipedia.org/wiki/Search_engine_indexing)任务。
+ * 在**文档服务**任务队列中,新增一个生成静态标题和摘要的任务。
+ * 生成页面签名
+ * 在 **NoSQL 数据库**的 `links_to_crawl` 中删除该链接
+ * 在 **NoSQL 数据库**的 `crawled_links` 中插入该链接以及页面签名
+
+**向面试官了解你需要写多少代码**。
+
+`PagesDataStore` 是**爬虫服务**中的一个抽象类,它使用 **NoSQL 数据库**进行存储。
+
+```python
+class PagesDataStore(object):
+
+ def __init__(self, db);
+ self.db = db
+ ...
+
+ def add_link_to_crawl(self, url):
+ """将指定链接加入 `links_to_crawl`。"""
+ ...
+
+ def remove_link_to_crawl(self, url):
+ """从 `links_to_crawl` 中删除指定链接。"""
+ ...
+
+ def reduce_priority_link_to_crawl(self, url)
+ """在 `links_to_crawl` 中降低一个链接的优先级以避免死循环。"""
+ ...
+
+ def extract_max_priority_page(self):
+ """返回 `links_to_crawl` 中优先级最高的链接。"""
+ ...
+
+ def insert_crawled_link(self, url, signature):
+ """将指定链接加入 `crawled_links`。"""
+ ...
+
+ def crawled_similar(self, signature):
+ """判断待抓取页面的签名是否与某个已抓取页面的签名相似。"""
+ ...
+```
+
+`Page` 是**爬虫服务**的一个抽象类,它封装了网页对象,由页面链接、页面内容、子链接和页面签名构成。
+
+```python
+class Page(object):
+
+ def __init__(self, url, contents, child_urls, signature):
+ self.url = url
+ self.contents = contents
+ self.child_urls = child_urls
+ self.signature = signature
+```
+
+`Crawler` 是**爬虫服务**的主类,由`Page` 和 `PagesDataStore` 组成。
+
+```python
+class Crawler(object):
+
+ def __init__(self, data_store, reverse_index_queue, doc_index_queue):
+ self.data_store = data_store
+ self.reverse_index_queue = reverse_index_queue
+ self.doc_index_queue = doc_index_queue
+
+ def create_signature(self, page):
+ """基于页面链接与内容生成签名。"""
+ ...
+
+ def crawl_page(self, page):
+ for url in page.child_urls:
+ self.data_store.add_link_to_crawl(url)
+ page.signature = self.create_signature(page)
+ self.data_store.remove_link_to_crawl(page.url)
+ self.data_store.insert_crawled_link(page.url, page.signature)
+
+ def crawl(self):
+ while True:
+ page = self.data_store.extract_max_priority_page()
+ if page is None:
+ break
+ if self.data_store.crawled_similar(page.signature):
+ self.data_store.reduce_priority_link_to_crawl(page.url)
+ else:
+ self.crawl_page(page)
+```
+
+### 处理重复内容
+
+我们要谨防网页爬虫陷入死循环,这通常会发生在爬虫路径中存在环的情况。
+
+**向面试官了解你需要写多少代码**.
+
+删除重复链接:
+
+* 假设数据量较小,我们可以用类似于 `sort | unique` 的方法。(译注: 先排序,后去重)
+* 假设有 10 亿条数据,我们应该使用 **MapReduce** 来输出只出现 1 次的记录。
+
+```python
+class RemoveDuplicateUrls(MRJob):
+
+ def mapper(self, _, line):
+ yield line, 1
+
+ def reducer(self, key, values):
+ total = sum(values)
+ if total == 1:
+ yield key, total
+```
+
+比起处理重复内容,检测重复内容更为复杂。我们可以基于网页内容生成签名,然后对比两者签名的相似度。可能会用到的算法有 [Jaccard index](https://en.wikipedia.org/wiki/Jaccard_index) 以及 [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity)。
+
+### 抓取结果更新策略
+
+要定期重新抓取页面以确保新鲜度。抓取结果应该有个 `timestamp` 字段记录上一次页面抓取时间。每隔一段时间,比如说 1 周,所有页面都需要更新一次。对于热门网站或是内容频繁更新的网站,爬虫抓取间隔可以缩短。
+
+尽管我们不会深入网页数据分析的细节,我们仍然要做一些数据挖掘工作来确定一个页面的平均更新时间,并且根据相关的统计数据来决定爬虫的重新抓取频率。
+
+当然我们也应该根据站长提供的 `Robots.txt` 来控制爬虫的抓取频率。
+
+### 用例:用户输入搜索词后,可以看到相关的搜索结果列表,列表每一项都包含由网页爬虫生成的页面标题及摘要
+
+* **客户端**向运行[反向代理](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)的 **Web 服务器**发送一个请求
+* **Web 服务器** 发送请求到 **Query API** 服务器
+* **查询 API** 服务将会做这些事情:
+ * 解析查询参数
+ * 删除 HTML 标记
+ * 将文本分割成词组 (译注: 分词处理)
+ * 修正错别字
+ * 规范化大小写
+ * 将搜索词转换为布尔运算
+ * 使用**倒排索引服务**来查找匹配查询的文档
+ * **倒排索引服务**对匹配到的结果进行排名,然后返回最符合的结果
+ * 使用**文档服务**返回文章标题与摘要
+
+我们使用 [**REST API**](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest) 与客户端通信:
+
+```
+$ curl https://search.com/api/v1/search?query=hello+world
+```
+
+响应内容:
+
+```
+{
+ "title": "foo's title",
+ "snippet": "foo's snippet",
+ "link": "https://foo.com",
+},
+{
+ "title": "bar's title",
+ "snippet": "bar's snippet",
+ "link": "https://bar.com",
+},
+{
+ "title": "baz's title",
+ "snippet": "baz's snippet",
+ "link": "https://baz.com",
+},
+```
+
+对于服务器内部通信,我们可以使用 [远程过程调用协议(RPC)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)
+
+
+## 第四步:架构扩展
+
+> 根据限制条件,找到并解决瓶颈。
+
+
+
+**重要提示:不要直接从最初设计跳到最终设计!**
+
+现在你要 1) **基准测试、负载测试**。2) **分析、描述**性能瓶颈。3) 在解决瓶颈问题的同时,评估替代方案、权衡利弊。4) 重复以上步骤。请阅读[设计一个系统,并将其扩大到为数以百万计的 AWS 用户服务](../scaling_aws/README.md) 来了解如何逐步扩大初始设计。
+
+讨论初始设计可能遇到的瓶颈及相关解决方案是很重要的。例如加上一套配备多台 **Web 服务器**的**负载均衡器**是否能够解决问题?**CDN**呢?**主从复制**呢?它们各自的替代方案和需要**权衡**的利弊又有哪些呢?
+
+我们将会介绍一些组件来完成设计,并解决架构规模扩张问题。内置的负载均衡器将不做讨论以节省篇幅。
+
+**为了避免重复讨论**,请参考[系统设计主题索引](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#系统设计主题的索引)相关部分来了解其要点、方案的权衡取舍以及替代方案。
+
+* [DNS](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#域名系统)
+* [负载均衡器](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#负载均衡器)
+* [水平扩展](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#水平扩展)
+* [Web 服务器(反向代理)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#反向代理web-服务器)
+* [API 服务器(应用层)](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用层)
+* [缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存)
+* [NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#nosql)
+* [一致性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#一致性模式)
+* [可用性模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#可用性模式)
+
+有些搜索词非常热门,有些则非常冷门。热门的搜索词可以通过诸如 Redis 或者 Memcached 之类的**内存缓存**来缩短响应时间,避免**倒排索引服务**以及**文档服务**过载。**内存缓存**同样适用于流量分布不均匀以及流量短时高峰问题。从内存中读取 1 MB 连续数据大约需要 250 微秒,而从 SSD 读取同样大小的数据要花费 4 倍的时间,从机械硬盘读取需要花费 80 倍以上的时间。1
+
+
+以下是优化**爬虫服务**的其他建议:
+
+* 为了处理数据大小问题以及网络请求负载,**倒排索引服务**和**文档服务**可能需要大量应用数据分片和数据复制。
+* DNS 查询可能会成为瓶颈,**爬虫服务**最好专门维护一套定期更新的 DNS 查询服务。
+* 借助于[连接池](https://en.wikipedia.org/wiki/Connection_pool),即同时维持多个开放网络连接,可以提升**爬虫服务**的性能并减少内存使用量。
+ * 改用 [UDP](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#用户数据报协议udp) 协议同样可以提升性能
+* 网络爬虫受带宽影响较大,请确保带宽足够维持高吞吐量。
+
+## 其它要点
+
+> 是否深入这些额外的主题,取决于你的问题范围和剩下的时间。
+
+### SQL 扩展模式
+
+* [读取复制](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#主从复制)
+* [联合](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#联合)
+* [分片](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#分片)
+* [非规范化](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#非规范化)
+* [SQL 调优](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-调优)
+
+#### NoSQL
+
+* [键-值存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#键-值存储)
+* [文档类型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#文档类型存储)
+* [列型存储](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#列型存储)
+* [图数据库](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#图数据库)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#sql-还是-nosql)
+
+
+### 缓存
+
+* 在哪缓存
+ * [客户端缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#客户端缓存)
+ * [CDN 缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#cdn-缓存)
+ * [Web 服务器缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#web-服务器缓存)
+ * [数据库缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库缓存)
+ * [应用缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#应用缓存)
+* 什么需要缓存
+ * [数据库查询级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#数据库查询级别的缓存)
+ * [对象级别的缓存](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#对象级别的缓存)
+* 何时更新缓存
+ * [缓存模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#缓存模式)
+ * [直写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#直写模式)
+ * [回写模式](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#回写模式)
+ * [刷新](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#刷新)
+
+### 异步与微服务
+
+* [消息队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#消息队列)
+* [任务队列](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#任务队列)
+* [背压](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#背压)
+* [微服务](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#微服务)
+
+### 通信
+
+* 可权衡选择的方案:
+ * 与客户端的外部通信 - [使用 REST 作为 HTTP API](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#表述性状态转移rest)
+ * 内部通信 - [RPC](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#远程过程调用协议rpc)
+* [服务发现](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#服务发现)
+
+
+### 安全性
+
+请参阅[安全](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#安全)。
+
+
+### 延迟数值
+
+请参阅[每个程序员都应该知道的延迟数](https://github.com/donnemartin/system-design-primer/blob/master/README-zh-Hans.md#每个程序员都应该知道的延迟数)。
+
+### 持续探讨
+
+* 持续进行基准测试并监控你的系统,以解决他们提出的瓶颈问题。
+* 架构扩展是一个迭代的过程。
diff --git a/website/solutions/system_design/web_crawler/README.md b/website/solutions/system_design/web_crawler/README.md
new file mode 100644
index 00000000000..e6e79ad2245
--- /dev/null
+++ b/website/solutions/system_design/web_crawler/README.md
@@ -0,0 +1,353 @@
+# Design a web crawler
+
+*Note: This document links directly to relevant areas found in the [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) to avoid duplication. Refer to the linked content for general talking points, tradeoffs, and alternatives.*
+
+## Step 1: Outline use cases and constraints
+
+> Gather requirements and scope the problem.
+> Ask questions to clarify use cases and constraints.
+> Discuss assumptions.
+
+Without an interviewer to address clarifying questions, we'll define some use cases and constraints.
+
+### Use cases
+
+#### We'll scope the problem to handle only the following use cases
+
+* **Service** crawls a list of urls:
+ * Generates reverse index of words to pages containing the search terms
+ * Generates titles and snippets for pages
+ * Title and snippets are static, they do not change based on search query
+* **User** inputs a search term and sees a list of relevant pages with titles and snippets the crawler generated
+ * Only sketch high level components and interactions for this use case, no need to go into depth
+* **Service** has high availability
+
+#### Out of scope
+
+* Search analytics
+* Personalized search results
+* Page rank
+
+### Constraints and assumptions
+
+#### State assumptions
+
+* Traffic is not evenly distributed
+ * Some searches are very popular, while others are only executed once
+* Support only anonymous users
+* Generating search results should be fast
+* The web crawler should not get stuck in an infinite loop
+ * We get stuck in an infinite loop if the graph contains a cycle
+* 1 billion links to crawl
+ * Pages need to be crawled regularly to ensure freshness
+ * Average refresh rate of about once per week, more frequent for popular sites
+ * 4 billion links crawled each month
+ * Average stored size per web page: 500 KB
+ * For simplicity, count changes the same as new pages
+* 100 billion searches per month
+
+Exercise the use of more traditional systems - don't use existing systems such as [solr](http://lucene.apache.org/solr/) or [nutch](http://nutch.apache.org/).
+
+#### Calculate usage
+
+**Clarify with your interviewer if you should run back-of-the-envelope usage calculations.**
+
+* 2 PB of stored page content per month
+ * 500 KB per page * 4 billion links crawled per month
+ * 72 PB of stored page content in 3 years
+* 1,600 write requests per second
+* 40,000 search requests per second
+
+Handy conversion guide:
+
+* 2.5 million seconds per month
+* 1 request per second = 2.5 million requests per month
+* 40 requests per second = 100 million requests per month
+* 400 requests per second = 1 billion requests per month
+
+## Step 2: Create a high level design
+
+> Outline a high level design with all important components.
+
+
+
+## Step 3: Design core components
+
+> Dive into details for each core component.
+
+### Use case: Service crawls a list of urls
+
+We'll assume we have an initial list of `links_to_crawl` ranked initially based on overall site popularity. If this is not a reasonable assumption, we can seed the crawler with popular sites that link to outside content such as [Yahoo](https://www.yahoo.com/), [DMOZ](http://www.dmoz.org/), etc.
+
+We'll use a table `crawled_links` to store processed links and their page signatures.
+
+We could store `links_to_crawl` and `crawled_links` in a key-value **NoSQL Database**. For the ranked links in `links_to_crawl`, we could use [Redis](https://redis.io/) with sorted sets to maintain a ranking of page links. We should discuss the [use cases and tradeoffs between choosing SQL or NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql).
+
+* The **Crawler Service** processes each page link by doing the following in a loop:
+ * Takes the top ranked page link to crawl
+ * Checks `crawled_links` in the **NoSQL Database** for an entry with a similar page signature
+ * If we have a similar page, reduces the priority of the page link
+ * This prevents us from getting into a cycle
+ * Continue
+ * Else, crawls the link
+ * Adds a job to the **Reverse Index Service** queue to generate a [reverse index](https://en.wikipedia.org/wiki/Search_engine_indexing)
+ * Adds a job to the **Document Service** queue to generate a static title and snippet
+ * Generates the page signature
+ * Removes the link from `links_to_crawl` in the **NoSQL Database**
+ * Inserts the page link and signature to `crawled_links` in the **NoSQL Database**
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+`PagesDataStore` is an abstraction within the **Crawler Service** that uses the **NoSQL Database**:
+
+```python
+class PagesDataStore(object):
+
+ def __init__(self, db);
+ self.db = db
+ ...
+
+ def add_link_to_crawl(self, url):
+ """Add the given link to `links_to_crawl`."""
+ ...
+
+ def remove_link_to_crawl(self, url):
+ """Remove the given link from `links_to_crawl`."""
+ ...
+
+ def reduce_priority_link_to_crawl(self, url)
+ """Reduce the priority of a link in `links_to_crawl` to avoid cycles."""
+ ...
+
+ def extract_max_priority_page(self):
+ """Return the highest priority link in `links_to_crawl`."""
+ ...
+
+ def insert_crawled_link(self, url, signature):
+ """Add the given link to `crawled_links`."""
+ ...
+
+ def crawled_similar(self, signature):
+ """Determine if we've already crawled a page matching the given signature"""
+ ...
+```
+
+`Page` is an abstraction within the **Crawler Service** that encapsulates a page, its contents, child urls, and signature:
+
+```python
+class Page(object):
+
+ def __init__(self, url, contents, child_urls, signature):
+ self.url = url
+ self.contents = contents
+ self.child_urls = child_urls
+ self.signature = signature
+```
+
+`Crawler` is the main class within **Crawler Service**, composed of `Page` and `PagesDataStore`.
+
+```python
+class Crawler(object):
+
+ def __init__(self, data_store, reverse_index_queue, doc_index_queue):
+ self.data_store = data_store
+ self.reverse_index_queue = reverse_index_queue
+ self.doc_index_queue = doc_index_queue
+
+ def create_signature(self, page):
+ """Create signature based on url and contents."""
+ ...
+
+ def crawl_page(self, page):
+ for url in page.child_urls:
+ self.data_store.add_link_to_crawl(url)
+ page.signature = self.create_signature(page)
+ self.data_store.remove_link_to_crawl(page.url)
+ self.data_store.insert_crawled_link(page.url, page.signature)
+
+ def crawl(self):
+ while True:
+ page = self.data_store.extract_max_priority_page()
+ if page is None:
+ break
+ if self.data_store.crawled_similar(page.signature):
+ self.data_store.reduce_priority_link_to_crawl(page.url)
+ else:
+ self.crawl_page(page)
+```
+
+### Handling duplicates
+
+We need to be careful the web crawler doesn't get stuck in an infinite loop, which happens when the graph contains a cycle.
+
+**Clarify with your interviewer how much code you are expected to write**.
+
+We'll want to remove duplicate urls:
+
+* For smaller lists we could use something like `sort | unique`
+* With 1 billion links to crawl, we could use **MapReduce** to output only entries that have a frequency of 1
+
+```python
+class RemoveDuplicateUrls(MRJob):
+
+ def mapper(self, _, line):
+ yield line, 1
+
+ def reducer(self, key, values):
+ total = sum(values)
+ if total == 1:
+ yield key, total
+```
+
+Detecting duplicate content is more complex. We could generate a signature based on the contents of the page and compare those two signatures for similarity. Some potential algorithms are [Jaccard index](https://en.wikipedia.org/wiki/Jaccard_index) and [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity).
+
+### Determining when to update the crawl results
+
+Pages need to be crawled regularly to ensure freshness. Crawl results could have a `timestamp` field that indicates the last time a page was crawled. After a default time period, say one week, all pages should be refreshed. Frequently updated or more popular sites could be refreshed in shorter intervals.
+
+Although we won't dive into details on analytics, we could do some data mining to determine the mean time before a particular page is updated, and use that statistic to determine how often to re-crawl the page.
+
+We might also choose to support a `Robots.txt` file that gives webmasters control of crawl frequency.
+
+### Use case: User inputs a search term and sees a list of relevant pages with titles and snippets
+
+* The **Client** sends a request to the **Web Server**, running as a [reverse proxy](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* The **Web Server** forwards the request to the **Query API** server
+* The **Query API** server does the following:
+ * Parses the query
+ * Removes markup
+ * Breaks up the text into terms
+ * Fixes typos
+ * Normalizes capitalization
+ * Converts the query to use boolean operations
+ * Uses the **Reverse Index Service** to find documents matching the query
+ * The **Reverse Index Service** ranks the matching results and returns the top ones
+ * Uses the **Document Service** to return titles and snippets
+
+We'll use a public [**REST API**](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest):
+
+```
+$ curl https://search.com/api/v1/search?query=hello+world
+```
+
+Response:
+
+```
+{
+ "title": "foo's title",
+ "snippet": "foo's snippet",
+ "link": "https://foo.com",
+},
+{
+ "title": "bar's title",
+ "snippet": "bar's snippet",
+ "link": "https://bar.com",
+},
+{
+ "title": "baz's title",
+ "snippet": "baz's snippet",
+ "link": "https://baz.com",
+},
+```
+
+For internal communications, we could use [Remote Procedure Calls](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc).
+
+## Step 4: Scale the design
+
+> Identify and address bottlenecks, given the constraints.
+
+
+
+**Important: Do not simply jump right into the final design from the initial design!**
+
+State you would 1) **Benchmark/Load Test**, 2) **Profile** for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat. See [Design a system that scales to millions of users on AWS](../scaling_aws/README.md) as a sample on how to iteratively scale the initial design.
+
+It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them. For example, what issues are addressed by adding a **Load Balancer** with multiple **Web Servers**? **CDN**? **Master-Slave Replicas**? What are the alternatives and **Trade-Offs** for each?
+
+We'll introduce some components to complete the design and to address scalability issues. Internal load balancers are not shown to reduce clutter.
+
+*To avoid repeating discussions*, refer to the following [system design topics](https://github.com/donnemartin/system-design-primer#index-of-system-design-topics) for main talking points, tradeoffs, and alternatives:
+
+* [DNS](https://github.com/donnemartin/system-design-primer#domain-name-system)
+* [Load balancer](https://github.com/donnemartin/system-design-primer#load-balancer)
+* [Horizontal scaling](https://github.com/donnemartin/system-design-primer#horizontal-scaling)
+* [Web server (reverse proxy)](https://github.com/donnemartin/system-design-primer#reverse-proxy-web-server)
+* [API server (application layer)](https://github.com/donnemartin/system-design-primer#application-layer)
+* [Cache](https://github.com/donnemartin/system-design-primer#cache)
+* [NoSQL](https://github.com/donnemartin/system-design-primer#nosql)
+* [Consistency patterns](https://github.com/donnemartin/system-design-primer#consistency-patterns)
+* [Availability patterns](https://github.com/donnemartin/system-design-primer#availability-patterns)
+
+Some searches are very popular, while others are only executed once. Popular queries can be served from a **Memory Cache** such as Redis or Memcached to reduce response times and to avoid overloading the **Reverse Index Service** and **Document Service**. The **Memory Cache** is also useful for handling the unevenly distributed traffic and traffic spikes. Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.1
+
+Below are a few other optimizations to the **Crawling Service**:
+
+* To handle the data size and request load, the **Reverse Index Service** and **Document Service** will likely need to make heavy use sharding and federation.
+* DNS lookup can be a bottleneck, the **Crawler Service** can keep its own DNS lookup that is refreshed periodically
+* The **Crawler Service** can improve performance and reduce memory usage by keeping many open connections at a time, referred to as [connection pooling](https://en.wikipedia.org/wiki/Connection_pool)
+ * Switching to [UDP](https://github.com/donnemartin/system-design-primer#user-datagram-protocol-udp) could also boost performance
+* Web crawling is bandwidth intensive, ensure there is enough bandwidth to sustain high throughput
+
+## Additional talking points
+
+> Additional topics to dive into, depending on the problem scope and time remaining.
+
+### SQL scaling patterns
+
+* [Read replicas](https://github.com/donnemartin/system-design-primer#master-slave-replication)
+* [Federation](https://github.com/donnemartin/system-design-primer#federation)
+* [Sharding](https://github.com/donnemartin/system-design-primer#sharding)
+* [Denormalization](https://github.com/donnemartin/system-design-primer#denormalization)
+* [SQL Tuning](https://github.com/donnemartin/system-design-primer#sql-tuning)
+
+#### NoSQL
+
+* [Key-value store](https://github.com/donnemartin/system-design-primer#key-value-store)
+* [Document store](https://github.com/donnemartin/system-design-primer#document-store)
+* [Wide column store](https://github.com/donnemartin/system-design-primer#wide-column-store)
+* [Graph database](https://github.com/donnemartin/system-design-primer#graph-database)
+* [SQL vs NoSQL](https://github.com/donnemartin/system-design-primer#sql-or-nosql)
+
+### Caching
+
+* Where to cache
+ * [Client caching](https://github.com/donnemartin/system-design-primer#client-caching)
+ * [CDN caching](https://github.com/donnemartin/system-design-primer#cdn-caching)
+ * [Web server caching](https://github.com/donnemartin/system-design-primer#web-server-caching)
+ * [Database caching](https://github.com/donnemartin/system-design-primer#database-caching)
+ * [Application caching](https://github.com/donnemartin/system-design-primer#application-caching)
+* What to cache
+ * [Caching at the database query level](https://github.com/donnemartin/system-design-primer#caching-at-the-database-query-level)
+ * [Caching at the object level](https://github.com/donnemartin/system-design-primer#caching-at-the-object-level)
+* When to update the cache
+ * [Cache-aside](https://github.com/donnemartin/system-design-primer#cache-aside)
+ * [Write-through](https://github.com/donnemartin/system-design-primer#write-through)
+ * [Write-behind (write-back)](https://github.com/donnemartin/system-design-primer#write-behind-write-back)
+ * [Refresh ahead](https://github.com/donnemartin/system-design-primer#refresh-ahead)
+
+### Asynchronism and microservices
+
+* [Message queues](https://github.com/donnemartin/system-design-primer#message-queues)
+* [Task queues](https://github.com/donnemartin/system-design-primer#task-queues)
+* [Back pressure](https://github.com/donnemartin/system-design-primer#back-pressure)
+* [Microservices](https://github.com/donnemartin/system-design-primer#microservices)
+
+### Communications
+
+* Discuss tradeoffs:
+ * External communication with clients - [HTTP APIs following REST](https://github.com/donnemartin/system-design-primer#representational-state-transfer-rest)
+ * Internal communications - [RPC](https://github.com/donnemartin/system-design-primer#remote-procedure-call-rpc)
+* [Service discovery](https://github.com/donnemartin/system-design-primer#service-discovery)
+
+### Security
+
+Refer to the [security section](https://github.com/donnemartin/system-design-primer#security).
+
+### Latency numbers
+
+See [Latency numbers every programmer should know](https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know).
+
+### Ongoing
+
+* Continue benchmarking and monitoring your system to address bottlenecks as they come up
+* Scaling is an iterative process
diff --git a/website/solutions/system_design/web_crawler/__init__.py b/website/solutions/system_design/web_crawler/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/website/solutions/system_design/web_crawler/web_crawler.graffle b/website/solutions/system_design/web_crawler/web_crawler.graffle
new file mode 100644
index 00000000000..831e02fd363
Binary files /dev/null and b/website/solutions/system_design/web_crawler/web_crawler.graffle differ
diff --git a/website/solutions/system_design/web_crawler/web_crawler.png b/website/solutions/system_design/web_crawler/web_crawler.png
new file mode 100644
index 00000000000..da55126125b
Binary files /dev/null and b/website/solutions/system_design/web_crawler/web_crawler.png differ
diff --git a/website/solutions/system_design/web_crawler/web_crawler_basic.graffle b/website/solutions/system_design/web_crawler/web_crawler_basic.graffle
new file mode 100644
index 00000000000..7d466eb227f
Binary files /dev/null and b/website/solutions/system_design/web_crawler/web_crawler_basic.graffle differ
diff --git a/website/solutions/system_design/web_crawler/web_crawler_basic.png b/website/solutions/system_design/web_crawler/web_crawler_basic.png
new file mode 100644
index 00000000000..6f2cb517130
Binary files /dev/null and b/website/solutions/system_design/web_crawler/web_crawler_basic.png differ
diff --git a/website/solutions/system_design/web_crawler/web_crawler_mapreduce.py b/website/solutions/system_design/web_crawler/web_crawler_mapreduce.py
new file mode 100644
index 00000000000..8a5f808779a
--- /dev/null
+++ b/website/solutions/system_design/web_crawler/web_crawler_mapreduce.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+from mrjob.job import MRJob
+
+
+class RemoveDuplicateUrls(MRJob):
+
+ def mapper(self, _, line):
+ yield line, 1
+
+ def reducer(self, key, values):
+ total = sum(values)
+ if total == 1:
+ yield key, total
+
+ def steps(self):
+ """Run the map and reduce steps."""
+ return [
+ self.mr(mapper=self.mapper,
+ reducer=self.reducer)
+ ]
+
+
+if __name__ == '__main__':
+ RemoveDuplicateUrls.run()
diff --git a/website/solutions/system_design/web_crawler/web_crawler_snippets.py b/website/solutions/system_design/web_crawler/web_crawler_snippets.py
new file mode 100644
index 00000000000..d84a25362ec
--- /dev/null
+++ b/website/solutions/system_design/web_crawler/web_crawler_snippets.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+
+class PagesDataStore(object):
+
+ def __init__(self, db):
+ self.db = db
+ pass
+
+ def add_link_to_crawl(self, url):
+ """Add the given link to `links_to_crawl`."""
+ pass
+
+ def remove_link_to_crawl(self, url):
+ """Remove the given link from `links_to_crawl`."""
+ pass
+
+ def reduce_priority_link_to_crawl(self, url):
+ """Reduce the priority of a link in `links_to_crawl` to avoid cycles."""
+ pass
+
+ def extract_max_priority_page(self):
+ """Return the highest priority link in `links_to_crawl`."""
+ pass
+
+ def insert_crawled_link(self, url, signature):
+ """Add the given link to `crawled_links`."""
+ pass
+
+ def crawled_similar(self, signature):
+ """Determine if we've already crawled a page matching the given signature"""
+ pass
+
+
+class Page(object):
+
+ def __init__(self, url, contents, child_urls):
+ self.url = url
+ self.contents = contents
+ self.child_urls = child_urls
+ self.signature = self.create_signature()
+
+ def create_signature(self):
+ # Create signature based on url and contents
+ pass
+
+
+class Crawler(object):
+
+ def __init__(self, pages, data_store, reverse_index_queue, doc_index_queue):
+ self.pages = pages
+ self.data_store = data_store
+ self.reverse_index_queue = reverse_index_queue
+ self.doc_index_queue = doc_index_queue
+
+ def crawl_page(self, page):
+ for url in page.child_urls:
+ self.data_store.add_link_to_crawl(url)
+ self.reverse_index_queue.generate(page)
+ self.doc_index_queue.generate(page)
+ self.data_store.remove_link_to_crawl(page.url)
+ self.data_store.insert_crawled_link(page.url, page.signature)
+
+ def crawl(self):
+ while True:
+ page = self.data_store.extract_max_priority_page()
+ if page is None:
+ break
+ if self.data_store.crawled_similar(page.signature):
+ self.data_store.reduce_priority_link_to_crawl(page.url)
+ else:
+ self.crawl_page(page)
+ page = self.data_store.extract_max_priority_page()
diff --git a/website/solutions/viewer.html b/website/solutions/viewer.html
new file mode 100644
index 00000000000..7d254dc3fa4
--- /dev/null
+++ b/website/solutions/viewer.html
@@ -0,0 +1,1043 @@
+
+
+
+
+
+ Notebook Viewer — System Design Primer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+