Khung trả lời phỏng vấn System Design trong 45 phút
Học system design có vẻ thiên về xử lý vấn đề trong thiết kế, thay vì học một "cách thiết kế chuẩn" nhỉ? Tức là từ vấn đề ra giải pháp, rồi từ giải pháp mới ra công nghệ?
Đúng. Bạn đang chạm đúng tư duy cốt lõi của System Design. Chương này dùng một project chạy xuyên suốt: shop-app, một hệ thống e-commerce bắt đầu rất nhỏ rồi lớn dần. Bài đầu tiên không học công nghệ nào cả, mà học cách nghĩ và cách trình bày trong 45 phút phỏng vấn.
1. Bài toán: interviewer nói "Design shop-app"
Interviewer:
"Thiết kế hệ thống đặt hàng cho 1 triệu user."
Phản xạ thường gặp của ứng viên:
"Em dùng microservice, Kafka, Redis, Kubernetes,
sharding, Elasticsearch..."
Interviewer hỏi lại: "Why Kafka?". Ứng viên: "Vì Kafka scale tốt." Câu trả lời này không đủ.
2. Vấn đề: học theo danh sách công nghệ
Nếu học kiểu "hôm nay Redis, mai Kafka, rồi Load Balancer, rồi Circuit Breaker", ta sẽ thuộc một danh sách công nghệ. Nhưng khi interviewer đưa bài toán mới, ta không biết lấy cái nào ra dùng, và không giải thích được tại sao.
- Vẽ quá nhiều box mà không có lý do (over-engineering).
- Không nói được trade-off của từng lựa chọn.
- Đề lớn quá, không biết bắt đầu từ đâu, nói lan man hết giờ.
3. Giải pháp: Problem → Solution → Trade-off → Technology
Problem → phân tích nguyên nhân → Solution → Trade-off → Technology
Ví dụ một chuỗi lỗi quen thuộc trong shop-app: Order gọi Payment, Payment gọi Bank.
Bank phản hồi chậm / chết
↓
Payment phải chờ
↓
thread của Payment bị giữ
↓
request mới tiếp tục vào
↓
thread pool cạn
↓
Payment cũng chết
↓
cascade failure
Từ vấn đề, mới sinh ra giải pháp:
Bank chậm
│
├── Không muốn chờ vô hạn → Timeout
├── Bank lỗi tạm thời → Retry
├── Bank đang chết mà cứ gọi tiếp → Circuit Breaker
├── Bank quá tải → Rate Limit / Bulkhead
└── Không cần kết quả ngay → Async / Queue
Sau khi hiểu giải pháp, công nghệ mới xuất hiện:
| Vấn đề | Giải pháp | Công nghệ ví dụ |
|---|---|---|
| Dependency chậm/chết | Timeout, Retry, Circuit Breaker | Resilience4j |
| Việc không cần đồng bộ | Async processing | Kafka / RabbitMQ |
| DB bị đọc quá nhiều | Cache | Redis |
| Một server không đủ | Scale out + chia request | Load Balancer: Nginx / AWS ALB |
| Read quá nhiều | Read Replica | PostgreSQL streaming replication |
| DB quá lớn, write quá nhiều | Partition / Sharding | Citus, Vitess, app-level routing |
| Static content ở xa user | CDN | CloudFront / Cloudflare |
Bước trade-off là thứ phân biệt "biết Redis" với "biết thiết kế hệ thống":
Problem : DB read quá tải
↓
Solution : Caching
↓
Trade-off : stale data, cache invalidation, memory cost, consistency
↓
Technology : Redis
Đừng bắt đầu bằng "dùng công nghệ gì?". Hãy bắt đầu bằng "hệ thống đang có vấn đề gì?". Công nghệ là implementation của solution.
4. Khung 45 phút, từng bước
| Bước | Thời gian | Làm gì | Ví dụ với shop-app |
|---|---|---|---|
| 1. Requirements | ~5 phút | Hỏi functional + non-functional, chốt phạm vi | Xem sản phẩm, giỏ hàng, checkout; không oversell, không trừ tiền 2 lần |
| 2. Estimation | ~5 phút | QPS, read/write ratio, storage, bandwidth | 10k req/s đọc, ~100 order/s ghi lúc peak → read-heavy |
| 3. API | ~3 phút | Vài endpoint chính | GET /products/{id}, POST /cart/items, POST /checkout |
| 4. Data model | ~4 phút | Bảng/entity chính, key, index | orders(id, user_id, status, total, created_at) |
| 5. High-level design | ~8 phút | Vẽ bản đơn giản nhất chạy được | Client → App → PostgreSQL |
| 6. Deep dive | ~12 phút | Đào 1–2 phần khó nhất (interviewer thường chọn) | Checkout: Saga, idempotency, oversell |
| 7. Bottlenecks & trade-offs | ~8 phút | Cái gì hỏng khi scale ×100? SPOF? Theo dõi thế nào? | Cache, replica, rate limit, HA, observability |
Bước 1 và 2 có bài riêng ở 15.2. Không cần cứng nhắc theo đúng thứ tự, nhưng khung này giúp không bị "đề lớn quá, không biết bắt đầu từ đâu".
5. Cách kiến trúc tiến hóa (evolution)
Bắt đầu đơn giản, rồi để vấn đề kéo kiến trúc lớn dần:
Giai đoạn 1: 100 user
Client → Spring Boot → PostgreSQL
Giai đoạn 2: "traffic tăng 100 lần"
Load Balancer
│
┌────────┼────────┐
▼ ▼ ▼
App 1 App 2 App 3
│
├──────── Redis (cache, session)
├──────── Kafka (việc async)
▼
Database
/ \
Primary Replica
Giai đoạn 3: DB không chịu nổi write → partition/sharding
search chậm → Elasticsearch/OpenSearch
service phụ chết kéo Order chết → timeout/retry/circuit breaker
Với mỗi box, phải trả lời được: "Tại sao tôi cần nó? Bỏ nó đi thì vấn đề gì xảy ra?"
6. Câu trả lời mẫu (template)
1. "Trước khi vẽ, em muốn làm rõ requirement..."
→ functional: ... non-functional: ... ngoài phạm vi: ...
2. "Em ước lượng nhanh..."
→ DAU, QPS trung bình/peak, read:write, storage/năm
3. "API chính gồm..."
4. "Data model..."
5. "Bản đơn giản nhất là ..., với scale hiện tại là đủ."
6. "Điểm khó nhất là X, em đi sâu vào X..."
7. "Khi traffic ×10, bottleneck đầu tiên sẽ là ..., em xử lý bằng ...,
trade-off là ..."
8. "Để vận hành: SPOF ở đâu, HA thế nào, monitor bằng metrics/logs/traces."
Câu giải thích "Why Kafka?" đúng cách:
Notification không nằm trên critical path của checkout, nên em xử lý bất đồng bộ. Checkout publish event
OrderConfirmed, Notification Service consume và gửi email. Checkout không phải chờ email, và email chậm cũng không làm checkout chậm. Trade-off là phải xử lý message trùng (consumer idempotent) và độ trễ eventual.
7. So sánh: cách trả lời yếu và mạnh
| Yếu | Mạnh | |
|---|---|---|
| Mở đầu | Vẽ ngay | Hỏi requirement, chốt scope |
| Chọn công nghệ | Liệt kê buzzword | Nêu problem rồi mới tới technology |
| Quy mô | Thiết kế cho Google từ đầu | Bắt đầu đơn giản, scale khi có số liệu |
| Trade-off | Không nhắc | Mỗi lựa chọn đều nói cái giá |
| Vận hành | Quên | SPOF, failure scenario, observability |
8. Khi nào dùng khung này, khi nào không
- Nên: mọi đề "Design X" mở. Kể cả đề nhỏ, khung giúp bạn không quên requirement và trade-off.
- Không cứng nhắc: interviewer muốn đào ngay một phần (ví dụ "tập trung vào rate limiter") thì đi thẳng vào deep dive, chỉ hỏi requirement liên quan.
- Đừng tự động nhét mọi thứ. Internal tool 100 user mà vẽ 20 microservice, Kafka cluster, 20 shard, multi-region là over-engineering.
Vậy trong 45 phút, mình nên hỏi requirement bao nhiêu là đủ? Hỏi nhiều quá có bị đánh giá là chậm không?
Đúng là có rủi ro đó. Không cần hỏi 30 câu. Chỉ hỏi những câu làm thay đổi kiến trúc:
Traffic bao nhiêu? Read/write ratio? → cache, replica, sharding?
Có cần consistency mạnh không? → transaction, lock, Saga?
Có cần realtime không? → WebSocket, push, polling?
Failure chấp nhận thế nào? → HA, RTO/RPO
Data giữ bao lâu? → storage, archive
Mẹo: nếu interviewer trả "tùy bạn", hãy tự giả định và nói ra: "Em giả định 10 triệu DAU, read:write khoảng 100:1, nếu sai anh chỉnh giúp em." Vậy là vừa nhanh vừa thể hiện được tư duy. Tổng cộng requirement + estimation nên gói trong khoảng 10 phút.
Nếu đang vẽ dở mà interviewer nói "bây giờ traffic tăng 100 lần", mình có phải vẽ lại từ đầu không?
Không cần. Bạn hiểu gần đúng, chỉ cần chỉnh một điểm: interviewer không muốn một bản vẽ mới, họ muốn xem bạn tìm bottleneck. Làm theo vòng lặp:
Đi lần lượt từng box trên sơ đồ hiện tại
↓
"Box này chịu được ×100 không?"
↓
Không → vì sao? (CPU, connection, disk, write lock...)
↓
Giải pháp + trade-off
↓
Thêm/đổi đúng box đó trên sơ đồ
Ví dụ shop-app: app CPU 95% → scale out sau load balancer (cần stateless). DB đọc quá tải → cache + read replica (chấp nhận stale/lag). Write vẫn quá tải → sharding (chấp nhận cross-shard query khó). Mỗi bước đúng một vấn đề, một giải pháp, một cái giá.
Phỏng vấn hỏi "Bạn tiếp cận một bài system design thế nào?" thì trả lời sao?
Q1. Bạn tiếp cận một câu hỏi system design như thế nào?
🇻🇳 Tiếng Việt:
Em bắt đầu bằng làm rõ requirement, cả functional và non-functional, rồi ước lượng nhanh traffic, read/write ratio và storage. Sau đó em định nghĩa vài API chính và data model, vẽ kiến trúc đơn giản nhất chạy được. Phần còn lại em dành để đi sâu vào phần khó nhất, tìm bottleneck, SPOF, failure scenario và nói rõ trade-off của từng lựa chọn.
🇬🇧 English:
I start by clarifying functional and non-functional requirements, then do a quick estimate of traffic, read/write ratio and storage. Next I define the main APIs and the data model and sketch the simplest architecture that works. I spend the rest of the time on a deep dive into the hardest part, identifying bottlenecks, single points of failure and failure scenarios, and I explain the trade-offs of each decision.
Q2. Tại sao không dùng ngay microservices, Kafka, sharding cho chắc?
🇻🇳 Tiếng Việt:
Mỗi thành phần giải quyết một vấn đề cụ thể và đều có cái giá: thêm vận hành, thêm failure mode, thêm độ phức tạp về consistency. Em chỉ thêm khi requirement hoặc bottleneck đòi hỏi. Với scale nhỏ, một Spring Boot app và một PostgreSQL là hợp lý; khi read thành bottleneck thì mới thêm cache, read replica; khi write và data vượt một node thì mới tính sharding.
🇬🇧 English:
Every component solves a specific problem and has a cost: more operations, more failure modes and harder consistency. I only add one when a requirement or a bottleneck calls for it. At small scale a single Spring Boot app with PostgreSQL is reasonable; when reads become the bottleneck I add caching and read replicas, and only when writes and data outgrow one node do I consider sharding.
Q3. Làm sao giải thích một lựa chọn công nghệ cho thuyết phục?
🇻🇳 Tiếng Việt:
Em đi theo chuỗi problem, solution, trade-off, technology. Ví dụ: notification không nằm trên critical path của checkout, nên em xử lý async bằng event; trade-off là phải chịu duplicate message nên consumer phải idempotent; công nghệ em chọn là Kafka vì team đã vận hành nó.
🇬🇧 English:
I follow the chain problem, solution, trade-off, technology. For example, notifications are not on the critical checkout path, so I process them asynchronously with events; the trade-off is that messages can be duplicated, so consumers must be idempotent; the technology I pick is Kafka because the team already operates it.
Cách nhớ ngắn gọn
- Problem → Solution → Trade-off → Technology.
- 45 phút: Requirements → Estimation → API → Data model → High-level → Deep dive → Bottleneck/Trade-off.
- Bắt đầu đơn giản; mỗi box phải trả lời "bỏ nó thì sao?".
- Thiếu thông tin thì tự giả định và nói ra.
Tự kiểm tra
Bốn bước của tư duy system design là gì?
Problem → Solution → Trade-off → Technology. Công nghệ là implementation của solution, không phải điểm xuất phát.
Kể các bước của khung 45 phút.
Requirements, estimation, API, data model, high-level design, deep dive, bottlenecks và trade-offs (kèm SPOF, failure, observability).
"Why Kafka?" → "Vì Kafka scale tốt" sai ở đâu?
Không nêu vấn đề nó giải quyết và không nêu trade-off. Phải nói: việc gì không cần đồng bộ, vì sao tách ra, cái giá (duplicate, eventual consistency).
Interviewer nói "traffic ×100", bạn làm gì?
Đi từng box, hỏi box nào gãy trước và vì sao, rồi thêm giải pháp đúng chỗ đó kèm trade-off. Không vẽ lại từ đầu.
Khi nào thiết kế "Client → Spring Boot → PostgreSQL" là câu trả lời tốt?
Khi scale nhỏ (ví dụ internal tool 100 user). Nói rõ sẽ thêm cache/replica/sharding khi nào số liệu đòi hỏi.