MongoDB cung cấp các công cụ mạnh mẽ để đơn giản hóa việc truy cập dữ liệu và cải thiện hiệu suất. Trong số đó có Standard Views và Materialized Views, hai cách tiếp cận cho phép bạn kiểm soát cách dữ liệu được đọc và trình bày, mỗi loại đều có những ưu nhược điểm và tình huống sử dụng riêng.
Trong bài viết này, chúng ta sẽ khám phá sự khác biệt giữa hai loại này và khi nào nên sử dụng từng loại một cách hiệu quả.
MongoDB Standard View là gì?
Bạn đang làm việc với MongoDB và nghe đến thứ gọi là “view”? Liệu chúng có giống nhau không? Không hẳn.
MongoDB Standard View (hay đơn giản là View) về cơ bản là một truy vấn đã được lưu sẵn (aggregation pipeline). Nó hoạt động như một bộ sưu tập ảo (virtual collection). Khi bạn truy vấn view này, MongoDB sẽ chạy truy vấn đã lưu trên các bộ sưu tập thực tại thời điểm đó.
- Chỉ là logic: Không lưu dữ liệu, chỉ lưu định nghĩa truy vấn.
- Chỉ đọc: Không thể ghi dữ liệu vào view.
- Sử dụng aggregation pipeline để xác định nội dung hiển thị.
- Chỉ là metadata: MongoDB chỉ lưu định nghĩa truy vấn, không lưu kết quả. Do đó không tốn thêm dung lượng lưu trữ.
Tóm lại, View giống như một lối tắt giúp bạn không phải viết đi viết lại truy vấn phức tạp mỗi lần.
Materialized View là gì?
Khác với Standard View, Materialized View thực sự lưu trữ dữ liệu. Nó là một bộ sưu tập thông thường chứa kết quả đã được tính toán trước từ một truy vấn aggregation.
Tuy nhiên, MongoDB không hỗ trợ natively (tính đến thời điểm hiện tại). Khi nói đến materialized view trong MongoDB, đó là một mô hình bạn tự triển khai, tức là tạo ra một bộ sưu tập khác chứa kết quả đã được tính toán sẵn.
- Lưu trữ kết quả: Aggregation được chạy và kết quả được lưu vào một bộ sưu tập thực.
- Đã tính toán sẵn: Không cần tính lại mỗi lần truy vấn.
- Giả lập denormalization: Giống như tạo bảng chỉ chứa dữ liệu bạn cần cho các truy vấn nhanh.
Bạn sẽ đánh đổi giữa độ tươi mới của dữ liệu và tốc độ đọc, vì đọc từ collection đã được tính sẵn nhanh hơn rất nhiều so với việc tính toán lại mỗi lần.
So sánh: MongoDB Views vs Materialized Views
Độ tươi mới của dữ liệu
- Views: Luôn luôn hiển thị dữ liệu mới nhất vì chạy truy vấn trực tiếp.
- Materialized Views: Chỉ mới như lần cập nhật gần nhất. Nếu cập nhật 1 lần/ngày thì dữ liệu có thể cũ tới 24 giờ.
Hiệu suất
- Views: Phụ thuộc vào độ phức tạp của truy vấn gốc. Aggregation nặng sẽ chậm.
- Materialized Views: Đọc rất nhanh vì chỉ là đọc từ một collection đã tính sẵn. Nhưng ghi thì nặng vì phải chạy aggregation và ghi kết quả định kỳ.
Lưu trữ
- Views: Gần như không tốn dung lượng, chỉ lưu truy vấn.
- Materialized Views: Tốn dung lượng vì lưu dữ liệu kết quả (có thể bị trùng lặp).
Bảo trì
- Views: Gần như không có. Định nghĩa một lần, dùng mãi.
- Materialized Views: Bạn phải tự xây dựng và duy trì quá trình cập nhật, xử lý lỗi, thay đổi cấu trúc dữ liệu nguồn,...
Hỗ trợ trong MongoDB
- Views: Được hỗ trợ hoàn toàn.
- Materialized Views: Không được hỗ trợ trực tiếp. Bạn phải dùng aggregation ($out, $merge), scheduler hoặc Change Streams để tự xây dựng.
Bảo mật và trừu tượng hóa
- Views: Tuyệt vời. Có thể cấp quyền đọc riêng cho từng view, ẩn bớt dữ liệu.
- Materialized Views: Chỉ là một collection khác, cần cấp quyền như bình thường.
Khi nào nên dùng Views hoặc Materialized Views?
Khi nào nên dùng Views?
- Ẩn logic phức tạp: Giúp dev truy vấn dễ dàng, ví dụ thay vì viết pipeline dài, chỉ cần gọi
order_analytics_view
. - Bảo mật dữ liệu: Tạo view chỉ hiển thị một số trường hoặc bản ghi cho người dùng/role cụ thể.
- Prototyping nhanh: Định hình dữ liệu nhanh chóng mà không cần thay đổi collection gốc.
- Dashboard nhẹ: Các dashboard cần dữ liệu thời gian thực, không aggregation nặng.
Khi nào nên dùng Materialized Views?
- Dashboard hiệu suất cao: Các trang phân tích cần phản hồi nhanh, chạy lại cùng một truy vấn nặng nhiều lần.
- Báo cáo nặng: Truy vấn aggregation phức tạp, dễ timeout. Chạy vào ban đêm, lưu kết quả để truy xuất nhanh.
- Tổng hợp dữ liệu time-series: Ví dụ như tính trung bình tháng từ dữ liệu cảm biến hàng ngày.
- Tối ưu cho microservice: Tạo collection riêng, dạng denormalized, phục vụ một microservice cụ thể.
Cách triển khai MongoDB View
Tạo View bằng MongoDB Shell
// Connect to your database first
// Example: use my_database db.createView( "active_users_view", // The name of your new view "users", // The source collection [ // The aggregation pipeline { $match: { status: "active" } } ]
Sau đó bạn có thể truy vấn như collection thường:
db.active_users_view.find()
Tạo View bằng MongoDB Driver (Node.js)
const client = await MongoClient.connect(process.env.MONGODB_URI);
const db = client.db(process.env.DATABASE_NAME); await db.createCollection("active_users_view", { viewOn: "users", pipeline: [ { $match: { status: "active" } } ]
});
Cập nhật View
View không thể thay đổi trực tiếp. Bạn cần:
- Xóa rồi tạo lại:
db.active_users_view.drop();
// Now create it again with the new definition db.createView("active_users_view", "users", [ /* new pipeline */ ]);
- Versioning: Tạo view mới với version (vd: active_users_v2), cập nhật code dùng version mới.
Cách triển khai Materialized View
MongoDB không có câu lệnh CREATE MATERIALIZED VIEW
. Bạn phải tự làm bằng aggregation.
Tạo/Làm mới bằng $out
db.orders.aggregate([ { $match: { status: "completed" } // Only completed orders }, { $group: { _id: "$customerId", // Group by customer totalSpent: { $sum: "$amount" } // Sum their order amounts } }, { $out: "customer_spending_mv" // Write results to this collection (materialized view) }
]);
Lưu ý:
$out
thay thế toàn bộ collection mỗi lần chạy. Nếu aggregation lỗi giữa chừng, collection có thể mất tạm thời.
Tạo/Làm mới bằng $merge
db.orders.aggregate([ { $match: { status: "completed" } // Only completed orders }, { $group: { _id: "$customerId", // Group by customer totalSpent: { $sum: "$amount" } // Sum their order amounts } }, { $merge: { into: "customer_spending_mv", // The target collection on: "_id", // Field to match documents on whenMatched: "replace", // What to do if a document matches (update it) whenNotMatched: "insert" // What to do if no document matches (insert it) } }
])
Tự xử lý hoàn toàn
Bạn có thể tự viết script truy vấn dữ liệu gốc rồi cập nhật collection đích. Linh hoạt nhất, nhưng tốn công nhất.
Chiến lược cập nhật: Làm mới theo yêu cầu vs. định kỳ
- Làm mới theo yêu cầu: Khi cần thì cập nhật. Đơn giản nhưng dễ bị cũ.
- Làm mới theo lịch (Batch): Dùng cron, hoặc MongoDB Atlas Trigger để cập nhật định kỳ.
- Làm mới gần thời gian thực: Dùng Change Streams để theo dõi thay đổi và cập nhật từng phần. Cần replica set.
Tùy nhu cầu về độ tươi mới và tài nguyên mà chọn cách phù hợp.
Đo hiệu suất: Views vs Materialized Views
Mình tạo app Node.js để benchmark:
Chạy lần lượt:
npm run setup-db # setup the `sales` database
npm run seed-data # seed the `orders` collection
npm run create-standard-view # create the `order_analytics_view` view
npm run create-materialized-view # create the `order_analytics_materialized` materialized view
npm run start # run the Node.js server
Sau đó truy cập: http://localhost:3000/api/analytics/compare
Kết quả trong môi trường test:
"performance": { "standardView": { "queryTimeMs": 274, "resultCount": 100 }, "materializedView": { "queryTimeMs": 6, "resultCount": 100, "lastUpdated": "2025-04-20T06:19:40.416Z" }, "comparison": { "timeDifferenceMs": 268, "percentageDifference": "97.81%" } }
Materialized View nhanh hơn tới 97.81% – hoàn toàn dễ hiểu.
Tình huống sử dụng phù hợp
Kết luận
Chúng ta đã tìm hiểu:
- Standard View là truy vấn được lưu sẵn.
- Materialized View là dữ liệu đã được lưu sau khi tính toán.
Mỗi loại đều có điểm mạnh – điểm yếu:
- Standard Views: Đơn giản, dữ liệu luôn mới, nhưng có thể chậm.
- Materialized Views: Truy vấn nhanh, nhưng phức tạp, có thể bị cũ và tốn dung lượng.
Hãy chọn công cụ phù hợp với bài toán. Đừng “over-engineer” nếu chỉ cần View đơn giản, nhưng cũng đừng ngại dùng Materialized View nếu bạn cần tốc độ cao.
Và đừng quên – có thể kết hợp cả hai để tận dụng ưu điểm của từng loại.