Chương 35. Replication, sharding và những thứ chưa nên vội
Đến đây ta đã đi qua nhiều phần của tầng dữ liệu:
- Relational database.
- Transaction.
- Cache.
- Search.
- File storage.
- Analytics.
Chương này nói về một nhóm kỹ thuật nghe rất "senior":
Replication.
Read replica.
Sharding.
Partitioning.
Database scaling.
Những từ này dễ tạo cảm giác:
Hệ thống chuyên nghiệp chắc phải dùng.
Nhưng thực tế là:
> Đây là những thứ nên hiểu sớm, nhưng không nên dùng vội.
Vì chúng giải quyết vấn đề thật.
Nhưng cũng tạo ra vấn đề mới.
Thông điệp chính của chương:
> Trước khi nghĩ đến sharding, hãy chắc rằng bạn đã hiểu hệ thống nghẽn ở đâu: đọc, ghi, dung lượng, query, index, connection, transaction, hay thiết kế dữ liệu.
---
35.1. Ví dụ dễ hiểu: một cửa hàng và nhiều quầy đọc thông tin
Hãy tưởng tượng một cửa hàng có một sổ chính.
Sổ chính ghi:
Đơn hàng.
Tồn kho.
Thanh toán.
Khách hàng.
Mọi thay đổi quan trọng phải ghi vào sổ chính.
Nhưng nhiều người chỉ cần đọc:
- Khách hỏi sản phẩm còn không.
- Nhân viên xem đơn hàng.
- Quản lý xem báo cáo.
- Website hiển thị danh sách sản phẩm.
Nếu ai cũng chen vào sổ chính để đọc, người ghi sổ sẽ bị làm phiền.
Một cách giải quyết:
Photocopy sổ chính ra vài bản phụ để người khác đọc.
Sổ chính vẫn là nguồn sự thật.
Bản phụ dùng để đọc.
Đó là ý tưởng của read replica.
---
35.2. Replication là gì?
Replication là sao chép dữ liệu từ database chính sang database khác.
Ví dụ:
Primary database
-> Replica database 1
-> Replica database 2
Primary thường nhận:
write + read quan trọng
Replica thường nhận:
read
Luồng:
App ghi đơn hàng vào primary.
Primary lưu thay đổi.
Thay đổi được copy sang replica.
App có thể đọc danh sách/báo cáo từ replica.
Mục tiêu:
- Tăng khả năng đọc.
- Giảm tải primary.
- Hỗ trợ báo cáo.
- Có bản dự phòng.
- Hỗ trợ failover trong một số cấu hình.
Replication không làm database tự nhiên vô hạn.
Nó là một công cụ cụ thể cho một nhóm vấn đề cụ thể.
---
35.3. Primary là gì?
Primary là database chính.
Nơi hệ thống ghi dữ liệu thật.
Ví dụ:
Tạo submission.
Cập nhật điểm.
Thanh toán thành công.
Đổi permission.
Tạo user.
Những thao tác này nên đi vào primary.
Primary là nơi giữ sự thật mới nhất.
Nếu có mâu thuẫn giữa primary và replica:
Primary thắng.
Trong đa số thiết kế web thông thường, hãy nghĩ đơn giản:
Write vào primary.
Read có thể vào primary hoặc replica tùy trường hợp.
---
35.4. Read replica là gì?
Read replica là bản sao dùng chủ yếu để đọc.
Ví dụ:
Primary DB:
nhận ghi submission, grading result, payment.
Read replica:
phục vụ dashboard, danh sách, report, search-like query nhẹ.
Khi traffic đọc lớn hơn traffic ghi, read replica rất hữu ích.
Ví dụ:
Một assignment có 10.000 học sinh.
Rất nhiều người mở trang xem kết quả.
Nhưng số lần ghi điểm ít hơn nhiều.
Ta có thể để một phần request đọc đi vào replica.
Primary bớt tải.
---
35.5. Read replica không giúp tăng khả năng ghi
Đây là hiểu nhầm rất phổ biến.
Nếu nghẽn vì ghi quá nhiều:
INSERT/UPDATE quá nhiều vào primary.
Thêm read replica không giải quyết gốc.
Vì write vẫn phải vào primary.
Read replica giúp khi:
Read nhiều, primary bị đọc làm nặng.
Không giúp nhiều khi:
Write nhiều, lock nhiều, transaction nặng, index ghi quá nhiều.
Muốn xử lý write bottleneck, cần xem chuyện khác:
- Batch write.
- Giảm index thừa.
- Tối ưu transaction.
- Queue/worker.
- Partitioning.
- Sharding nếu thật sự cần.
- Tách workload.
- Thiết kế dữ liệu lại.
Đừng dùng read replica để chữa sai bệnh.
---
35.6. Replication lag là gì?
Replication lag là độ trễ giữa primary và replica.
Ví dụ:
10:00:00
User nộp bài.
Primary đã có submission.
10:00:01
Replica vẫn chưa thấy submission.
10:00:03
Replica mới nhận được submission.
Lag ở đây là khoảng 3 giây.
Điều này rất quan trọng.
Nếu user vừa nộp bài xong mà trang kế tiếp đọc từ replica, có thể thấy:
Chưa có bài nộp.
Dù thực ra primary đã ghi thành công.
Đây là lỗi trải nghiệm rất hay gặp.
---
35.7. Read-your-write là gì?
Read-your-write nghĩa là:
Sau khi tôi vừa ghi xong, tôi đọc lại thì phải thấy dữ liệu mới của tôi.
Ví dụ:
Tôi vừa đổi avatar.
Tôi reload trang.
Tôi muốn thấy avatar mới.
Nếu request đọc đi vào replica đang lag, user có thể thấy avatar cũ.
Với những luồng cần read-your-write, nên:
- Đọc từ primary ngay sau khi ghi.
- Hoặc sticky user vào primary trong vài giây.
- Hoặc dùng version/timestamp để kiểm tra.
- Hoặc chấp nhận UI hiển thị trạng thái đang cập nhật.
Không phải mọi màn hình đều cần read-your-write.
Nhưng những màn hình ngay sau hành động của user thường cần.
---
35.8. Những thứ không nên đọc từ replica
Không nên đọc từ replica cho quyết định cần dữ liệu mới nhất.
Ví dụ:
- Kiểm tra còn quyền nộp bài không.
- Kiểm tra thanh toán vừa thành công chưa.
- Kiểm tra tồn kho cuối cùng.
- Kiểm tra user vừa bị revoke quyền chưa.
- Kiểm tra job có được claim bởi worker không.
- Kiểm tra trạng thái giao dịch tài chính.
Những thứ này nên đọc primary hoặc dùng cơ chế consistency rõ ràng.
Replica phù hợp hơn cho:
- Danh sách ít nhạy cảm.
- Dashboard có thể trễ.
- Báo cáo.
- Trang public.
- Query đọc nặng không cần dữ liệu tức thì.
Quy tắc:
> Dữ liệu càng ảnh hưởng đến quyết định đúng/sai ngay bây giờ, càng nên đọc từ nguồn mới nhất.
---
35.9. Ví dụ AI Judge với read replica
AI Judge có thể dùng primary cho:
Create Submission
Create GradingJob
Update GradingResult
Check permission
Teacher override score
Student submit/retry
Read replica có thể dùng cho:
Dashboard thống kê lớp.
Danh sách submission cũ.
Báo cáo chấm bài.
Trang analytics nội bộ.
Query lịch sử.
Nhưng sau khi học sinh vừa nộp bài:
Trang "bài của bạn đã nộp" nên đọc primary.
Nếu đọc replica, có thể user thấy chưa nộp và bấm nộp lại.
Đó là lỗi thiết kế.
---
35.10. Failover là gì?
Failover là chuyển sang database khác khi database chính gặp sự cố.
Ví dụ:
Primary chết.
Replica được promote thành primary mới.
App chuyển sang primary mới.
Nghe đơn giản.
Nhưng trong thực tế, failover có nhiều chi tiết:
- Replica có đủ mới không?
- Có mất dữ liệu chưa replicate không?
- App đổi connection thế nào?
- Có hai primary cùng ghi không?
- DNS/connection pool cập nhật ra sao?
- Transaction đang chạy bị xử lý thế nào?
Managed database thường hỗ trợ failover tốt hơn tự dựng.
Nhưng vẫn phải hiểu:
> Replica không tự động biến hệ thống thành không bao giờ lỗi.
Nó chỉ là một phần của chiến lược availability.
---
35.11. Synchronous và asynchronous replication
Asynchronous replication:
Primary commit xong.
Sau đó mới gửi thay đổi sang replica.
Ưu điểm:
- Write nhanh hơn.
- Ít chờ replica.
Nhược điểm:
- Có replication lag.
- Nếu primary chết quá nhanh, có thể mất thay đổi chưa kịp copy.
Synchronous replication:
Primary chỉ commit khi replica đã nhận thay đổi.
Ưu điểm:
- Ít nguy cơ mất dữ liệu hơn.
- Consistency mạnh hơn.
Nhược điểm:
- Write chậm hơn.
- Nếu replica/network chậm, primary bị ảnh hưởng.
Nhiều hệ thống web dùng asynchronous replica cho read scaling.
Những hệ thống cần độ bền cực cao có thể dùng cấu hình chặt hơn.
Không có lựa chọn miễn phí.
Mạnh hơn về consistency thường trả giá bằng latency/availability.
---
35.12. Vertical scaling là gì?
Vertical scaling là tăng sức mạnh cho một database server.
Ví dụ:
Tăng CPU.
Tăng RAM.
Dùng disk nhanh hơn.
Tăng IOPS.
Tăng connection limit hợp lý.
Đây thường là bước đầu tiên rất thực dụng.
Nhiều hệ thống có thể đi rất xa với:
- Một PostgreSQL/MySQL tốt.
- Index đúng.
- Query tốt.
- Connection pool.
- Cache đúng chỗ.
- Read replica khi cần.
- Hardware/managed instance đủ mạnh.
Đừng coi vertical scaling là kém sang.
Nó thường rẻ hơn nhiều so với sharding.
---
35.13. Horizontal scaling database là gì?
Horizontal scaling là chia tải ra nhiều máy.
Với database, có vài kiểu:
- Read replica: chia tải đọc.
- Partitioning: chia dữ liệu trong cùng hệ thống logic.
- Sharding: chia dữ liệu sang nhiều database/node.
- Specialized stores: tách workload sang search/cache/warehouse.
Horizontal scaling nghe hấp dẫn.
Nhưng database không giống web server.
Web server stateless có thể nhân bản dễ hơn.
Database có state.
State phải đúng.
Vì vậy scale database luôn phức tạp hơn scale app server.
---
35.14. Partitioning là gì?
Partitioning là chia một bảng lớn thành nhiều phần nhỏ hơn.
Ví dụ bảng events rất lớn.
Ta chia theo tháng:
events_2026_01
events_2026_02
events_2026_03
Về mặt logic, app vẫn coi như một bảng events.
Database có thể tối ưu query nếu biết cần đọc partition nào.
Partitioning giúp:
- Query nhanh hơn nếu lọc đúng partition.
- Dễ xoá dữ liệu cũ.
- Quản lý bảng lớn tốt hơn.
- Giảm một số vấn đề index/maintenance.
Ví dụ:
Analytics events partition theo ngày/tháng.
Logs partition theo thời gian.
Grading jobs history partition theo created_at.
Partitioning thường nhẹ hơn sharding.
Nhưng vẫn cần hiểu database cụ thể hỗ trợ ra sao.
---
35.15. Partitioning không giống sharding
Partitioning thường vẫn nằm trong cùng database cluster logic.
Sharding là chia dữ liệu ra nhiều database/shard độc lập hơn.
Nói đơn giản:
Partitioning:
chia bảng thành nhiều mảnh để database quản lý tốt hơn.
Sharding:
chia dữ liệu sang nhiều database/node, app hoặc middleware phải biết shard nào giữ dữ liệu nào.
Partitioning có thể là bước trước sharding.
Nhưng không phải cứ partition là đã sharding.
---
35.16. Sharding là gì?
Sharding là chia dữ liệu thành nhiều phần và đặt ở nhiều database khác nhau.
Ví dụ:
Shard 1: user_id 1-1.000.000
Shard 2: user_id 1.000.001-2.000.000
Shard 3: user_id 2.000.001-3.000.000
Hoặc:
Shard A: user_id hash ra A
Shard B: user_id hash ra B
Shard C: user_id hash ra C
Khi cần lấy dữ liệu user 9, hệ thống phải biết:
user 9 nằm ở shard nào?
Mục tiêu của sharding:
- Tăng khả năng ghi.
- Tăng dung lượng tổng.
- Chia tải.
- Tránh một database quá lớn.
Nhưng cái giá rất cao.
---
35.17. Vì sao sharding rất đau?
Vì sau khi sharding, những thứ đơn giản trở nên khó hơn.
Trước sharding:
SELECT * FROM submissions WHERE user_id = 9;
Sau sharding:
User 9 nằm ở shard nào?
Query shard đó.
Nếu query nhiều user ở nhiều shard thì sao?
Nếu join với bảng course nằm shard khác thì sao?
Vấn đề phát sinh:
- Chọn shard key.
- Cross-shard query.
- Cross-shard transaction.
- Rebalancing.
- Hot shard.
- Migration dữ liệu.
- Backup/restore nhiều shard.
- Monitoring phức tạp.
- Debug khó hơn.
- Schema migration khó hơn.
- Report tổng hợp khó hơn.
Sharding không chỉ là "thêm máy".
Nó là thay đổi cách cả hệ thống nghĩ về dữ liệu.
---
35.18. Shard key là gì?
Shard key là field dùng để quyết định dữ liệu nằm ở shard nào.
Ví dụ:
user_id
organization_id
tenant_id
course_id
Nếu chọn shard key tốt, nhiều query sẽ đi đúng một shard.
Nếu chọn sai, hệ thống sẽ đau lâu dài.
Ví dụ AI Judge:
Nếu shard theo user_id, lấy bài của một học sinh dễ.
Nhưng xem toàn bộ assignment có thể phải query nhiều shard.
Nếu shard theo course_id, xem dữ liệu một course dễ.
Nhưng user học nhiều course có thể nằm nhiều shard.
Không có shard key hoàn hảo cho mọi query.
Chọn shard key là chọn query nào được ưu tiên.
---
35.19. Hot shard là gì?
Hot shard là shard bị tải quá nhiều so với shard khác.
Ví dụ:
Shard theo course_id.
Một course siêu lớn có 100.000 học sinh.
Course đó nằm ở shard 3.
Shard 3 quá tải.
Shard khác rảnh.
Vậy dù có 10 shard, hệ thống vẫn nghẽn ở shard 3.
Sharding không tự động chia tải đều.
Phải chọn shard key và chiến lược phân phối cẩn thận.
Hot shard là lý do nhiều hệ thống lớn phải dùng hash, virtual shard, hoặc chiến lược phức tạp hơn.
Nhưng ở giai đoạn chưa cần, đừng tự kéo mình vào.
---
35.20. Cross-shard transaction
Transaction trong một database đã cần cẩn thận.
Transaction qua nhiều shard còn khó hơn.
Ví dụ:
Chuyển dữ liệu từ shard A sang shard B.
Cập nhật course ở shard 1.
Cập nhật submission ở shard 2.
Nếu một shard commit thành công, shard kia lỗi thì sao?
Có kỹ thuật như distributed transaction/two-phase commit, nhưng phức tạp và có chi phí.
Nhiều hệ thống sharded phải tránh transaction xuyên shard bằng cách:
- Thiết kế aggregate nằm cùng shard.
- Dùng eventual consistency.
- Dùng saga.
- Dùng event.
- Chấp nhận quy trình bù trừ.
Đây không còn là chuyện database đơn giản.
Nó ảnh hưởng đến thiết kế domain.
---
35.21. Cross-shard query và báo cáo
Trước sharding, muốn thống kê:
Tổng số submission hôm nay.
Query một database.
Sau sharding:
Query shard 1.
Query shard 2.
Query shard 3.
...
Gộp kết quả.
Nếu có 100 shard, query này không còn đơn giản.
Vì vậy hệ thống sharded thường cần:
- Data warehouse.
- Event pipeline.
- Aggregation tables.
- Async reporting.
Bạn thấy các chương trước bắt đầu nối vào nhau:
Sharding làm reporting khó hơn.
Analytics pipeline giúp gom dữ liệu để phân tích.
Kiến trúc là các trade-off nối tiếp nhau.
---
35.22. Rebalancing là gì?
Rebalancing là di chuyển dữ liệu giữa các shard để cân bằng tải/dung lượng.
Ví dụ:
Shard 1 quá đầy.
Shard 2 còn trống.
Chuyển một phần dữ liệu từ shard 1 sang shard 2.
Nghe hợp lý.
Nhưng thực tế khó:
- Di chuyển dữ liệu lớn mất thời gian.
- Trong lúc di chuyển vẫn có request mới.
- Làm sao không mất dữ liệu?
- Làm sao app biết dữ liệu đã chuyển?
- Làm sao rollback nếu lỗi?
- Làm sao không làm hệ thống chậm?
Rebalancing là một trong các lý do sharding đắt về vận hành.
---
35.23. Trước khi sharding, hãy tối ưu query
Nhiều hệ thống nghĩ mình cần sharding, nhưng thực ra chỉ cần sửa query.
Ví dụ:
SELECT * FROM submissions ORDER BY created_at DESC;
trên bảng hàng chục triệu dòng mà không filter/index tốt.
Hoặc:
N+1 query.
Hoặc:
Không có index cho query chính.
Hoặc:
Query dashboard chạy thẳng trên production database.
Trước khi chia database, hãy làm:
- Xem slow query.
- Dùng EXPLAIN.
- Thêm/sửa index.
- Giảm SELECT *.
- Pagination đúng.
- Tránh N+1.
- Tách dashboard nặng.
- Dùng cache cho read hot.
- Dùng search cho full-text.
- Dùng warehouse cho analytics.
Sharding không chữa query xấu.
Nó chỉ làm query xấu chạy trên nhiều nơi hơn.
---
35.24. Index đúng có thể đi rất xa
Index là một trong những công cụ rẻ và mạnh nhất.
Ví dụ:
Query hay dùng:
WHERE assignment_id = ?
ORDER BY created_at DESC
Index có thể là:
(assignment_id, created_at)
Query hay dùng:
WHERE user_id = ? AND status = ?
Index có thể là:
(user_id, status)
Index không phải cứ thêm càng nhiều càng tốt.
Vì index làm read nhanh hơn nhưng write chậm hơn và tốn dung lượng.
Nhưng index đúng thường giúp tránh tối ưu quá sớm ở tầng kiến trúc lớn.
---
35.25. Connection bottleneck
Đôi khi database không nghẽn vì CPU hay disk.
Nó nghẽn vì quá nhiều connection.
Ví dụ:
FastAPI 20 instance.
Mỗi instance mở 50 connection.
Tổng 1000 connection.
Database chịu không nổi.
Cần dùng:
- Connection pool.
- Giới hạn pool size.
- PgBouncer với PostgreSQL nếu phù hợp.
- Đóng transaction nhanh.
- Không giữ connection khi chờ I/O bên ngoài.
Trong AI Judge, lỗi rất dễ xảy ra:
Worker mở transaction database.
Sau đó gọi Gemini API 90 giây.
Trong 90 giây đó, connection/transaction bị giữ vô ích.
Đúng hơn:
Lấy job.
Commit/đóng transaction.
Gọi Gemini.
Mở transaction ngắn để lưu kết quả.
Đây là tối ưu thiết kế, không phải sharding.
---
35.26. Transaction dài làm database mệt
Transaction nên ngắn.
Transaction dài gây:
- Lock lâu.
- Giữ connection.
- Làm vacuum/cleanup khó hơn.
- Tăng khả năng conflict.
- Làm request khác chờ.
Ví dụ xấu:
BEGIN;
Update job status.
Call AI API 90 seconds.
Save result.
COMMIT;
Ví dụ tốt hơn:
BEGIN;
Claim job.
COMMIT;
Call AI API.
BEGIN;
Save result.
COMMIT;
Rất nhiều vấn đề hiệu năng được giải bằng việc:
Giữ transaction ngắn và rõ.
Không phải bằng sharding.
---
35.27. Tách read workload nặng
Nếu dashboard hoặc report làm nặng database chính, có vài lựa chọn trước khi sharding:
- Read replica.
- Analytics database.
- Materialized view.
- Aggregate table.
- Data warehouse.
- Cache kết quả report.
- Chạy report theo batch.
Ví dụ:
Teacher dashboard cần thống kê 12 tháng.
Không nhất thiết query live từ bảng submission production mỗi lần mở trang.
Có thể:
Mỗi giờ tính aggregate.
Dashboard đọc aggregate.
Đây thường là giải pháp đơn giản và rẻ hơn nhiều.
---
35.28. Tách dữ liệu theo workload
Đôi khi không cần sharding database chính.
Chỉ cần đưa đúng workload sang đúng công cụ:
Full-text search -> Search engine
Analytics -> Warehouse
Cache hot data -> Redis/CDN
File bytes -> Object storage
Job queue -> Queue
Logs -> Log storage
Metrics -> Time-series DB
Database chính bớt phải làm mọi thứ.
Đây là kiểu scaling thực dụng:
> Không chia database nghiệp vụ quá sớm. Trước hết, đừng bắt nó làm việc không thuộc sở trường.
---
35.29. Khi nào read replica đáng dùng?
Read replica đáng cân nhắc khi:
- Primary bị tải đọc nhiều.
- Query read có thể trễ vài giây.
- Dashboard/report đọc nặng.
- Muốn tách read traffic khỏi write traffic.
- Cần bản phụ hỗ trợ backup/failover.
- Hệ thống đã có monitoring để thấy primary nghẽn do read.
Không nên dùng chỉ vì:
Nghe chuyên nghiệp.
Vì khi có replica, app phải biết:
- Query nào đọc primary.
- Query nào đọc replica.
- Lag bao nhiêu là chấp nhận được.
- Khi replica lỗi thì fallback thế nào.
- Làm sao tránh user thấy dữ liệu cũ.
Read replica thêm năng lực, nhưng cũng thêm trách nhiệm.
---
35.30. Khi nào partitioning đáng dùng?
Partitioning đáng cân nhắc khi:
- Một bảng rất lớn.
- Query thường lọc theo thời gian/tenant/range.
- Cần xoá dữ liệu cũ nhanh.
- Maintenance bảng lớn khó.
- Index quá lớn.
- Analytics/events/log history phình nhanh.
Ví dụ:
events partition theo ngày/tháng.
grading_jobs partition theo created_at nếu lịch sử rất lớn.
audit_logs partition theo tháng.
Không nên partition nếu:
- Bảng còn nhỏ.
- Query không lọc theo partition key.
- Team chưa hiểu query pattern.
- Nó làm migration phức tạp hơn mà chưa có lợi rõ.
Partitioning đúng giúp.
Partitioning sai chỉ làm hệ thống khó hơn.
---
35.31. Khi nào sharding đáng nghĩ tới?
Sharding đáng nghĩ tới khi:
- Một database đã được tối ưu nhưng vẫn không đủ.
- Write throughput vượt khả năng primary.
- Dung lượng vượt giới hạn thực tế.
- Bảng quá lớn không thể vận hành tốt.
- Một tenant/user group quá lớn cần cô lập.
- Chi phí vertical scaling/read replica không còn hợp lý.
- Team có năng lực vận hành distributed data.
- Product thật sự cần scale đó.
Trước khi sharding, nên đã làm:
- Slow query/index tuning.
- Connection pooling.
- Cache đúng chỗ.
- Tách search/file/analytics/logs khỏi DB chính.
- Read replica nếu read bottleneck.
- Partitioning nếu bảng lớn theo thời gian/range.
- Archive dữ liệu cũ.
- Batch/aggregate report.
Nếu chưa làm những thứ này, sharding thường là quá sớm.
---
35.32. Dấu hiệu bạn chưa cần sharding
Bạn có thể chưa cần sharding nếu:
- Database còn dưới ngưỡng dung lượng dễ quản lý.
- CPU/RAM/IO chưa gần giới hạn.
- Slow query chưa được xử lý.
- Chưa có index đúng.
- Dashboard vẫn query production bừa bãi.
- File vẫn lưu trong DB.
- Analytics event vẫn trộn vào DB chính không retention.
- App mở quá nhiều connection.
- Transaction giữ quá lâu.
- Chưa dùng cache cho dữ liệu hot.
- Chưa có read replica dù nghẽn do read.
Nếu những thứ này còn chưa ổn, sharding sẽ không làm hệ thống trưởng thành hơn.
Nó chỉ làm vấn đề khó debug hơn.
---
35.33. Multi-tenant và sharding
SaaS thường có nhiều tenant/organization.
Có vài mô hình:
Shared database, shared schema:
mọi tenant chung database, phân biệt bằng tenant_id.
Shared database, separate schema:
mỗi tenant một schema.
Separate database:
mỗi tenant hoặc nhóm tenant có database riêng.
Shared database đơn giản hơn lúc đầu.
Separate database cô lập tốt hơn nhưng vận hành nặng hơn.
Khi tenant lớn, có thể cần:
Tách tenant lớn ra database riêng.
Đây là một dạng sharding theo tenant.
Nó hợp khi:
- Tenant lớn gây tải riêng.
- Cần cô lập dữ liệu.
- Cần backup/restore theo tenant.
- Enterprise customer yêu cầu.
Nhưng nếu sản phẩm chưa có tenant lớn, đừng vội phức tạp hóa.
---
35.34. Archive dữ liệu cũ
Trước khi sharding vì bảng quá lớn, hãy hỏi:
Có cần giữ toàn bộ dữ liệu nóng trong database chính không?
Ví dụ:
- Job history 3 năm trước.
- Raw analytics event cũ.
- Audit log rất cũ.
- File export hết hạn.
- Session expired.
Có thể archive:
Hot data trong DB chính.
Cold data sang storage/warehouse/archive DB.
Ví dụ:
GradingJob 90 ngày gần nhất ở DB chính.
Job cũ hơn chuyển sang warehouse/archive.
Nếu user hiếm khi xem dữ liệu cũ, không cần để nó làm nặng workload hằng ngày.
---
35.35. Materialized view và aggregate table
Nếu dashboard chậm, đừng vội sharding.
Có thể tạo bảng tổng hợp.
Ví dụ raw data:
submissions
grading_results
ai_usage_records
Aggregate table:
daily_course_stats
------------------
date
course_id
submission_count
avg_grading_seconds
ai_cost_usd
error_count
Dashboard đọc bảng này rất nhanh.
Dữ liệu có thể được cập nhật:
- Mỗi giờ.
- Mỗi ngày.
- Khi event xảy ra.
Nếu số liệu cho phép trễ vài phút/giờ, aggregate table rất hiệu quả.
---
35.36. Cẩn thận với "database per service"
Trong microservices, một nguyên tắc hay được nói:
Mỗi service sở hữu database riêng.
Ý tưởng này đúng về boundary.
Nhưng không có nghĩa là:
Tách database càng sớm càng tốt.
Nếu team nhỏ, domain chưa rõ, query vẫn cần join nhiều, việc tách database quá sớm có thể làm mọi thứ khó hơn.
Modular monolith với schema/module rõ ràng có thể là bước tốt trước.
Tách database khi:
- Boundary đã rõ.
- Service có lifecycle riêng.
- Scale/workload khác biệt rõ.
- Team đủ vận hành.
- Chấp nhận eventual consistency giữa service.
Database per service là công cụ thiết kế.
Không phải huy hiệu trưởng thành.
---
35.37. Một lộ trình scale database thực dụng
Một thứ tự thường hợp lý:
1. Thiết kế schema rõ.
2. Dùng index đúng.
3. Sửa slow query.
4. Dùng pagination đúng.
5. Tránh N+1.
6. Dùng connection pool.
7. Giữ transaction ngắn.
8. Tách file/search/cache/analytics ra đúng công cụ.
9. Cache hot read.
10. Aggregate report.
11. Vertical scaling.
12. Read replica nếu nghẽn do đọc.
13. Partitioning nếu bảng lớn theo pattern rõ.
14. Archive cold data.
15. Sharding nếu thật sự không còn cách đơn giản hơn.
Đây không phải luật cứng.
Nhưng nó là thứ tự giúp tránh đi vào vùng phức tạp quá sớm.
---
35.38. Checklist trước khi nghĩ đến sharding
Trước khi sharding, hãy trả lời:
- Database nghẽn ở CPU, RAM, disk, IOPS, lock, connection, hay query?
- Read hay write là vấn đề chính?
- Slow query top 10 là gì?
- Index hiện tại có đúng query pattern không?
- Có query dashboard/report đang đánh vào production không?
- Có transaction nào giữ quá lâu không?
- Có workload nào nên tách sang cache/search/warehouse/object storage không?
- Có dữ liệu cũ nào archive được không?
- Có bảng nào partition theo thời gian được không?
- Read replica có giải quyết được không?
- Shard key sẽ là gì?
- Query nào sẽ trở nên cross-shard?
- Transaction nào sẽ trở nên khó?
- Rebalancing làm thế nào?
- Backup/restore từng shard làm thế nào?
- Team có vận hành được không?
Nếu nhiều câu chưa trả lời được, bạn chưa sẵn sàng sharding.
---
35.39. Ví dụ AI Judge: chưa cần sharding
Giả sử AI Judge bắt đầu chậm.
Đừng kết luận ngay:
Phải sharding PostgreSQL.
Hãy kiểm tra:
Gemini API có đang là bottleneck không?
Celery worker có đủ concurrency không?
Database có slow query nào không?
Worker có giữ transaction trong lúc gọi AI không?
Dashboard teacher có query quá nặng không?
Submission table có index theo assignment_id không?
Analytics event có đang nhét vào DB chính không?
File bài nộp có đang lưu trong DB không?
Có thể vấn đề thật là:
4 worker đang chờ AI 90 giây mỗi job.
Hoặc:
Transaction bị giữ khi gọi external API.
Hoặc:
Dashboard đang scan toàn bộ grading_results.
Những vấn đề này không cần sharding.
Chúng cần thiết kế workload đúng hơn.
---
35.40. Ví dụ AI Judge: khi read replica có ích
AI Judge có:
- 50.000 học sinh.
- Nhiều teacher xem dashboard.
- Nhiều báo cáo theo lớp.
- Nhiều query đọc lịch sử.
Primary bắt đầu bị nặng do read dashboard.
Write vẫn ổn.
Lúc này có thể:
Primary:
submission, grading, permission, write critical.
Read replica:
teacher dashboard, report đọc lịch sử, admin browsing.
Nhưng cần nhớ:
Sau khi teacher sửa điểm, màn hình xác nhận nên đọc primary.
Vì replica có thể lag.
---
35.41. Ví dụ AI Judge: khi partitioning có ích
Bảng grading_jobs có hàng trăm triệu dòng lịch sử.
Query thường hỏi:
Jobs trong 7 ngày gần nhất.
Jobs tháng trước.
Jobs theo created_at.
Lúc này partition theo thời gian có thể hợp lý.
Ví dụ:
grading_jobs_2026_01
grading_jobs_2026_02
grading_jobs_2026_03
Lợi ích:
- Query thời gian gần đây nhanh hơn.
- Xoá/archive job cũ dễ hơn.
- Maintenance dễ hơn.
Nhưng nếu query chủ yếu là:
WHERE submission_id = ?
mà không có filter thời gian, partition theo tháng có thể không giúp nhiều.
Partition key phải đi cùng query pattern.
---
35.42. Ví dụ AI Judge: khi sharding có thể đáng nghĩ
Sharding có thể đáng nghĩ nếu AI Judge rất lớn.
Ví dụ:
Hàng chục triệu học sinh.
Hàng tỷ submission.
Write throughput quá cao.
Một database primary dù tối ưu vẫn không đủ.
Tenant/course lớn cần cô lập.
Có thể shard theo:
tenant_id
course_id
user_id
Nhưng mỗi lựa chọn có cái giá.
Shard theo tenant_id:
- Dễ cô lập organization.
- Hợp SaaS B2B.
- Tenant lớn có thể thành hot shard.
Shard theo user_id:
- Dữ liệu của user dễ tìm.
- Query theo course/assignment có thể cross-shard.
Shard theo course_id:
- Dashboard course dễ hơn.
- User học nhiều course nằm nhiều shard.
Không có đáp án chung.
Phải dựa vào access pattern thật.
---
35.43. Distributed SQL là gì?
Một số database hiện đại cố gắng cung cấp SQL trên nhiều node.
Ví dụ:
- CockroachDB.
- YugabyteDB.
- Google Spanner.
Chúng giúp một số bài toán scale/replication dễ hơn so với tự sharding thủ công.
Nhưng không có nghĩa là hết trade-off.
Vẫn cần hiểu:
- Latency giữa node/region.
- Transaction cost.
- Consistency model.
- Hot key/hot range.
- Query pattern.
- Operational complexity.
- Chi phí.
Distributed SQL có thể rất mạnh.
Nhưng không nên dùng chỉ để né việc hiểu database.
---
35.44. Multi-region database
Multi-region nghĩa là dữ liệu trải qua nhiều vùng địa lý.
Ví dụ:
US
Europe
Asia
Mục tiêu có thể là:
- User gần database hơn.
- Chịu lỗi vùng.
- Tuân thủ dữ liệu địa phương.
- Availability cao hơn.
Nhưng multi-region kéo theo:
- Latency giữa vùng.
- Consistency phức tạp.
- Conflict.
- Failover khó.
- Chi phí cao.
- Quy định dữ liệu.
Đây là cấp độ còn khó hơn read replica một vùng.
Nếu user chủ yếu ở một thị trường, đừng vội multi-region database.
CDN, cache edge, hoặc read replica theo vùng có thể đủ cho nhiều phần đọc.
---
35.45. Đừng nhầm availability với scale
Replication có thể dùng cho availability.
Sharding có thể dùng cho scale.
Nhưng hai mục tiêu khác nhau.
Availability hỏi:
Nếu một node/vùng chết, hệ thống còn chạy không?
Scale hỏi:
Nếu tải tăng, hệ thống xử lý nổi không?
Một replica có thể giúp failover nhưng không giải quyết write bottleneck.
Một shard có thể tăng capacity nhưng làm failover/consistency khó hơn.
Trước khi chọn kỹ thuật, hãy nói rõ mục tiêu.
---
35.46. CAP ở mức vừa đủ
CAP theorem thường bị nói quá hàn lâm.
Ở mức thực dụng, hãy nhớ:
Khi hệ thống phân tán và network có vấn đề, bạn thường phải chọn ưu tiên:
Consistency:
mọi nơi thấy dữ liệu mới nhất/đúng nhất.
Availability:
hệ thống vẫn trả lời được.
Ví dụ:
Nếu replica không liên lạc được primary,
ta có cho đọc dữ liệu cũ không?
Với trang tin tức:
Đọc hơi cũ có thể chấp nhận.
Với thanh toán:
Đọc sai trạng thái có thể nguy hiểm.
Không cần dùng CAP để làm màu.
Chỉ cần hỏi:
> Khi dữ liệu có thể cũ hoặc không chắc đúng, nghiệp vụ có chấp nhận không?
---
35.47. Những câu hỏi khi chọn đọc primary hay replica
Với mỗi query, hỏi:
- User có vừa ghi dữ liệu này không?
- Dữ liệu cũ vài giây có gây hại không?
- Query này có dùng để ra quyết định quan trọng không?
- Query này có nặng không?
- Query này có đọc thường xuyên không?
- Nếu replica lag, UI nên làm gì?
- Nếu replica lỗi, fallback về primary có làm primary chết không?
Ví dụ:
Trang public product listing:
có thể đọc replica/cache.
Trang xác nhận vừa nộp bài:
nên đọc primary.
Teacher dashboard tổng hợp:
có thể đọc replica/aggregate.
Kiểm tra permission:
nên đọc primary hoặc nguồn consistency mạnh.
---
35.48. Khi nào dữ liệu cũ là chấp nhận được?
Dữ liệu cũ vài giây/phút có thể ổn với:
- Dashboard.
- Report.
- Analytics.
- Danh sách public.
- Search index.
- Cache trang tin.
- Số liệu thống kê không realtime.
Dữ liệu cũ thường không ổn với:
- Permission.
- Payment.
- Inventory cuối cùng.
- Job claiming.
- Submit/retry logic.
- Điểm chính thức ngay sau cập nhật.
- Trạng thái tài khoản vừa bị khóa.
Thiết kế tốt là biết query nào thuộc nhóm nào.
Không phải mọi thứ đều cần mạnh nhất.
Không phải mọi thứ đều được phép cũ.
---
35.49. Bảng chọn nhanh
| Vấn đề | Thường nên thử trước | |---|---| | Query chậm | EXPLAIN, index, sửa query, pagination | | N+1 query | Eager loading/batching/query design | | Dashboard nặng | Aggregate table, read replica, warehouse | | Read traffic cao | Cache, read replica | | Write traffic cao | Giảm index thừa, batch, transaction ngắn, queue | | Database quá nhiều connection | Pooling, PgBouncer, giới hạn worker/app pool | | File làm DB phình | Object storage | | Full-text search nặng | Search engine | | Analytics event quá nhiều | Analytics pipeline/warehouse | | Bảng lịch sử quá lớn | Partitioning, archive | | Một DB tối ưu vẫn không đủ write/dung lượng | Sharding hoặc distributed DB | | Cần availability cao | Replication/failover/backup strategy |
---
35.50. Tóm tắt bằng AI Judge
Với AI Judge, đừng bắt đầu bằng sharding.
Bắt đầu bằng:
PostgreSQL/MySQL tốt.
Schema rõ.
Index đúng.
Transaction ngắn.
Queue/worker đúng.
File ở object storage.
Analytics tách dần.
Cache/search đúng chỗ.
Nếu đọc nhiều:
Thêm read replica cho dashboard/report phù hợp.
Nếu bảng lịch sử quá lớn:
Partition/archive theo thời gian.
Nếu dashboard cần phân tích dài hạn:
Đưa sang warehouse.
Chỉ khi:
Write/dung lượng vượt khả năng một database đã tối ưu.
Access pattern rõ.
Team đủ sức vận hành.
mới nên nghĩ đến sharding.
Điểm quan trọng nhất:
Đọc dữ liệu mới nhất sau khi user vừa hành động:
primary.
Đọc báo cáo/danh sách có thể trễ:
replica/aggregate/warehouse.
---
35.51. Kết luận của chương
Replication và sharding là công cụ mạnh.
Nhưng chúng không phải bước đầu tiên.
Read replica giúp tăng khả năng đọc và hỗ trợ một phần availability, nhưng phải sống chung với replication lag.
Partitioning giúp quản lý bảng lớn theo pattern rõ, nhất là dữ liệu thời gian.
Sharding giúp vượt giới hạn của một database, nhưng đổi lại là cross-shard query, transaction khó, rebalancing, backup, monitoring, và thiết kế domain phức tạp hơn nhiều.
Thông điệp cần nhớ:
> Scale database tốt không bắt đầu từ việc chia database. Nó bắt đầu từ việc hiểu workload, query, index, transaction, connection, dữ liệu nóng/lạnh, và đâu là source of truth.
Ở chương tiếp theo, ta sẽ bước sang phần bảo mật và phân quyền, bắt đầu với authentication: đăng nhập thật ra xác nhận điều gì, session/cookie/JWT khác nhau ra sao, OAuth2/OpenID Connect nên hiểu thế nào, và khi nào nên dùng dịch vụ auth có sẵn.