175 lines
11 KiB
Markdown
175 lines
11 KiB
Markdown
# ADR-006 — Một cụm RDS PostgreSQL chính, schema-per-module, chịu tải ghi/đọc gộp (trừ Payment)
|
|
|
|
*Tên file: `adr/ADR-006_chien-luoc-du-lieu-rds-gop-schema-per-module.md`*
|
|
|
|
| | |
|
|
|---|---|
|
|
| **Status** | `Proposed` *(🔴 radar ≥8 — theo `decision-radar.md §2`/§6, **không được chuyển `Accepted` khi chưa có `POC-02` PASS**)* |
|
|
| **Date** | 2026-09-15 |
|
|
| **Người quyết** | SA + Tech Lead/DBA (dữ liệu — `decision-radar.md §5`) |
|
|
| **Người đề xuất** | SA (qua skill `sa-2-architecture`, hoạt động `adr`) |
|
|
| **Điểm radar** | **~8/10** — 🔴 bắt buộc POC trước `Accepted` |
|
|
| **Supersedes** | — |
|
|
| **Superseded by** | — |
|
|
| **Liên quan** | `ASR-006` · `QAS-006` · `ARISK-02` · `POC-02` · `ADR-001` · `ADR-002` (Payment tách riêng, độc lập) |
|
|
|
|
> ⚠️ **ADR bất biến sau khi `Accepted`.** Muốn đổi quyết định thì viết ADR mới có
|
|
> `Supersedes: ADR-006` và đổi trạng thái bản cũ thành `Superseded by`.
|
|
|
|
---
|
|
|
|
## 1. Bối cảnh
|
|
|
|
Modular monolith P2 (`ADR-001`) cần một chiến lược dữ liệu nhất quán cho mọi module ngoài
|
|
Payment (đã tách riêng theo `ADR-002`). `ASR-006` ép ra một cụm RDS PostgreSQL Multi-AZ chính,
|
|
chia theo schema-per-module — chưa đo tải ghi (checkout) + đọc (catalog) gộp ở tải đỉnh flash
|
|
sale. Đây là quyết định đạt ngưỡng radar ≥8 vì chưa có bài đo thật trong ngữ cảnh này (`ASM-08`).
|
|
|
|
**Ràng buộc đang chi phối:**
|
|
|
|
| Nguồn | Nội dung |
|
|
|---|---|
|
|
| `ASR-006` | 1 RDS chính schema-per-module, chịu tải ghi/đọc gộp cho mọi module trừ Payment |
|
|
| `QAS-006` | p95 write (checkout) ≤300ms, p95 read (catalog) ≤200ms, CPU <75%, connection pool không exhaust |
|
|
| `ADR-001` | Modular monolith P2 — tiền đề cho việc gộp dữ liệu theo schema thay vì database-per-service |
|
|
| `ADR-002` | Payment có RDS riêng — không nằm trong phạm vi cụm RDS chính này |
|
|
|
|
**Cái đã biết chắc / cái còn là giả định:**
|
|
|
|
| Điều | 🟢 Đã kiểm chứng / 🔴 Giả định | Bằng chứng |
|
|
|---|---|---|
|
|
| Payment có RDS riêng, tách biệt hoàn toàn khỏi cụm chính | 🟢 Đã kiểm chứng | `ADR-002` |
|
|
| 1 cụm RDS `db.r6g.xlarge` Multi-AZ đủ chịu tải ghi/đọc gộp cho các module ngoài Payment ở kịch bản A/B | 🔴 Giả định (`ASM-08`) | `POC-02` — **chưa chạy** |
|
|
| Connection pool chia theo module đủ tránh một module chiếm hết pool | 🔴 Giả định | Chưa có thiết kế connection pool chi tiết — thuộc `DAT`, hoạt động 5 |
|
|
|
|
## 2. Phương án đã cân nhắc
|
|
|
|
### PA-1 — Database-per-service (mỗi module 1 RDS logic riêng)
|
|
|
|
| | |
|
|
|---|---|
|
|
| **Mô tả** | Mỗi module (Identity, Catalog, Cart & Order, Seller, Commission, Promotion, Review, Notification, Shipping) có 1 RDS instance/logic DB riêng — theo `SAD.md`/hồ sơ thầu (P1) |
|
|
| **Ưu** | Cô lập tải hoàn toàn — một module quá tải không ảnh hưởng module khác; dễ scale/tune riêng từng DB |
|
|
| **Nhược** | ~9-10 RDS instance phải patch/backup/monitor độc lập — vi phạm Conway với năng lực team hiện tại (`CON-05`); tăng chi phí vận hành đáng kể so với 1-2 instance |
|
|
| **Chi phí đảo ngược** | Cao — gộp lại thành 1 cụm sau này cần migrate dữ liệu qua nhiều DB, đồng bộ schema |
|
|
|
|
### PA-2 — 1 cụm RDS chính, schema-per-module (ranh giới logic, chung instance) *(chọn)*
|
|
|
|
| | |
|
|
|---|---|
|
|
| **Mô tả** | 1 cụm RDS PostgreSQL Multi-AZ, mỗi module có schema riêng trong cùng instance (ranh giới logic rõ ràng qua schema + quyền truy cập DB user riêng theo schema) |
|
|
| **Ưu** | Ít thành phần phải vận hành hơn (1 instance thay vì ~9-10) — khớp Conway với team hiện tại; vẫn giữ ranh giới logic rõ ràng qua schema (không phải 1 schema dùng chung hỗn loạn) |
|
|
| **Nhược** | Một module quá tải (VD Catalog bị truy vấn nặng mùa flash sale) có thể ảnh hưởng module khác dùng chung instance (Cart & Order, Commission…) — chưa đo tải gộp thật |
|
|
| **Chi phí đảo ngược** | Trung bình-cao — tách read replica/DB riêng cho từng module sau khi đã gộp là một dự án migration riêng |
|
|
|
|
### PA-3 — 1 cụm RDS, 1 schema chung (không phân tách theo module)
|
|
|
|
| | |
|
|
|---|---|
|
|
| **Mô tả** | Tất cả bảng của mọi module nằm chung 1 schema, không phân tách quyền truy cập theo module |
|
|
| **Ưu** | Đơn giản nhất về mặt cấu hình DB ban đầu |
|
|
| **Nhược** | Không có ranh giới logic nào giữa các module — vi phạm `D7` ("mỗi thực thể có đúng một chủ sở hữu" cần ranh giới rõ để enforce); dễ phát sinh coupling ngầm (module A viết trực tiếp bảng của module B) làm mất tác dụng modular monolith |
|
|
| **Chi phí đảo ngược** | Cao — tách schema sau khi code đã coupling chéo là công việc tái cấu trúc lớn |
|
|
|
|
### Bảng so sánh
|
|
|
|
| Tiêu chí | PA-1 (DB-per-service) | PA-2 (1 cụm, schema-per-module) | PA-3 (1 cụm, 1 schema) |
|
|
|---|---|---|---|
|
|
| Số thành phần phải vận hành | ~9-10 | 1 | 1 |
|
|
| Cô lập tải giữa module | ✅ Hoàn toàn | 🟠 Một phần (chung instance) | ❌ Không |
|
|
| Ranh giới logic rõ ràng (`D7`) | ✅ | ✅ (qua schema) | ❌ |
|
|
| Khớp Conway (`CON-05`) | ❌ | ✅ | ✅ |
|
|
|
|
## 3. Quyết định
|
|
|
|
> **Chọn PA-2 — 1 cụm RDS PostgreSQL Multi-AZ chính, schema-per-module, cho mọi module ngoại trừ
|
|
> Payment.**
|
|
|
|
**Vì sao:** Cân bằng giữa khớp Conway (`CON-05`, ít thành phần vận hành hơn database-per-service)
|
|
và vẫn giữ ranh giới logic rõ ràng qua schema (khác PA-3 gộp chung 1 schema mất kiểm soát ranh
|
|
giới dữ liệu).
|
|
|
|
**Phạm vi áp dụng:** Toàn bộ module ngoại trừ Payment (đã có RDS riêng theo `ADR-002`).
|
|
|
|
### Điều kiện chuyển `Proposed → Accepted`
|
|
|
|
| Điều kiện | Ai xác nhận | Trạng thái hiện tại |
|
|
|---|---|---|
|
|
| **`POC-02` PASS** — p95 write ≤300ms, p95 read ≤200ms, CPU <75%, connection pool không exhaust | Tech Lead/DBA | 🔴 **Chưa chạy** (`ARISK-02`) |
|
|
| Thiết kế connection pool theo module (giới hạn để tránh một module chiếm hết pool) | Tech Lead/DBA | Chưa thiết kế — thuộc `DAT` |
|
|
|
|
🔴 **Bắt buộc theo `decision-radar.md §2`/§6** — điểm radar ~8 (≥8) đồng nghĩa ADR này **không
|
|
được chuyển `Accepted`** cho tới khi `POC-02` chạy xong và PASS. Nếu `POC-02` FAIL: tách read
|
|
replica/DB riêng cho Catalog/Search sớm hơn dự kiến, cập nhật `TCO` (chi phí hạ tầng tăng, tiệm
|
|
cận P1 cho phần đó — đã cảnh báo ở `TCO §3.3`).
|
|
|
|
## 4. Phương án bị loại và lý do
|
|
|
|
| Phương án | Loại vì | Gắn với | Điều kiện nào thì xét lại |
|
|
|---|---|---|---|
|
|
| PA-1 — Database-per-service | Vi phạm Conway với năng lực team hiện tại; tăng chi phí vận hành đáng kể | `CON-05`, `OPT §4` tiêu chí 5 | Nếu `POC-02` FAIL nghiêm trọng (không thể khắc phục bằng read replica) **và** team đủ lớn để vận hành nhiều DB — xét lại cho riêng module gây nghẽn (VD tách Catalog) |
|
|
| PA-3 — 1 cụm, 1 schema chung | Không có ranh giới logic — vi phạm `D7`, dễ coupling chéo giữa module | `D7` (design-rules) | Không xét lại — đây là anti-pattern cho modular monolith, không phải trade-off hợp lý |
|
|
|
|
## 5. Hệ quả
|
|
|
|
**Hệ quả tích cực**
|
|
|
|
- Chỉ 1 RDS instance chính phải patch/backup/monitor (ngoài Payment) — khớp năng lực team, giảm
|
|
chi phí vận hành đáng kể so với database-per-service
|
|
- Ranh giới logic rõ ràng qua schema — enforce được `D7` (mỗi thực thể có đúng một chủ)
|
|
|
|
**Hệ quả tiêu cực phải sống chung**
|
|
|
|
- Một module quá tải (VD Catalog mùa flash sale) có thể ảnh hưởng module khác dùng chung instance
|
|
— cần connection pool giới hạn theo module để giảm thiểu
|
|
- Nếu `POC-02` FAIL, phải tách read replica sớm hơn dự kiến — tăng chi phí hạ tầng ngoài kế hoạch
|
|
ban đầu
|
|
|
|
**Cái quyết định này khoá lại**
|
|
|
|
| Muốn đổi về sau thì | Tốn |
|
|
|---|---|
|
|
| Tách một module ra RDS riêng sau khi đã gộp | Migration dữ liệu + đồng bộ song song trong thời gian chuyển đổi — dự án riêng, không miễn phí |
|
|
|
|
**Việc phát sinh**
|
|
|
|
| Việc | Chủ | Hạn | Ghi ở đâu |
|
|
|---|---|---|---|
|
|
| Chạy `POC-02` theo tiêu chí đã viết trước | Tech Lead/DBA | Trước khi `Accepted` | `ARISK_e-commerce_v1.0.md §6` |
|
|
| Thiết kế connection pool giới hạn theo module | SA (hoạt động `dat`) + DBA | GĐ2 tiếp theo | `DAT_e-commerce` |
|
|
| Kế hoạch tách read replica dự phòng nếu `POC-02` FAIL | Tech Lead/DBA | Khi có kết quả `POC-02` | `TDEBT` (GĐ3) hoặc cập nhật `TCO` |
|
|
|
|
## 6. Cách kiểm chứng quyết định này được tuân thủ
|
|
|
|
| Cách kiểm | Công cụ | Chạy ở đâu | `FIT` |
|
|
|---|---|---|---|
|
|
| `POC-02` — tải ghi/đọc gộp | Load test staging, cấu hình instance thật | Trước `Accepted`, lặp lại trước mỗi major release | — (ghi vào `ARISK §6`) |
|
|
| Không có bảng nào bị truy cập chéo schema (module A viết trực tiếp bảng module B) | DB user permission theo schema + code review/lint | CI (schema access lint) | `FIT-09` (ứng viên) |
|
|
| CPU/connection pool RDS trong ngưỡng ở production | CloudWatch metric + alarm | Production, liên tục | — |
|
|
|
|
## 7. Điều kiện xét lại
|
|
|
|
| Dấu hiệu | Ngưỡng | Ai theo dõi |
|
|
|---|---|---|
|
|
| `POC-02` FAIL | Không đạt tiêu chí PASS đã viết trước | Tech Lead/DBA |
|
|
| CPU RDS production vượt 75% thường xuyên | > 3 lần/tuần trong giờ cao điểm | SRE |
|
|
| Connection pool exhaust trong production | Bất kỳ lần nào | SRE |
|
|
|
|
## 8. Tham chiếu
|
|
|
|
- POC: `ARISK_e-commerce_v1.0.md §6 POC-02`
|
|
- Bài đo: `FIT-09` (ứng viên GĐ3)
|
|
- Tài liệu ngoài: `QAS_e-commerce_v1.0.md §A3 QAS-006`
|
|
- Thảo luận: `ASR_e-commerce_v1.0.md §B2 ASR-006`, `OPT_e-commerce_v1.0.md §8 ASM-08`
|
|
|
|
## 9. Review log (không đổi Status)
|
|
|
|
| Ngày | Người review | Vai trò | Quyết định | Lý do chưa chuyển `Accepted` |
|
|
|---|---|---|---|---|
|
|
| 2026-09-15 | Điều phối dự án (thay mặt Tech Lead, chế độ chạy thử) | Tech Lead (ký thay, ngoại lệ `DEC-01`) | `Reviewed (Proposed giữ nguyên)` — nội dung đủ làm cơ sở thiết kế tiếp (`icd`/`dat`/`sec`/`inf`/`fail`) | Radar 8 — bắt buộc `POC-02` (tải RDS gộp) PASS trước khi `Accepted`; chưa chạy |
|
|
|
|
> Đây là ghi nhận **review nội dung**, không phải "sign" theo nghĩa gate: `Status` giữ nguyên
|
|
> `Proposed`. Xem `00-index/ADL_e-commerce.md` (Change Log — Review 2026-09-15) và `ADL §8`
|
|
> "Việc phải làm" cho điều kiện chuyển `Accepted`. Confidence tổng thể của lượt review: 🔴 —
|
|
> `AG2` chưa ký.
|