MongoDB aggregation pipeline

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ăngStages
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_nametotal_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 collection customers dựa trên customer_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ường firstNamelastName:

      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 passwordsecret:

      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ăngOperators
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 VariableMô tả
$$NOWTrả về thời gian hiện tại
$$CLUSTER_TIMETrả về timestamp hiện tại trên cluster
$$ROOTTham chiếu đến document gốc
$$CURRENTTham 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:

  1. $match: Lọc các đơn hàng đã hoàn thành trong khoảng thời gian mong muốn.

  2. $group: Nhóm các đơn hàng theo tháng và tính tổng doanh thu.

  3. $sort: Sắp xếp kết quả theo tháng tăng dần.

  4. $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 methodexplain(): 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ủa explain() 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ụ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. 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 (collectionorders):

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 (collectionwebsite_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 (collectionorders):

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 (collectioncustomers):

Copy

[
    { _id: 1, name: "John Doe", city: "New York" },
    { _id: 2, name: "Jane Smith", city: "London" }
]
  • Dữ liệu mẫu (collectionproducts):

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 (collectionstudents):

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 (collectionsales):

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$sort để tăng tốc độ truy vấn.

  • Ví dụ: Nếu bạn thường xuyên lọc dữ liệu theo statuscreated_at trong collection orders, 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ăngAggregation PipelineMap-Reduce
Hiệu năngCao hơnThấp hơn
Dễ sử dụngDễ hơnKhó hơn
Linh hoạtLinh hoạt hơnÍt linh hoạt hơn
Khả năng mở rộngTốt hơnKém hơn
Tính năngCung cấp nhiều stage và operator tích hợpCho 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 (collectionorders):

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ảng items, 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 mapFunctionreduceFunction để 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