Schema, query và connection design
Schema là API lâu dài cho mọi writers. Constraints, types, access patterns, connection capacity và migration sequence phải cùng bảo vệ correctness lẫn khả năng rollout.
Constraints bảo vệ invariant
Bắt đầu từ invariant cụ thể: balance không âm, email unique, order chỉ chuyển state hợp lệ và một idempotency key chỉ tạo một payment. NOT NULL, CHECK, UNIQUE, primary key, foreign key và exclusion constraints bảo vệ dữ liệu cho mọi writer. Application validation cải thiện UX nhưng không thay database constraint. Deferred constraints hữu ích cho một số multi-row transitions nhưng làm failure xuất hiện muộn và tăng transaction complexity.
Normalization và denormalization
Normalization tách facts để tránh update, delete và insert anomalies. Denormalization là optimization phải có source of truth, sync owner, rebuild path và consistency model. JSON aggregate không thay relational model nếu fields cần join, constraint hoặc query thường xuyên.
Chọn data types
| Domain | Lựa chọn | Caveat |
|---|---|---|
| Money | numeric hoặc integer minor units theo precision/range. | Không dùng floating point cho exact financial values. |
| Time | timestamptz cho instant; date cho calendar date. | Timezone presentation thuộc boundary; DST cần test. |
| Status | DB enum, text + check hoặc reference table. | Enum chặt nhưng migration có trade-off; text linh hoạt hơn nhưng cần constraint. |
| Identifier | UUID hoặc identity/bigint. | Random UUID ảnh hưởng index locality; sequential ID đơn giản nhưng dễ đoán và khó generate phân tán. |
JSONB
JSONB phù hợp attributes linh hoạt hoặc document payload; GIN và operators hỗ trợ search mạnh. Business fields quan trọng vẫn nên có typed columns và constraints. Update một phần JSON vẫn tạo row version mới và WAL; schema evolution không biến mất chỉ vì dữ liệu là document.
Chọn SQL, document hay key-value storage
| Nhu cầu chính | Candidate | Câu hỏi quyết định |
|---|---|---|
| Relations, constraints, flexible joins/reporting | Relational database | Invariant và multi-row transaction nào phải được bảo vệ? |
| Aggregate/document đọc-ghi cùng nhau, nested shape biến đổi | Document database | Aggregate có bounded size/lifecycle và ít cross-document joins không? |
| Simple key access, latency rất thấp | Key-value store | Consistency, durability, scan/query và failure model có đủ không? |
| Massive time-series/write pattern | Column-family hoặc time-series store | Partition key, retention và query dimensions là gì? |
| Relationship traversal là core query | Graph database | Traversal depth/pattern có justify engine chuyên biệt không? |
Với MongoDB, embed khi child được đọc/cập nhật cùng aggregate, cùng lifecycle và bounded growth. Reference khi relation many-to-many, child được chia sẻ, update độc lập hoặc collection có thể tăng vô hạn. “Schema linh hoạt” không loại nhu cầu validation, indexes và migrations.
Pagination
Offset dễ dùng nhưng chậm và không ổn định ở page sâu khi dataset thay đổi. Keyset cursor chứa last ordering values, dùng lexicographic predicate và unique tie-breaker. Cursor nên opaque/versioned; snapshot semantics và direction phải rõ.
Connection pool
Pool size dựa database concurrent capacity và query latency, không dựa số HTTP requests. Acquisition timeout phải ngắn hơn request deadline. Idle/max lifetime giúp thay stale connections; leak detection chỉ là diagnostic. PgBouncer transaction pooling có giới hạn với session state, temp tables và một số prepared-statement behaviors.
Zero-downtime migrations
- Expand: thêm schema nullable hoặc backward-compatible.
- Deploy code đọc/ghi tương thích cả cũ lẫn mới.
- Backfill theo bounded batches, có progress và retry.
- Validate constraints/indexes khi dữ liệu đã sạch.
- Chuyển read/write path và quan sát.
- Contract: xóa schema cũ sau compatibility window.
Đánh giá DDL lock và table rewrite trước rollout. Migration phải audited và resumable; backup không thay rollback/forward-fix plan vì restore có thể vượt RTO và mất dữ liệu sau backup.