MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

Mongodb tổng hợp sắp xếp và giới hạn trong nhóm

Vấn đề cơ bản

Đó không phải là ý tưởng khôn ngoan nhất hiện có để thử và làm điều này trong khuôn khổ tổng hợp hiện tại trong tương lai gần. Tất nhiên, vấn đề chính xuất phát từ dòng này trong đoạn mã bạn đã có:

"items" : { "$push": "$$ROOT" }

Và điều đó có nghĩa là chính xác rằng, điều cần xảy ra về cơ bản là tất cả các đối tượng trong khóa nhóm cần được đẩy vào một mảng để có được kết quả "đầu N" trong bất kỳ đoạn mã nào sau này.

Điều này rõ ràng không mở rộng quy mô vì cuối cùng kích thước của mảng đó có thể vượt quá giới hạn BSON là 16MB và không có phần còn lại của dữ liệu trong tài liệu được nhóm. Điểm chính ở đây là không thể "giới hạn số lượng đẩy" chỉ ở một số mặt hàng nhất định. Có một vấn đề JIRA tồn tại từ lâu về một vấn đề như vậy.

Chỉ vì lý do đó, cách tiếp cận thực tế nhất cho việc này là chạy các truy vấn riêng lẻ cho các mục "đầu N" cho mỗi khóa nhóm. Chúng thậm chí không cần phải là .aggregate() trạng thái (tùy thuộc vào dữ liệu) và thực sự có thể là bất kỳ thứ gì chỉ đơn giản là giới hạn giá trị "N cao nhất" mà bạn muốn.

Phương pháp tốt nhất

Kiến trúc của bạn dường như nằm trên node.js với mongoose , nhưng bất kỳ thứ gì hỗ trợ IO không đồng bộ và thực hiện song song các truy vấn sẽ là lựa chọn tốt nhất. Lý tưởng nhất là thứ gì đó có thư viện API riêng hỗ trợ kết hợp kết quả của những truy vấn đó thành một phản hồi duy nhất.

Ví dụ:có danh sách ví dụ đơn giản này sử dụng kiến ​​trúc của bạn và các thư viện có sẵn (đặc biệt là async ) mà kết quả song song và kết hợp này chính xác:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var data = [
  { "merchant": 1, "rating": 1 },
  { "merchant": 1, "rating": 2 },
  { "merchant": 1, "rating": 3 },
  { "merchant": 2, "rating": 1 },
  { "merchant": 2, "rating": 2 },
  { "merchant": 2, "rating": 3 }
];

var testSchema = new Schema({
  merchant: Number,
  rating: Number
});

