Khung trả lời phỏng vấn System Design trong 45 phút

4 câu hỏi~25 phútTừ khóa: problem → solution → trade-off → technology, requirements, estimation, API, data model, high-level design, deep dive, bottleneck, over-engineering

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.

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ápCông nghệ ví dụ
Dependency chậm/chếtTimeout, Retry, Circuit BreakerResilience4j
Việc không cần đồng bộAsync processingKafka / RabbitMQ
DB bị đọc quá nhiềuCacheRedis
Một server không đủScale out + chia requestLoad Balancer: Nginx / AWS ALB
Read quá nhiềuRead ReplicaPostgreSQL streaming replication
DB quá lớn, write quá nhiềuPartition / ShardingCitus, Vitess, app-level routing
Static content ở xa userCDNCloudFront / 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ướcThời gianLàm gìVí dụ với shop-app
1. Requirements~5 phútHỏi functional + non-functional, chốt phạm viXem sản phẩm, giỏ hàng, checkout; không oversell, không trừ tiền 2 lần
2. Estimation~5 phútQPS, read/write ratio, storage, bandwidth10k req/s đọc, ~100 order/s ghi lúc peak → read-heavy
3. API~3 phútVài endpoint chínhGET /products/{id}, POST /cart/items, POST /checkout
4. Data model~4 phútBảng/entity chính, key, indexorders(id, user_id, status, total, created_at)
5. High-level design~8 phútVẽ bản đơn giản nhất chạy đượcClient → 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útCá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ếuMạnh
Mở đầuVẽ ngayHỏi requirement, chốt scope
Chọn công nghệLiệt kê buzzwordNêu problem rồi mới tới technology
Quy môThiết kế cho Google từ đầuBắt đầu đơn giản, scale khi có số liệu
Trade-offKhông nhắcMỗi lựa chọn đều nói cái giá
Vận hànhQuênSPOF, failure scenario, observability

8. Khi nào dùng khung này, khi nào không

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


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.