Học cách đưa ra quyết định kiến trúc như một Senior Architect. Hiểu rằng không có giải pháp hoàn hảo, chỉ có giải pháp phù hợp. Master nghệ thuật cân bằng trade-offs trong system design.
Chia sẻ bài học
Có một câu hỏi mà mọi junior engineer đều từng hỏi tôi: "Cách tốt nhất để thiết kế hệ thống này là gì?"
Câu trả lời của tôi luôn làm họ thất vọng: "Không có 'cách tốt nhất'. Chỉ có cách phù hợp nhất với constraints của bạn."
Đó là bài học khó nhất mà mọi architect phải học: không có giải pháp hoàn hảo.
Khi tôi mới bắt đầu, tôi tin rằng với đủ thời gian và kiến thức, tôi có thể tìm ra "the perfect architecture" - một kiến trúc không có nhược điểm.
Sau 10 năm thiết kế hệ thống, tôi nhận ra một sự thật tàn khốc:
Mọi quyết định kiến trúc đều là một sự đánh đổi.
Không phải vì bạn không đủ giỏi. Mà vì trong thế giới thực, bạn luôn phải chọn giữa những thứ mâu thuẫn nhau:
Bạn không thể có tất cả. Và đó không phải là vấn đề. Đó là thực tế.
Tôi thường thấy developers nói: "Microservices là best practice" hoặc "NoSQL tốt hơn SQL".
Không. Sai hoàn toàn.
Microservices là best practice cho Netflix với 200 triệu users và 500 engineering teams. Nó là worst practice cho startup 5 người với 100 users.
NoSQL tốt cho Facebook với billions of posts không cần strict relationships. Nó là terrible choice cho banking system cần ACID transactions.
"Best practice" không tồn tại trong chân không. Context is everything.
Hãy phân tích trade-off của hai approaches này.
Monolith:
Advantages:
- Simple deployment (1 app)
- Easy debugging (1 codebase)
- No network latency giữa components
- Transactions đơn giản (same database)
- Fast development initially
Disadvantages:
- Khó scale independently
- Deploy all-or-nothing
- Team conflicts trên same codebase
- Technology lock-in
- Restart toàn bộ app khi update
Microservices:
Advantages:
- Scale independently
- Deploy independently
- Technology diversity
- Team autonomy
- Fault isolation
Disadvantages:
- Network latency
- Distributed transactions nightmare
- Complex deployment
- Monitoring complexity
- Data consistency challenges
Câu hỏi không phải là "cái nào tốt hơn?". Câu hỏi là:
"Với context hiện tại của tôi, tôi cần advantages nào và có thể chấp nhận disadvantages nào?"
Đây là framework tôi dùng để đánh giá mọi quyết định kiến trúc.
Trước khi so sánh giải pháp, hãy hiểu rõ constraints của bạn:
Technical Constraints:
Business Constraints:
Team Constraints:
Với mỗi option, viết ra rõ ràng:
Option A: PostgreSQL
Gains:
- ACID guarantees
- Rich query capabilities (JOIN, aggregations)
- Mature ecosystem
- Team đã biết
Costs:
- Vertical scaling limits
- Schema changes expensive
- Not optimal for unstructured data
Option B: MongoDB
Gains:
- Horizontal scaling easier
- Flexible schema
- Good for rapid iteration
Costs:
- No JOIN support (application-level)
- Eventual consistency complexity
- Team phải học mới
Rank priorities của project:
Priority 1: Ship fast (MVP trong 2 tháng)
Priority 2: Team productivity (ít learning curve)
Priority 3: Data integrity (financial data)
Given priorities → PostgreSQL wins
Why? Team biết, ACID critical, can scale later.
Nếu priorities thay đổi:
Priority 1: Scale to 100M users (đã có PMF)
Priority 2: Handle unstructured data (user-generated content)
Priority 3: Read performance
Given priorities → MongoDB might win
Why? Scale + flexibility outweigh JOIN limitations.
Thấy chưa? Cùng 2 options, khác priorities → khác decision.
Hãy xem một quyết định tưởng chừng đơn giản: "Có nên dùng cache không?"
Hầu hết developers nghĩ: "Cache làm nhanh hơn → Always cache!"
Sai rồi. Hãy phân tích trade-offs.
Without Cache:
User request → Query DB → Return
Advantages:
- Always fresh data (price, stock)
- Simple logic (no invalidation)
- No infrastructure overhead
Disadvantages:
- DB load cao
- Slow response (DB query mỗi request)
- Hard to scale reads
With Cache (Redis):
User request → Check Redis → If miss: Query DB → Return
Advantages:
- Fast response (< 10ms)
- Reduce DB load
- Easy to scale reads
Disadvantages:
- Stale data risk (user thấy sold-out item as available)
- Cache invalidation complexity
- Infrastructure cost (Redis server)
- Memory limits
Nếu bạn là Amazon:
→ Use cache vì:
Nếu bạn là startup nhỏ:
→ Skip cache vì:
Same problem, different context, different decision.
Bạn không thể có cả hai trong distributed system khi có network partition.
Choose Consistency (CP):
Banking, payments, inventory management
→ Đúng quan trọng hơn fast
→ OK với downtime nếu cần ensure correctness
Choose Availability (AP):
Social media feeds, view counts, recommendations
→ Fast quan trọng hơn perfect accuracy
→ Eventual consistency acceptable
Trade-off thinking:
Normalized Database:
-- 3 tables, proper 3NF
users: id, name
posts: id, user_id, content
likes: id, post_id, user_id
-- Query post with like count
SELECT p.*, COUNT(l.id) as likes
FROM posts p
LEFT JOIN likes l ON p.id = l.post_id
WHERE p.id = 123
GROUP BY p.id;
Data integrity, no duplication Slow queries (JOINs expensive)
Denormalized:
-- 1 table
posts: id, user_id, content, like_count
-- Query post with like count
SELECT * FROM posts WHERE id = 123;
Fast reads (no JOIN) Risk of inconsistency, update overhead
When to denormalize?
When to keep normalized?
Synchronous:
def upload_video(video):
save_to_s3(video) # 2s
generate_thumbnail(video) # 3s
transcode_720p(video) # 30s
send_notification() # 1s
return "Success" # User waits 36s
Simple, immediate feedback Poor UX, timeout risk
Asynchronous:
def upload_video(video):
save_to_s3(video) # 2s
queue.add_job('process_video', video.id)
return "Success" # User waits 2s
# Background worker processes rest
Better UX, handles spikes Complex, eventual processing, error handling harder
Trade-off decision:
Một trong những skills quan trọng nhất của architect: Biết khi nào dừng optimize.
Tôi từng thấy engineers spend 2 tuần optimize một query từ 50ms xuống 20ms.
Impact? Users không thấy khác biệt (human perception threshold là ~100ms).
Cost? 2 tuần dev time = features khác bị delay.
"Perfect is the enemy of good."
20% effort thường giải quyết 80% vấn đề.
Ví dụ: Optimizing API Response Time
Current: 800ms
Target: < 200ms
Option A: Add database indexes
- Effort: 2 hours
- Result: 800ms → 300ms (62% improvement)
Option B: Thêm + implement Redis caching
- Effort: 1 week
- Result: 300ms → 150ms (thêm 50% improvement)
Option C: Migrate sang Cassandra + rewrite queries
- Effort: 2 months
- Result: 150ms → 100ms (thêm 33% improvement)
Smart decision:
Requirement: User login system
Option A: JWT (Stateless)
List trade-offs:
Option B: Session (Stateful)
List trade-offs:
Viết ra reasoning của bạn. Không có đúng/sai, chỉ có phù hợp/không phù hợp.
Context:
Options:
Your task:
Không có "correct answer". Practice trade-off thinking.
Câu hỏi: Quyết định này có dễ thay đổi sau không?
High reversibility (can change easily):
→ Don't overthink. Chọn nhanh, iterate sau.
Low reversibility (expensive to change):
→ Think carefully. Cost of wrong decision cao.
High Impact, Low Effort → DO NOW
High Impact, High Effort → PLAN & PRIORITIZE
Low Impact, Low Effort → DO IF TIME
Low Impact, High Effort → DON'T DO
Ví dụ:
Câu hỏi: 6 tháng sau, decision nào tôi sẽ hối hận nhất?
Scenario: Choose tech stack cho startup MVP
Decision A: Use new trendy framework (Deno, Bun...)
Decision B: Use boring proven stack (Node.js + Express + PostgreSQL)
→ For startup MVP: Minimize regret → Choose B
Trước khi finalize bất kỳ architectural decision nào:
Nếu check được 6/7 → Bạn đang think như một architect.
Không có perfect solution, chỉ có optimal trade-off.
Mọi architectural decision là một sự đánh đổi. Công việc của architect không phải là tìm "best practice", mà là:
Câu nói yêu thích của tôi:
"There are no solutions. There are only trade-offs." — Thomas Sowell
Một senior architect giỏi không phải là người biết mọi pattern. Mà là người biết pattern nào fit với context nào, và tại sao.
Good enough, shipped, and iterated beats perfect but never launched.
Hãy bắt đầu nghĩ theo trade-offs từ hôm nay. Mỗi khi bạn nói "solution tốt nhất", hãy dừng lại và hỏi:
"Tốt nhất cho ai? Trong context nào? Tôi đang trade-off cái gì?"