var Test = mongoose.model( 'Test', testSchema, 'test' );

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.each(data,function(item,callback) {
        Test.create(item,callback);
      },callback);
    },
    function(callback) {
      async.waterfall(
        [
          function(callback) {
            Test.distinct("merchant",callback);
          },
          function(merchants,callback) {
            async.concat(
              merchants,
              function(merchant,callback) {
                Test.find({ "merchant": merchant })
                  .sort({ "rating": -1 })
                  .limit(2)
                  .exec(callback);
              },
              function(err,results) {
                console.log(JSON.stringify(results,undefined,2));
                callback(err);
              }
            );
          }
        ],
        callback
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Điều này chỉ dẫn đến 2 kết quả hàng đầu cho mỗi người bán trong đầu ra:

[
  {
    "_id": "560d153669fab495071553ce",
    "merchant": 1,
    "rating": 3,
    "__v": 0
  },
  {
    "_id": "560d153669fab495071553cd",
    "merchant": 1,
    "rating": 2,
    "__v": 0
  },
  {
    "_id": "560d153669fab495071553d1",
    "merchant": 2,
    "rating": 3,
    "__v": 0
  },
  {
    "_id": "560d153669fab495071553d0",
    "merchant": 2,
    "rating": 2,
    "__v": 0
  }
]

Nó thực sự là cách hiệu quả nhất để xử lý điều này mặc dù nó sẽ tốn tài nguyên vì nó vẫn là nhiều truy vấn. Nhưng không có nơi nào gần các tài nguyên bị tiêu hao trong đường dẫn tổng hợp nếu bạn cố gắng lưu trữ tất cả các tài liệu trong một mảng và xử lý nó.

Vấn đề Tổng hợp, hiện tại và tương lai gần

Đối với dòng đó, có thể coi rằng số lượng tài liệu không gây ra vi phạm trong giới hạn BSON mà điều này có thể được thực hiện. Các phương thức với bản phát hành hiện tại của MongoDB không phải là tuyệt vời cho việc này, nhưng bản phát hành sắp tới (theo văn bản, chi nhánh nhà phát triển 3.1.8 thực hiện điều này) ít nhất cũng giới thiệu một $slice nhà điều hành đến đường ống tổng hợp. Vì vậy, nếu bạn thông minh hơn về hoạt động tổng hợp và sử dụng $sort trước tiên, bạn có thể dễ dàng chọn ra các mục đã được sắp xếp trong mảng:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var data = [
  { "merchant": 1, "rating": 1 },
  { "merchant": 1, "rating": 2 },
  { "merchant": 1, "rating": 3 },
  { "merchant": 2, "rating": 1 },
  { "merchant": 2, "rating": 2 },
  { "merchant": 2, "rating": 3 }
];

var testSchema = new Schema({
  merchant: Number,
  rating: Number
});

var Test = mongoose.model( 'Test', testSchema, 'test' );

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.each(data,function(item,callback) {
        Test.create(item,callback);
      },callback);
    },
    function(callback) {
      Test.aggregate(
        [
          { "$sort": { "merchant": 1, "rating": -1 } },
          { "$group": {
            "_id": "$merchant",
            "items": { "$push": "$$ROOT" }
          }},
          { "$project": {
            "items": { "$slice": [ "$items", 2 ] }
          }}
        ],
        function(err,results) {
          console.log(JSON.stringify(results,undefined,2));
          callback(err);
        }
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Điều này mang lại kết quả cơ bản giống như 2 mục hàng đầu được "cắt" khỏi mảng sau khi chúng được sắp xếp trước.

Nó cũng thực sự "có thể" trong các bản phát hành hiện tại, nhưng với những ràng buộc cơ bản giống nhau ở chỗ điều này vẫn liên quan đến việc đẩy tất cả nội dung vào một mảng sau khi sắp xếp nội dung trước. Nó chỉ cần một cách tiếp cận "lặp đi lặp lại". Bạn có thể viết mã này để tạo ra quy trình tổng hợp cho các mục lớn hơn, nhưng chỉ hiển thị "hai" sẽ cho thấy đó không phải là một ý tưởng thực sự tuyệt vời để thử:

var async = require('async'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var data = [
  { "merchant": 1, "rating": 1 },
  { "merchant": 1, "rating": 2 },
  { "merchant": 1, "rating": 3 },
  { "merchant": 2, "rating": 1 },
  { "merchant": 2, "rating": 2 },
  { "merchant": 2, "rating": 3 }
];

var testSchema = new Schema({
  merchant: Number,
  rating: Number
});

var Test = mongoose.model( 'Test', testSchema, 'test' );

async.series(
  [
    function(callback) {
      Test.remove({},callback);
    },
    function(callback) {
      async.each(data,function(item,callback) {
        Test.create(item,callback);
      },callback);
    },
    function(callback) {
      Test.aggregate(
        [
          { "$sort": { "merchant": 1, "rating": -1 } },
          { "$group": {
            "_id": "$merchant",
            "items": { "$push": "$$ROOT" }
          }},
          { "$unwind": "$items" },
          { "$group": {
            "_id": "$_id",
            "first": { "$first": "$items" },
            "items": { "$push": "$items" }
          }},
          { "$unwind": "$items" },
          { "$redact": {
            "$cond": [
              { "$eq": [ "$items", "$first" ] },
              "$$PRUNE",
              "$$KEEP"
            ]
          }},
          { "$group": {
            "_id": "$_id",
            "first": { "$first": "$first" },
            "second": { "$first": "$items" }
          }},
          { "$project": {
            "items": {
              "$map": {
                "input": ["A","B"],
                "as": "el",
                "in": {
                  "$cond": [
                    { "$eq": [ "$$el", "A" ] },
                    "$first",
                    "$second"
                  ]
                }
              }
            }
          }}
        ],
        function(err,results) {
          console.log(JSON.stringify(results,undefined,2));
          callback(err);
        }
      );
    }
  ],
  function(err) {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Và một lần nữa, mặc dù "có thể" trong các phiên bản trước (đây là sử dụng 2,6 tính năng được giới thiệu để rút ngắn vì bạn đã gắn thẻ $$ROOT ), các bước cơ bản là lưu trữ mảng và sau đó lấy từng mục "ra khỏi ngăn xếp" bằng cách sử dụng $first và so sánh mục đó (và có thể là các mục khác) với các mục trong mảng để loại bỏ chúng và sau đó lấy mục "đầu tiên tiếp theo" khỏi ngăn xếp đó cho đến khi hoàn thành "đầu N" của bạn.

Kết luận

Cho đến ngày có một hoạt động như vậy cho phép các mục trong $push bộ tích lũy tổng hợp được giới hạn ở một số lượng nhất định, khi đó đây không thực sự là một hoạt động thực tế cho tổng hợp.

Bạn có thể làm điều đó, nếu dữ liệu bạn có trong các kết quả này đủ nhỏ và nó thậm chí có thể hiệu quả hơn xử lý phía máy khách nếu máy chủ cơ sở dữ liệu có đủ thông số kỹ thuật để mang lại lợi thế thực sự. Nhưng rất có thể điều đó sẽ không xảy ra trong hầu hết các ứng dụng thực tế với cách sử dụng hợp lý.

Đặt cược tốt nhất là sử dụng tùy chọn "truy vấn song song" được trình bày trước. Nó sẽ luôn mở rộng quy mô tốt và không cần phải "viết mã" logic như vậy mà một nhóm cụ thể có thể không trả về ít nhất tổng số mục "đầu N" được yêu cầu và tìm cách giữ lại chúng (ví dụ lâu hơn nữa về điều đó đã bị bỏ qua ) vì nó chỉ thực hiện từng truy vấn và kết hợp các kết quả.

Sử dụng các truy vấn song song. Nó sẽ tốt hơn so với cách tiếp cận được mã hóa mà bạn có và nó sẽ hoạt động tốt hơn so với cách tiếp cận tổng hợp đã được chứng minh trong một chặng đường dài. Ít nhất là cho đến khi có một lựa chọn tốt hơn.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Tìm và Đếm các phần tử của bộ sưu tập với Mongoose

  2. Cập nhật mô hình với Mongoose, Express, NodeJS

  3. Nâng cấp lên ClusterControl Enterprise Edition

  4. Cập nhật một mục trong một mảng nằm trong một mảng

  5. MongoDB Mối quan hệ một đến nhiều