Bài 01 21/09/2026

Ghi Chép Kiến Trúc Phần Mềm (21/09/2026)

Nội dung nguyên bản chi tiết từ note-9-21.md.

1. Thiết Kế Kiến Trúc Cho Khởi Đầu (500 Users/Ngày)

Câu hỏi: Nếu ứng dụng hiện chỉ có 500 người dùng/ngày, có nên xây Kubernetes + 20 microservices ngay không? Vì sao?

Phân tích theo hướng: Problem ➔ Trade-off ➔ Decision

Problem (Bài toán thực tế):

  • Lưu lượng truy cập rất nhỏ (~500 DAU), không đòi hỏi hạ tầng phức tạp hay khả năng xử lý hàng triệu request/giây.
  • Nguồn lực team (nhân sự, thời gian, ngân sách) thường hạn chế ở giai đoạn đầu.
  • Nhu cầu cốt lõi là tốc độ phát triển tính năng (Feature Velocity) và kiểm chứng sản phẩm (Product-Market Fit).

Trade-off (Đánh đổi khi dùng Microservices + Kubernetes quá sớm):

Đổi lấy: Khả năng scale độc lập từng service và cách ly lỗi (nếu có).

Phải trả giá (Overengineering Overhead):
  • Phức tạp vận hành (DevOps Burden): Quản lý cluster Kubernetes, CI/CD pipelines cho 20 services, service mesh, distributed tracing, central logging.
  • Độ trễ & Phức tạp mạng (Network Latency & Distributed Complexity): Giao tiếp qua mạng giữa 20 services dẫn tới latency, rủi ro partial failure, quản lý distributed transactions (Saga Pattern/2PC).
  • Chi phí hạ tầng cao: 20 microservices + Kubernetes cluster yêu cầu nhiều tài nguyên phần cứng nền tảng (control plane, ingress, monitoring tools) ngay cả khi không có traffic.
  • Chậm tốc độ phát triển: Thay vì tập trung viết logic sản phẩm, team mất phần lớn thời gian cấu hình Helm, Dockerfile, NetworkPolicies, gRPC/REST clients.

Decision (Quyết định & Đề xuất kiến trúc):

KHÔNG NÊN triển khai Kubernetes + 20 microservices ngay từ đầu.

Giải pháp tối ưu:

  • Bắt đầu với kiến trúc Monolith hoặc Modular Monolith.
  • Triển khai trên hạ tầng đơn giản (PaaS như Render/Fly.io, hoặc VPS với Docker Compose).
  • Thiết kế codebase theo dạng mô-đun rõ ràng để sẵn sàng tách thành Microservices khi hệ thống thực sự chạm giới hạn scale hoặc khi quy mô team tăng lên.

2. Phân Biệt Scale Up & Scale Out

Tiêu chí Scale Up (Vertical Scaling) Scale Out (Horizontal Scaling)
Khái niệm Nâng cấp tài nguyên phần cứng (CPU, RAM, SSD) của máy chủ hiện tại. Thêm nhiều máy chủ mới vào hệ thống để cùng chia sẻ tải qua Load Balancer.
Giới hạn Có giới hạn trần phần cứng (Hardware limit & Vendor limits). Về mặt lý thuyết là không giới hạn (Unlimited scaling).
Độ phức tạp kiến trúc Đơn giản, không cần thay đổi code hay kiến trúc ứng dụng. Phức tạp hơn, cần thiết kế hệ thống hỗ trợ phân tán, stateless, chia tải.
Độ tin cậy (Availability) Single Point of Failure (SPOF): Khi máy chủ chết, toàn bộ hệ thống sập. High Availability (HA): Một máy chủ chết, các node còn lại vẫn gánh tải.
Downtime khi nâng cấp Thường yêu cầu downtime để thay thế/nâng cấp phần cứng. Không cần downtime (Zero-downtime deployment, rolling updates).

3. Tại Sao Application Server Scale Out Dễ Hơn Relational Database (RDBMS)?

1. Tính Chất Stateless vs. Stateful

Application Server (Stateless):
  • Không lưu giữ trạng thái phiên làm việc (session status) hay dữ liệu lâu dài trong bộ nhớ cục bộ.
  • Mọi thông tin trạng thái được đẩy ra bên ngoài (ví dụ: Session lưu ở Redis/Memcached, Data lưu ở Database).
  • ➔ Hệ quả: Có thể nhân bản (duplicate) thêm 10 hay 100 App Nodes phía sau Load Balancer mà không sợ sai lệch trạng thái.
