I. Giới Thiệu & Lợi Ích
1. Aggregation Pipeline là gì?
Aggregation Pipeline là một tính năng mạnh mẽ trong MongoDB cho phép bạn thực hiện phân tích dữ liệu phức tạp trực tiếp trên database server. Nó hoạt động bằng cách truyền dữ liệu qua một chuỗi các stage (giai đoạn), mỗi stage thực hiện một thao tác cụ thể trên dữ liệu trước khi truyền đến stage tiếp theo.
Hãy tưởng tượng nó như một băng chuyền xử lý dữ liệu có khả năng tùy chỉnh cao: dữ liệu thô của bạn được đưa vào đầu băng chuyền, đi qua các stage để được lọc, biến đổi, nhóm, sắp xếp... và cuối cùng cho ra kết quả mong muốn.
2. Lợi ích của Aggregation Framework so vớifind()
Phân tích dữ liệu tại chỗ (In-database analytics): Aggregation Framework cho phép bạn phân tích dữ liệu trực tiếp trên database server, tránh việc phải di chuyển dữ liệu sang các hệ thống phân tích khác như Apache Spark hay Hadoop. Điều này giúp tiết kiệm thời gian, băng thông và chi phí.
Xử lý dữ liệu trực tiếp: Bạn có thể truy vấn và phân tích dữ liệu trực tiếp trên database, không phải trên bản sao dữ liệu cũ. Điều này đảm bảo tính chính xác và cập nhật của kết quả phân tích.
Khả năng biến đổi dữ liệu mạnh mẽ: Aggregation Framework cung cấp một tập hợp phong phú các stage và operator, cho phép bạn thực hiện các phép biến đổi dữ liệu phức tạp, từ lọc, nhóm, sắp xếp đến join với các collection khác.
3. So sánh Aggregation Pipeline với Map-Reduce
Cả Aggregation Pipeline và Map-Reduce đều là các công cụ mạnh mẽ để xử lý và phân tích dữ liệu trong MongoDB. Tuy nhiên, Aggregation Pipeline được ưa chuộng hơn trong hầu hết các trường hợp do:
Hiệu năng cao hơn: Aggregation Pipeline được tối ưu hóa để chạy nhanh hơn Map-Reduce, đặc biệt là với các tác vụ phân tích phổ biến.
Dễ sử dụng hơn: Cú pháp của Aggregation Pipeline dễ đọc và dễ hiểu hơn Map-Reduce, giúp bạn dễ dàng xây dựng và debug pipeline.
Linh hoạt hơn: Aggregation Pipeline cung cấp nhiều stage và operator hơn Map-Reduce, cho phép bạn thực hiện các phép biến đổi dữ liệu phức tạp hơn.
Map-Reduce vẫn có thể hữu ích cho các tác vụ phân tích rất phức tạp hoặc khi bạn cần tùy chỉnh logic xử lý dữ liệu ở mức độ chi tiết.
II. Các Thành Phần Chính
1. Stages:
Tổng quan: Mỗi stage trong Aggregation Pipeline thực hiện một thao tác cụ thể trên dữ liệu. Các stage được kết nối với nhau theo thứ tự, dữ liệu đầu ra của stage trước sẽ là dữ liệu đầu vào của stage tiếp theo.
Phân loại:
Chức năng | Stages |
Lọc dữ liệu | $match, $redact, $limit, $sample |
Biến đổi dữ liệu | $project, $addFields, $unset, $replaceRoot, $unwind |
Nhóm dữ liệu | $group, $bucket, $bucketAu |
Sắp xếp dữ liệu | $sort |
Join dữ liệu | $lookup, $graphLookup, $unionWith |
Ghi kết quả | $out, $merge |
Khác | $count, $facet, $setWindowFields, etc. |
Mô tả chi tiết từng stage:
$match
: Lọc dữ liệu dựa trên điều kiện, tương tự như mệnh đềWHERE
trong SQL.{ $match: { <query> } }
Ví dụ: Lọc các đơn hàng có trạng thái "completed":
{ $match: { status: "completed" } }
$group
: Nhóm các document dựa trên một hoặc nhiều trường, tương tự như mệnh đềGROUP BY
trong SQL.{ $group: { _id: <expression>, <field1>: { <accumulator1>: <expression1> }, ... } }
Ví dụ: Nhóm các đơn hàng theo khách hàng và tính tổng giá trị đơn hàng:
{ $group: { _id: "$customer_id", totalAmount: { $sum: "$amount" } } }
$project
: Chọn các trường cần hiển thị hoặc tạo ra các trường mới, tương tự như mệnh đềSELECT
trong SQL.Copy
{ $project: { <field1>: <expression1>, <field2>: <expression2>, ... } }
Ví dụ: Chọn trường
customer_name
vàtotal_amount
:Copy
{ $project: { _id: 0, customer_name: 1, total_amount: 1 } }
$sort
: Sắp xếp các document dựa trên một hoặc nhiều trường, tương tự như mệnh đềORDER BY
trong SQL.Copy
{ $sort: { <field1>: <sort order>, <field2>: <sort order>, ... } }
Ví dụ: Sắp xếp các đơn hàng theo ngày tạo giảm dần:
Copy
{ $sort: { created_at: -1 } }
$limit
: Giới hạn số lượng document trả về, tương tự như mệnh đềLIMIT
trong SQL.Copy
{ $limit: <number> }
Ví dụ: Trả về 5 đơn hàng đầu tiên:
Copy
{ $limit: 5 }
$unwind
: Tách các element trong một array field thành các document riêng biệt.Copy
{ $unwind: <field path> }
Ví dụ: Tách các sản phẩm trong đơn hàng:
Copy
{ $unwind: "$products" }
$lookup
: Thực hiện phép join với một collection khác, tương tự như mệnh đềJOIN
trong SQL.Copy
{ $lookup: { from: <collection to join>, localField: <field from the input documents>, foreignField: <field from the documents of the "from" collection>, as: <output array field> } }
Ví dụ: Join collection
orders
với collectioncustomers
dựa trêncustomer_id
:Copy
{ $lookup: { from: "customers", localField: "customer_id", foreignField: "_id", as: "customer_info" } }
$merge
: Ghi kết quả vào một collection hiện có hoặc mới.Copy
{ $merge: { into: <output collection name>, on: <field to join on>, whenMatched: <merge action>, whenNotMatched: <merge action> } }
Ví dụ: Ghi kết quả vào collection
order_summary
, thay thế document nếu khớp và chèn document mới nếu không khớp:Copy
{ $merge: { into: "order_summary", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } }
$addFields
: Thêm trường mới vào document.Copy
{ $addFields: { <newField>: <expression>, ... } }
Ví dụ: thêm trường
fullName
bằng cách kết hợp trườngfirstName
vàlastName
:Copy
{ $addFields: { fullName: { $concat: ["$firstName", " ", "$lastName"] } } }
$bucket
: Phân loại document vào các nhóm ("bucket") dựa trên biểu thức và giới hạn.Copy
{ $bucket: { groupBy: <expression>, boundaries: [ <lowerbound1>, <lowerbound2>, ... ], default: <literal>, output: { <output1>: { <accumulator1>: <expression1> }, ... } } }
Ví dụ: phân loại người dùng vào các nhóm tuổi dựa trên trường
age
:Copy
{ $bucket: { groupBy: "$age", boundaries: [ 0, 18, 30, 50, 100 ], default: "100+", output: { count: { $sum: 1 } } } }
$bucketAuto
: Tương tự$bucket
, nhưng tự động xác định giới hạn bucket để phân phối document đồng đều.Copy
{ $bucketAuto: { groupBy: <expression>, buckets: <number>, output: { <output1>: { <accumulator1>: <expression1> }, ... } } }
Ví dụ: Phân loại người dùng vào 5 nhóm dựa trên trường
score
:Copy
{ $bucketAuto: { groupBy: "$score", buckets: 5, output: { count: { $sum: 1 }, avgScore: { $avg: "$score" } } } }
$count
: Đếm số document.Copy
{ $count: <string> }
Ví dụ: Đếm số document và lưu vào trường
total
:Copy
{ $count: "total" }
$facet
: Xử lý nhiều aggregation pipeline song song.Copy
{ $facet: { <outputField1>: [ <stage1>, <stage2>, ... ], <outputField2>: [ <stage1>, <stage2>, ... ], ... } }
Ví dụ: Thực hiện 2 pipeline song song: đếm tổng số sản phẩm và tính giá trung bình:
Copy
{ $facet: { totalProducts: [ { $count: "count" } ], averagePrice: [ { $group: { _id: null, avgPrice: { $avg: "$price" } } } ] } }
$replaceRoot
: Thay thế document hiện tại bằng một document con.Copy
{ $replaceRoot: { newRoot: <expression> } }
Ví dụ: Thay thế document hiện tại bằng document con trong trường
customer
:Copy
{ $replaceRoot: { newRoot: "$customer" } }
$sample
: Lấy mẫu ngẫu nhiên một số document.Copy
{ $sample: { size: <number> } }
Ví dụ: Lấy mẫu ngẫu nhiên 10 document:
Copy
{ $sample: { size: 10 } }
$skip
: Bỏ qua một số document đầu tiên.Copy
{ $skip: <number> }
Ví dụ: Bỏ qua 5 document đầu tiên:
Copy
{ $skip: 5 }
$unset
: Loại bỏ trường khỏi document.Copy
{ $unset: [ <field1>, <field2>, ... ] }
Ví dụ: Loại bỏ trường
password
vàsecret
:Copy
{ $unset: [ "password", "secret" ] }
$setWindowFields
: Tính toán các giá trị dựa trên một "cửa sổ" các document.Copy
{ $setWindowFields: { partitionBy: <expression>, sortBy: { <sort field>: <sort direction> }, output: { <output field>: { $function: <window function>, window: <window specification> }, ... } } }
Ví dụ: Tính toán tổng doanh thu tích lũy cho mỗi khách hàng, sắp xếp theo ngày:
Copy
{ $setWindowFields: { partitionBy: "$customerId", sortBy: { orderDate: 1 }, output: { cumulativeRevenue: { $sum: "$amount", window: { documents: [ "unbounded", "current" ] } } } } }
2. Operators:
Tổng quan: Operators là các hàm được sử dụng trong Aggregation Pipeline để biến đổi dữ liệu và thực hiện các phép tính. Operators có thể được sử dụng trong các stage như
$project
,$match
,$group
, etc.Phân loại:
Chức năng | Operators |
Toán học | $add, $subtract, $multiply, $divide, $mod, $abs, etc. |
Mảng | $size, $arrayElemAt, $concatArrays, $filter, $in, etc. |
Logic | $and, $or, $not |
So sánh | $eq, $gt, $lt, $gte, $lte, $ne, $cmp |
Điều kiện | $cond, $ifNull, $switch |
Ngày tháng | $year, $month, $dayOfMonth, $hour, $minute, etc. |
Chuỗi | $concat, $substr, $toUpper, $toLower, $trim, etc. |
Tập hợp | $setUnion, $setIntersection, $setDifference, etc. |
Tổng hợp | $sum, $avg, $max, $min, $first, $last, $push, etc. |
Window | $sum, $avg, $min, $max, $denseRank, $rank, etc. |
Mô tả chi tiết một số operator:
$sum
: Tính tổng các giá trị.Copy
{ $sum: <expression> }
Ví dụ: Tính tổng giá trị đơn hàng:
Copy
{ $sum: "$amount" }
$avg
: Tính trung bình các giá trị.Copy
{ $avg: <expression> }
Ví dụ: Tính giá trung bình của sản phẩm:
Copy
{ $avg: "$price" }
$max
: Tìm giá trị lớn nhất.Copy
{ $max: <expression> }
Ví dụ: Tìm giá trị đơn hàng lớn nhất:
Copy
{ $max: "$amount" }
$min
: Tìm giá trị nhỏ nhất.Copy
{ $min: <expression> }
Ví dụ: Tìm giá trị đơn hàng nhỏ nhất:
Copy
{ $min: "$amount" }
$concat
: Nối các chuỗi.Copy
{ $concat: [ <expression1>, <expression2>, ... ] }
Ví dụ: Nối họ và tên:
Copy
{ $concat: [ "$last_name", ", ", "$first_name" ] }
$cond
: Thực hiện biểu thức điều kiện.Copy
{ $cond: { if: <boolean expression>, then: <expression>, else: <expression> } }
Ví dụ: Gán giá trị "high" nếu giá lớn hơn 100, ngược lại gán giá trị "low":
Copy
{ $cond: { if: { $gt: [ "$price", 100 ] }, then: "high", else: "low" } }
3. Expressions:
Định nghĩa: Aggregation Expressions là các biểu thức được sử dụng để định nghĩa các operator và các phép tính phức tạp trong Aggregation Pipeline.
Các thành phần:
Field Path
: Đường dẫn đến một trường trong document. Ví dụ:"$customer_id"
.Literal Values
: Các giá trị cố định như số, chuỗi, boolean. Ví dụ:100
,"completed"
,true
.System Variables
: Các biến hệ thống cung cấp thông tin về ngữ cảnh thực thi pipeline. Ví dụ:$$NOW
trả về thời gian hiện tại.Operators
: Các hàm được sử dụng để biến đổi dữ liệu và thực hiện các phép tính. Ví dụ:$sum
,$avg
,$concat
.
Ví dụ:
Copy
{ $project: { _id: 0, totalAmount: { $multiply: ["$price", "$quantity"] }, orderDate: { $dateToString: { format: "%Y-%m-%d", date: "$created_at" } } } }
4. Variables:
User-Defined Variables
: Biến do người dùng định nghĩa trong stage$let
, cho phép bạn lưu trữ giá trị tạm thời và sử dụng trong các biểu thức khác.Copy
{ $let: { vars: { <var1>: <expression1>, <var2>: <expression2>, ... }, in: <expression> } }
Ví dụ:
Copy
{ $let: { vars: { totalAmount: { $multiply: ["$price", "$quantity"] } }, in: { $cond: { if: { $gt: [ "$$totalAmount", 100 ] }, then: "high", else: "low" } } } }
System Variables
: Biến hệ thống cung cấp thông tin về ngữ cảnh thực thi pipeline.
System Variable | Mô tả |
$$NOW | Trả về thời gian hiện tại |
$$CLUSTER_TIME | Trả về timestamp hiện tại trên cluster |
$$ROOT | Tham chiếu đến document gốc |
$$CURRENT | Tham chiếu đến trường hiện tại đang xử lý |
Ví dụ:
Copy
{ $project: { _id: 0, orderDate: "$$NOW", item: "$item" } }
III. Xây Dựng Aggregation Pipeline (Steps by Steps)
1. Xác định mục tiêu phân tích:
Bước đầu tiên là xác định rõ ràng mục tiêu của bạn là gì. Bạn muốn đạt được điều gì bằng cách sử dụng Aggregation Pipeline? Ví dụ:
Tính toán tổng doanh thu theo từng tháng?
Tìm 5 khách hàng chi tiêu nhiều nhất?
Phân tích hành vi người dùng theo tuần?
Phân loại sản phẩm theo mức giá?
...
Việc xác định rõ mục tiêu sẽ giúp bạn chọn lựa các stage và operator phù hợp.
2. Lựa chọn các stage phù hợp:
Dựa vào mục tiêu phân tích, bạn cần lựa chọn các stage phù hợp để xử lý dữ liệu. Hãy nhớ rằng dữ liệu sẽ được truyền qua các stage theo thứ tự, do đó hãy sắp xếp các stage một cách logic.
Ví dụ: nếu bạn muốn tính tổng doanh thu theo từng tháng, bạn có thể sử dụng các stage sau:
$match
: Lọc các đơn hàng đã hoàn thành trong khoảng thời gian mong muốn.$group
: Nhóm các đơn hàng theo tháng và tính tổng doanh thu.$sort
: Sắp xếp kết quả theo tháng tăng dần.$project
: Chọn các trường cần thiết (tháng và tổng doanh thu), loại bỏ các trường không cần thiết.
3. Sử dụng operators và expressions để biến đổi dữ liệu:
Trong mỗi stage, bạn có thể sử dụng operators và expressions để biến đổi dữ liệu theo ý muốn.
Ví dụ: trong stage $group
, bạn có thể sử dụng operator $sum
để tính tổng doanh thu, operator $avg
để tính giá trị trung bình, operator $push
để tạo mảng chứa danh sách sản phẩm...
4. Ghi kết quả vào collection (tùy chọn):
Nếu bạn muốn lưu trữ kết quả phân tích, bạn có thể sử dụng stage $out
hoặc $merge
để ghi kết quả vào một collection mới hoặc collection hiện có.
5. Debug và tối ưu hóa Aggregation Pipeline:
Sử dụng method
explain()
:explain()
cho phép bạn xem kế hoạch thực thi của Aggregation Pipeline, giúp bạn hiểu rõ cách pipeline được xử lý và tìm ra các điểm cần tối ưu hóa. Kết quả củaexplain()
sẽ cho bạn biết MongoDB sử dụng index nào (nếu có), các stage nào tốn nhiều thời gian nhất, etc.Tối ưu hóa stage
$match
:Đặt
$match
ở đầu pipeline: Lọc dữ liệu sớm nhất có thể để giảm thiểu lượng dữ liệu cần xử lý ở các stage tiếp theo.Sử dụng index hiệu quả: Tạo index cho các trường được sử dụng trong điều kiện
$match
để tăng tốc độ truy vấn.
Giảm thiểu lượng dữ liệu truyền qua pipeline: Sử dụng stage
$project
để chọn chỉ các trường cần thiết, tránh truyền các trường không cần thiết qua các stage tiếp theo.Sử dụng
allowDiskUse
khi cần thiết: Nếu pipeline yêu cầu nhiều bộ nhớ hơn giới hạn cho phép (100MB), bạn có thể sử dụng optionallowDiskUse
để cho phép MongoDB ghi dữ liệu tạm thời vào đĩa. Tuy nhiên, lưu ý rằng việc ghi dữ liệu vào đĩa sẽ làm giảm hiệu năng của pipeline.
IV. Các Ví Dụ Minh Họa
1. Tính toán các chỉ số thống kê:
- Dữ liệu mẫu (collection
orders
):
Copy
[
{ _id: 1, customer_id: 1, amount: 100, status: "completed", created_at: ISODate("2023-01-10T10:00:00Z") },
{ _id: 2, customer_id: 2, amount: 50, status: "pending", created_at: ISODate("2023-01-15T12:30:00Z") },
{ _id: 3, customer_id: 1, amount: 200, status: "completed", created_at: ISODate("2023-02-20T09:15:00Z") },
{ _id: 4, customer_id: 3, amount: 150, status: "completed", created_at: ISODate("2023-03-05T14:45:00Z") }
]
- Ví dụ 1: Tính tổng doanh thu, số lượng đơn hàng và giá trị đơn hàng trung bình cho các đơn hàng đã hoàn thành:
Copy
db.orders.aggregate([
{ $match: { status: "completed" } },
{
$group: {
_id: null,
totalRevenue: { $sum: "$amount" },
orderCount: { $sum: 1 },
avgOrderValue: { $avg: "$amount" }
}
}
]);
- Ví dụ 2: Tính tổng doanh thu theo từng khách hàng cho các đơn hàng đã hoàn thành:
Copy
db.orders.aggregate([
{ $match: { status: "completed" } },
{
$group: {
_id: "$customer_id",
totalRevenue: { $sum: "$amount" }
}
},
{ $sort: { totalRevenue: -1 } }
]);
2. Phân tích dữ liệu theo thời gian:
- Dữ liệu mẫu (collection
website_visits
):
Copy
[
{ _id: 1, user_id: 1, page: "/home", timestamp: ISODate("2023-04-01T10:00:00Z") },
{ _id: 2, user_id: 2, page: "/products", timestamp: ISODate("2023-04-01T12:30:00Z") },
{ _id: 3, user_id: 1, page: "/cart", timestamp: ISODate("2023-04-02T09:15:00Z") },
{ _id: 4, user_id: 3, page: "/checkout", timestamp: ISODate("2023-04-03T14:45:00Z") }
]
- Ví dụ: Tính số lượng truy cập website theo ngày:
Copy
db.website_visits.aggregate([
{
$group: {
_id: { $dateToString: { format: "%Y-%m-%d", date: "$timestamp" } },
visitCount: { $sum: 1 }
}
},
{ $sort: { _id: 1 } }
]);
3. Thực hiện phép join phức tạp:
- Dữ liệu mẫu (collection
orders
):
Copy
[
{ _id: 1, customer_id: 1, items: [ { product_id: 1, quantity: 2 }, { product_id: 2, quantity: 1 } ] },
{ _id: 2, customer_id: 2, items: [ { product_id: 3, quantity: 3 } ] }
]
- Dữ liệu mẫu (collection
customers
):
Copy
[
{ _id: 1, name: "John Doe", city: "New York" },
{ _id: 2, name: "Jane Smith", city: "London" }
]
- Dữ liệu mẫu (collection
products
):
Copy
[
{ _id: 1, name: "Product A", price: 10 },
{ _id: 2, name: "Product B", price: 20 },
{ _id: 3, name: "Product C", price: 15 }
]
- Ví dụ: Lấy danh sách đơn hàng, thông tin khách hàng, và chi tiết sản phẩm:
Copy
db.orders.aggregate([
{
$lookup: {
from: "customers",
localField: "customer_id",
foreignField: "_id",
as: "customer"
}
},
{ $unwind: "$customer" },
{
$unwind: "$items"
},
{
$lookup: {
from: "products",
localField: "items.product_id",
foreignField: "_id",
as: "product"
}
},
{ $unwind: "$product" },
{
$group: {
_id: "$_id",
customer: { $first: "$customer" },
items: { $push: { product: "$product", quantity: "$items.quantity" } }
}
},
{
$project: {
_id: 0,
orderId: "$_id",
customerName: "$customer.name",
customerCity: "$customer.city",
items: 1
}
}
]);
4. Xử lý dữ liệu dạng Array:
- Dữ liệu mẫu (collection
students
):
Copy
[
{ _id: 1, name: "Alice", scores: [80, 90, 75] },
{ _id: 2, name: "Bob", scores: [95, 85, 90] }
]
- Ví dụ 1: Tính điểm trung bình cho mỗi học sinh:
Copy
db.students.aggregate([
{
$project: {
_id: 0,
name: 1,
averageScore: { $avg: "$scores" }
}
}
]);
- Ví dụ 2: Tìm học sinh có điểm cao nhất trong mỗi bài kiểm tra:
Copy
db.students.aggregate([
{ $unwind: "$scores" },
{
$group: {
_id: { $indexOfArray: ["$scores", { $max: "$scores" }] },
highestScore: { $max: "$scores" },
student: { $push: { name: "$name", score: "$scores" } }
}
},
{ $sort: { "_id": 1 } },
{
$project: {
_id: 0,
testIndex: "$_id",
highestScore: 1,
students: {
$filter: {
input: "$student",
as: "student",
cond: { $eq: ["$$student.score", "$highestScore"] }
}
}
}
}
]);
5. Tạo báo cáo và dashboard:
- Dữ liệu mẫu (collection
sales
):
Copy
[
{ _id: 1, product: "Product A", region: "North America", revenue: 1000, date: ISODate("2023-01-10") },
{ _id: 2, product: "Product B", region: "Europe", revenue: 800, date: ISODate("2023-01-15") },
{ _id: 3, product: "Product A", region: "Asia", revenue: 1200, date: ISODate("2023-02-20") },
{ _id: 4, product: "Product C", region: "North America", revenue: 1500, date: ISODate("2023-03-05") }
]
- Ví dụ: Tạo báo cáo doanh thu theo khu vực và sản phẩm:
Copy
db.sales.aggregate([
{
$group: {
_id: { region: "$region", product: "$product" },
totalRevenue: { $sum: "$revenue" }
}
},
{ $sort: { "_id.region": 1, "_id.product": 1 } },
{
$project: {
_id: 0,
region: "$_id.region",
product: "$_id.product",
totalRevenue: 1
}
}
]);
V. Tối Ưu Hóa Aggregation Pipeline
1. Sử dụng Index:
Tạo index cho các trường được sử dụng trong stage
$match
và$sort
để tăng tốc độ truy vấn.Ví dụ: Nếu bạn thường xuyên lọc dữ liệu theo
status
vàcreated_at
trong collectionorders
, hãy tạo index cho hai trường này:
Copy
db.orders.createIndex({ status: 1, created_at: -1 });
2. Tối ưu hóa stage$match
:
Đặt stage
$match
ở đầu pipeline để lọc dữ liệu sớm nhất có thể, giảm thiểu lượng dữ liệu cần xử lý ở các stage tiếp theo.Ví dụ:
Copy
// Không tối ưu
db.orders.aggregate([
{ $sort: { created_at: -1 } },
{ $match: { status: "completed" } },
{ $limit: 10 }
]);
// Tối ưu
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $sort: { created_at: -1 } },
{ $limit: 10 }
]);
3. Giảm thiểu lượng dữ liệu truyền qua pipeline:
Sử dụng stage
$project
để chọn chỉ các trường cần thiết, tránh truyền các trường không cần thiết qua các stage tiếp theo.Ví dụ:
Copy
// Không tối ưu
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customer_id", totalAmount: { $sum: "$amount" } } },
{ $sort: { totalAmount: -1 } },
{ $project: { _id: 0, customer_id: "$_id", totalAmount: 1 } }
]);
// Tối ưu
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customer_id", totalAmount: { $sum: "$amount" } } },
{ $sort: { totalAmount: -1 } },
{ $project: { _id: 0, totalAmount: 1 } }
]);
4. Sử dụngallowDiskUse
khi cần thiết:
Nếu pipeline yêu cầu nhiều bộ nhớ hơn giới hạn cho phép (100MB), bạn có thể sử dụng option
allowDiskUse
để cho phép MongoDB ghi dữ liệu tạm thời vào đĩa.Ví dụ:
Copy
db.orders.aggregate([
// Các stage phức tạp, yêu cầu nhiều bộ nhớ
], { allowDiskUse: true });
5. Sử dụngexplain()
để phân tích hiệu năng:
- Method
explain()
cho phép bạn xem kế hoạch thực thi của Aggregation Pipeline, giúp bạn hiểu rõ cách pipeline được xử lý và tìm ra các điểm cần tối ưu hóa.
Copy
db.orders.aggregate([
// Các stage trong pipeline
]).explain();
Kết quả
explain()
sẽ chứa các thông tin chi tiết về cách pipeline được thực thi, bao gồm:winningPlan
: Kế hoạch thực thi được MongoDB lựa chọn.stages
: Danh sách các stage trong pipeline và thông tin chi tiết về cách mỗi stage được thực thi.inputStage
: Stage cung cấp dữ liệu đầu vào cho stage hiện tại.indexBounds
: Thông tin về index được sử dụng (nếu có).executionStats
: Thống kê về thời gian thực
VI. So Sánh Aggregation Pipeline với Map-Reduce
Tính năng | Aggregation Pipeline | Map-Reduce |
Hiệu năng | Cao hơn | Thấp hơn |
Dễ sử dụng | Dễ hơn | Khó hơn |
Linh hoạt | Linh hoạt hơn | Ít linh hoạt hơn |
Khả năng mở rộng | Tốt hơn | Kém hơn |
Tính năng | Cung cấp nhiều stage và operator tích hợp | Cho phép tùy chỉnh logic xử lý dữ liệu bằng JavaScript |
Nên sử dụng Aggregation Pipeline khi:
Bạn cần thực hiện các tác vụ phân tích phổ biến như tính toán tổng, trung bình, max, min, etc.
Bạn muốn pipeline dễ đọc, dễ hiểu và dễ bảo trì.
Hiệu năng là yếu tố quan trọng.
Nên sử dụng Map-Reduce khi:
Bạn cần thực hiện các tác vụ phân tích rất phức tạp, yêu cầu logic tùy chỉnh chi tiết mà Aggregation Pipeline không hỗ trợ.
Bạn không quá quan tâm đến hiệu năng.
Ví dụ minh họa về cách chuyển đổi từ Map-Reduce sang Aggregation Pipeline:
Bài toán: Tính toán số lượng sản phẩm đã bán cho mỗi loại sản phẩm.
- Dữ liệu mẫu (collection
orders
):
Copy
[
{ _id: 1, items: [{ product: "A", quantity: 2 }, { product: "B", quantity: 1 }] },
{ _id: 2, items: [{ product: "A", quantity: 3 }] },
{ _id: 3, items: [{ product: "B", quantity: 2 }, { product: "C", quantity: 1 }] }
]
- Giải pháp Map-Reduce:
Copy
var mapFunction = function() {
for (var i = 0; i < this.items.length; i++) {
emit(this.items[i].product, this.items[i].quantity);
}
};
var reduceFunction = function(key, values) {
return Array.sum(values);
};
db.orders.mapReduce(
mapFunction,
reduceFunction,
{ out: "product_sales" }
);
- Giải pháp Aggregation Pipeline:
Copy
db.orders.aggregate([
{ $unwind: "$items" },
{
$group: {
_id: "$items.product",
totalQuantity: { $sum: "$items.quantity" }
}
},
{ $out: "product_sales" }
]);
Giải thích:
Cả hai phương pháp đều cho ra kết quả giống nhau, được lưu trữ trong collection
product_sales
.Aggregation Pipeline sử dụng stage
$unwind
để tách các sản phẩm trong mảngitems
, sau đó sử dụng stage$group
để nhóm theo tên sản phẩm và tính tổng số lượng.Map-Reduce sử dụng hai hàm
mapFunction
vàreduceFunction
để thực hiện tương tự.Tuy nhiên, Aggregation Pipeline dễ đọc và dễ hiểu hơn, đồng thời hiệu năng cũng cao hơn Map-Reduce.
VII. Tài Liệu Tham Khảo
Aggregation Pipeline là một công cụ vô cùng mạnh mẽ để phân tích dữ liệu trong MongoDB. Bằng cách kết hợp các stage và operator, bạn có thể thực hiện các phép phân tích phức tạp ngay trên database server, giúp tiết kiệm thời gian, băng thông và chi phí.
Để tìm hiểu sâu hơn về Aggregation Pipeline, bạn có thể tham khảo các tài liệu sau:
\======================
Một chút không liên quan: Mình cũng có dịp sharing ở Vietnam MongoDB User Group meetup, và có chút liên quan đến aggregation pipeline, chủ đề của mình là Data Modeling, nếu ai có quan tâm:
Slide: docs.google.com/presentation/d/1dhC84tcvFo3..
Bài sharing private (Bài này mình sharing lại cho nhóm em mình theo nhu cầu của họ, chứ không phải bài sharing public ở VMUG): youtube.com/watch?v=N7LqwEuC6hQ