Embedding vs. Referencing
Đây là một trong những quyết định thiết kế quan trọng nhất (trade-off) khi làm việc với MongoDB. Là một Senior Backend Engineer, bạn không chỉ chọn một trong hai, mà cần hiểu rõ access patterns (tần suất đọc/ghi) của ứng dụng để quyết định.
Dưới đây là phân tích chuyên sâu về Embedding (Nhúng) và Referencing (Tham chiếu):
1. Embedding (Denormalization)
Dữ liệu liên quan được lưu trữ ngay bên trong document chính (dưới dạng sub-document hoặc array).1
Cấu trúc JSON:
JSON
// User Collection
{
"_id": "user123",
"name": "John Doe",
"addresses": [ // Embedded
{ "city": "HCM", "zip": "700000", "type": "home" },
{ "city": "Hanoi", "zip": "100000", "type": "work" }
]
}Ưu điểm:
Hiệu năng đọc cực cao: Chỉ cần 1 query để lấy toàn bộ dữ liệu liên quan. Không cần
$lookup(JOIN).Atomicity: Các thao tác ghi trên một document là atomic.2 Bạn có thể cập nhật user và thêm địa chỉ mới trong cùng 1 lệnh mà không cần transaction phức tạp.
Nhược điểm:
Giới hạn kích thước: MongoDB document có giới hạn cứng là 16MB.3 Nếu array này phình to không kiểm soát (unbounded array), nó sẽ làm vỡ document.
Dư thừa dữ liệu: Nếu nhiều user cùng chia sẻ một địa chỉ (ví dụ: địa chỉ công ty), bạn phải lưu lặp lại địa chỉ đó. Khi địa chỉ update, bạn phải update hàng loạt document.
Khi nào dùng (Use Cases):
Quan hệ 1-1 hoặc 1-Few (Một - Ít): Ví dụ: User có vài địa chỉ, Post có vài tags.
Dữ liệu luôn được truy xuất cùng nhau: Nếu UI luôn hiển thị User kèm Address, hãy nhúng nó vào.
2. Referencing (Normalization)
Dữ liệu liên quan được lưu ở các Collection riêng biệt và liên kết với nhau qua _id (giống Foreign Key trong SQL).
Cấu trúc JSON:
JSON
Ưu điểm:
Dữ liệu nhất quán (Consistency): Thông tin sản phẩm chỉ nằm ở một nơi. Khi update giá MacBook, tất cả user đều thấy giá mới ngay lập tức.
Document nhỏ gọn: User document không bị phình to khi danh sách yêu thích dài ra.
Linh hoạt: Dễ dàng query độc lập (ví dụ: chỉ query danh sách Products mà không cần quan tâm User).
Nhược điểm:
Hiệu năng đọc thấp hơn: Cần dùng
$lookuphoặc thực hiện 2 round-trips (2 query) từ phía ứng dụng (Backend) để lấy đủ thông tin.Consistency: Nếu xóa Product, bạn phải tự đi xóa ID của nó trong list
favorite_product_idscủa User (manual cleanup).
Khi nào dùng (Use Cases):
Quan hệ 1-Many (Một - Nhiều) hoặc 1-Squillions (Một - Vô cực): Ví dụ: Logs của hệ thống, Comment trong bài viết hot (hàng triệu comments).
Dữ liệu được truy xuất độc lập: Bạn thường xuyên query Product mà không cần biết ai đã like nó.
3. Bảng So Sánh & Chiến lược chọn lựa
Tiêu chí
Embedding (Nhúng)
Referencing (Tham chiếu)
Tư duy SQL tương ứng
JOIN sẵn dữ liệu vào bảng.
Chuẩn hóa (Normalization) thành nhiều bảng.
Số lượng Queries
1 query (Nhanh).
2+ queries hoặc $lookup (Chậm hơn).
Cập nhật dữ liệu
Nhanh cho 1 doc, chậm nếu phải update hàng loạt.
Nhanh, update 1 nơi là xong.
Giới hạn
Document < 16MB.
Không giới hạn kích thước collection.
4. Góc nhìn Senior: "The Hybrid Approach" (Subset Pattern)
Trong thực tế, đôi khi bạn cần cả hai. Đây là kỹ thuật tối ưu phổ biến cho các feed tin tức hoặc danh sách review.
Ví dụ: E-commerce Product Review
Một sản phẩm có thể có 10,000 reviews.
Nếu Embed hết: Vỡ 16MB limit.
Nếu Reference hết: Khi load trang Product, phải query thêm collection Reviews -> chậm.
Giải pháp: Subset Pattern
Nhúng 5-10 review mới nhất/được like nhiều nhất vào document Product (để hiển thị ngay lập tức). Các review còn lại lưu ở collection riêng.
Cấu trúc:
JSON
5. Triển khai với Golang (Go)
Với Go, sự khác biệt nằm ở cách bạn define struct.
Embedding Struct:
Go
Referencing Struct:
Go
Tổng kết
Dùng Embedding mặc định (Default choice) vì lợi thế tốc độ của NoSQL.
Chuyển sang Referencing khi:
Document có nguy cơ vượt quá 16MB.
Dữ liệu con (child data) được cập nhật rất thường xuyên và độc lập.
Quan hệ là Many-to-Many phức tạp.
Last updated