Relational Database (Stateful):
  • Lưu trữ trạng thái và dữ liệu cốt lõi của toàn bộ ứng dụng trên đĩa cứng/bộ nhớ.
  • Mọi thao tác ghi (Write/Update/Delete) phải đảm bảo tính nhất quán (Consistency).

2. Thách Thức Đồng Bộ Dữ Liệu & Ràng Buộc ACID

Tính nhất quán dữ liệu (ACID Compliance):

  • Replication Lag: Việc đồng bộ dữ liệu giữa các node đọc (Read Replicas) và node ghi (Master) luôn có độ trễ.
  • Distributed Transactions: Đảm bảo ACID trên nhiều máy chủ đòi hỏi các giao thức đồng thuận phức tạp (2-Phase Commit, Paxos, Raft) làm giảm đáng kể hiệu năng ghi.

Phân mảnh dữ liệu (Sharding):

Khi dữ liệu quá lớn phải chia nhỏ ra nhiều database node (Sharding), việc thực hiện các truy vấn phức tạp như JOIN, GROUP BY, hoặc đánh INDEX trên nhiều shard trở nên cực kỳ khó khăn và tốn chi phí tính toán.

4. Tách Database Sang Máy Riêng Giúp Reliability, Nhưng Tại Sao Database Vẫn Có Thể Là Single Point of Failure (SPOF)?

Câu hỏi: Tách database sang máy riêng giúp tăng tính tin cậy (reliability), nhưng tại sao database vẫn có thể là Single Point of Failure (SPOF)?

Phân Tích Chi Tiết:

1. Bản chất của Single Instance Database:

Dù đã tách khỏi App Server, nếu hệ thống chỉ có 1 máy chủ Database duy nhất, toàn bộ ứng dụng vẫn phụ thuộc hoàn toàn vào máy chủ này. Nếu máy chủ DB gặp sự cố (hỏng phần cứng ổ đĩa, tràn bộ nhớ RAM, sập OS, đứt kết nối mạng), App Servers dù chạy hàng trăm node cũng không thể đọc/ghi dữ liệu, dẫn tới toàn bộ hệ thống ngưng hoạt động.

2. Nút thắt tài nguyên & Kết nối (Connection Exhaustion):

Khi App Server Scale Out thành nhiều instance, tổng số lượng kết nối (connection pool) gửi tới 1 DB Server đơn lẻ tăng vọt. DB Server bị nghẽn CPU/IOPS hoặc cạn kết nối (Too many connections), gây đứt gãy dây chuyền (Cascading Failure).

3. Thiếu cơ chế Chuyển vùng Tự động (Failover):

Reliability tăng lên do tính rành mạch (isolation) giữa App và Data, nhưng Availability (Tính sẵn sàng) vẫn bằng 0 nếu không có DB dự phòng (Standby/Replica) kèm cơ chế Auto-Failover (như Patroni, Keepalived, AWS Multi-AZ RDS).

Giải pháp triệt tiêu SPOF cho Database:

  • Triển khai mô hình Primary-Replica (Master-Slave): 1 Node Master đảm nhận việc Ghi (Write), nhiều Node Replica đảm nhận việc Đọc (Read).
  • Tích hợp Auto-Failover: Khi Master sập, hệ thống tự động bầu chọn (electorate) 1 Replica lên làm Master mới.
  • Sử dụng Connection Proxy/Pooler (PgBouncer, ProxySQL) để quản lý kết nối hiệu quả.

5. Phân Biệt Bản Chất Giữa Load Balancer Và CDN

Câu hỏi: Load Balancer và CDN đều đứng trước Server. Khác biệt bản chất giữa hai thứ là gì?

Phân Tích Bản Chất & Mục Đích Chính:
CDN (Content Delivery Network):
  • Bản chất: Mạng lưới các máy chủ bộ nhớ tạm (Edge Servers/POPs) phân bố rộng khắp thế giới.
  • Mục đích: Cung cấp dữ liệu gần người dùng nhất về mặt địa lý để giảm độ trễ (latency), tập trung vào Caching nội dung tĩnh (Static Assets: HTML, CSS, JS, Image, Video).
Load Balancer (Bộ cân bằng tải):
  • Bản chất: Đóng vai trò là Reverse Proxy ở tầng mạng (Layer 4 hoặc Layer 7 OSI) đặt tại Datagram/VPC nội bộ.
  • Mục đích: Phân phối điều hướng lưu lượng (Traffic Routing) đến các máy chủ ứng dụng (App Servers) phía sau nhằm tránh quá tải 1 server, tăng khả năng mở rộng (Scale Out) và đảm bảo tính sẵn sàng (High Availability).
Bảng So Sánh Chi Tiết:
Tiêu chí CDN (Content Delivery Network) Load Balancer (Bộ Cân Bằng Tải)
Vị trí triển khai Nằm ở rìa mạng (Edge Network - toàn cầu, gần người dùng). Nằm ở trung tâm dữ liệu (VPC / Data Center - phía trước App Servers).
Đối tượng xử lý chính Nội dung tĩnh (Static Content: .jpg, .png, .js, .css, video). Dữ liệu động & Request ứng dụng (Dynamic API Requests, Business Logic).
Cơ chế cốt lõi Caching & Geo-routing (Trả dữ liệu lưu sẵn tại Edge). Traffic Distribution Algorithms (Round Robin, Least Connections, IP Hash).
Lưu trữ dữ liệu Có lưu trữ bản sao dữ liệu (Edge Cache Storage). Không lưu dữ liệu (Chỉ chuyển tiếp traffic - Stateless proxy).
Mục tiêu ưu tiên Giảm latency người dùng, giảm băng thông cho Origin Server. Giảm tải cho từng Server, chống sập hệ thống (High Availability & Scale).

6. Bài Học Từ Case Study Stack Overflow Về Khả Năng Scale Out

Câu hỏi: Case Stack Overflow chứng minh điều gì về câu nói "phải dùng Microservices mới scale được"?

Thực Tế Tại Stack Overflow:

Stack Overflow là một trong những website thuộc top lượng truy cập toàn cầu (hàng trăm triệu lượt truy cập hàng tháng, hàng tỷ pageviews), nhưng hệ thống của họ:

  • Đẩy phần lớn ứng dụng theo kiến trúc Monolith (Monolithic Architecture) viết bằng C# (.NET Framework / .NET Core).
  • Chạy trên số lượng máy chủ rất khiêm tốn (chỉ vài chục Web Servers + một cụm SQL Server cực mạnh kèm Redis Cache và Elasticsearch).
  • Đạt độ trễ phản hồi cực thấp (thường < 15ms cho mỗi trang).
Bài Học Rút Ra:

1. Microservices KHÔNG PHẢI là con đường duy nhất để Scale:

Một hệ thống Monolith được tối ưu hóa tốt hoàn toàn có thể phục vụ lượng truy cập khổng lồ ở quy mô Web-scale mà không cần chia nhỏ thành hàng trăm microservices.

2. Hiệu năng & Tối ưu hóa quan trọng hơn Pattern Architecture:

Việc scale phụ thuộc rất lớn vào tối ưu hóa Code, Caching tầng tầng lớp lớp (Redis/In-memory), DB Indexing hợp lý, và tận dụng triệt để sức mạnh phần cứng. Chia nhỏ Microservices quá sớm mang lại độ trễ mạng (Network Latency) và Overhead quản lý, đôi khi làm chậm hệ thống so với Monolith.

3. Phân biệt Scale Traffic vs. Scale Team:

Monolith scale tốt về mặt Traffic & Performance nếu biết cách thiết kế. Còn Microservices sinh ra chủ yếu để scale về mặt Quy mô con người/Tổ chức (Team Size) — cho phép hàng trăm kỹ sư làm việc song song trên các module độc lập mà không bị dẫm chân lên nhau.

7. Thêm index luôn làm database nhanh hơn - đúng hay sai?

Nhận định này là SAI. Index giúp tăng tốc độ đọc (Read/Select) nhưng làm chậm tốc độ ghi (Write/Insert/Update/Delete).

Giải thích chi tiết:

  • Chi phí cập nhật (Write Overhead): Mỗi khi dữ liệu được thêm mới, cập nhật hoặc xóa, database phải đồng thời tính toán và cập nhật lại cây chỉ mục (B-Tree/Hash Index). Càng nhiều index, hiệu năng ghi càng giảm sút nghiêm trọng.
  • Tiêu tốn bộ nhớ (Storage & RAM Bloat): Index chiếm dung lượng đĩa đáng kể và cạnh tranh bộ nhớ đệm (buffer pool/RAM) của database, có thể đẩy các trang dữ liệu thường dùng ra ngoài.
  • Độ chọn lọc thấp (Low Cardinality): Đánh index trên cột có ít giá trị phân biệt (ví dụ: giới tính, cờ trạng thái boolean) thường không có tác dụng và bộ tối ưu hóa truy vấn (Query Optimizer) sẽ bỏ qua để quét toàn bảng (Full Table Scan).