Как уменьшить этапы раскрутки в конвейере агрегации для вложенных документов? - PullRequest
0 голосов
/ 19 мая 2018

Я новичок в mongodb и пытаюсь работать с вложенными документами. У меня есть запрос, как показано ниже

    db.EndpointData.aggregate([
{ "$group" : { "_id" : "$EndpointId", "RequestCount" : { "$sum" : 1 }, "FirstActivity" : { "$min" : "$DateTime" }, "LastActivity" : { "$max" : "$DateTime" }, "Tags" : { "$push" : "$Tags" } } }, 
{ "$unwind" : "$Tags" }, 
{ "$unwind" : "$Tags" }, 
{ "$group" : { "_id" : "$_id", "RequestCount" : { "$first" : "$RequestCount" }, "Tags" : { "$push" : "$Tags" }, "FirstActivity" : { "$first" : "$FirstActivity" }, "LastActivity" : { "$first" : "$LastActivity" } } }, 
{ "$unwind" : "$Tags" }, 
{ "$unwind" : "$Tags.Sensors" }, 
{ "$group" : { "_id" : { "EndpointId" : "$_id", "Uid" : "$Tags.Uid", "Type" : "$Tags.Sensors.Type" }, "RequestCount" : { "$first" : "$RequestCount" }, "FirstActivity" : { "$first" : "$FirstActivity" }, "LastActivity" : { "$first" : "$LastActivity" } } }, 
{ "$group" : { "_id" : { "EndpointId" : "$_id.EndpointId", "Uid" : "$_id.Uid" }, "count" : { "$sum" : 1 }, "RequestCount" : { "$first" : "$RequestCount" }, "FirstActivity" : { "$first" : "$FirstActivity" }, "LastActivity" : { "$first" : "$LastActivity" } } }, 
{ "$group" : { "_id" : "$_id.EndpointId", "TagCount" : { "$sum" : 1 }, "SensorCount" : { "$sum" : "$count" }, "RequestCount" : { "$first" : "$RequestCount" }, "FirstActivity" : { "$first" : "$FirstActivity" }, "LastActivity" : { "$first" : "$LastActivity" } } }])

, и моя структура данных выглядит следующим образом

{
  "_id": "6aef51dfaf42ea1b70d0c4db",  
  "EndpointId": "98799bcc-e86f-4c8a-b340-8b5ed53caf83",  
  "DateTime": "2018-05-06T19:05:02.666Z",
  "Url": "test",
  "Tags": [
    {
      "Uid": "C1:3D:CA:D4:45:11",
      "Type": 1,
      "DateTime": "2018-05-06T19:05:02.666Z",
      "Sensors": [
        {
          "Type": 1,
          "Value": { "$numberDecimal": "-95" }
        },
        {
          "Type": 2,
          "Value": { "$numberDecimal": "-59" }
        },
        {
          "Type": 3,
          "Value": { "$numberDecimal": "11.029802536740132" }
        }
      ]
    },
    {
      "Uid": "C1:3D:CA:D4:45:11",
      "Type": 1,
      "DateTime": "2018-05-06T19:05:02.666Z",
      "Sensors": [
        {
          "Type": 1,
          "Value": { "$numberDecimal": "-92" }
        },
        {
          "Type": 2,
          "Value": { "$numberDecimal": "-59" }
        }
      ]
    }   
  ]
}

Этот запрос отлично работает и правильно .Я считаю метки, датчики и время повторения каждого EdpointID.Но проблема в , когда я работаю с большим объемом данных (около 10 000 000 документов), у меня возникает проблема с памятью .Кажется, в этом запросе есть 4 уровня раскрутки.Как я могу уменьшить раскрутки в этом запросе?

Ответы [ 2 ]

0 голосов
/ 19 мая 2018

Пока ваши данные имеют уникальные показания датчиков и тегов для каждого документа, к которому на сегодняшний день представляется то, что вы представили, вам просто не нужно $unwind вообще.

На самом деле все, что вам действительно нужно, - это один $group:

db.endpoints.aggregate([
  // In reality you would $match to limit the selection of documents
  { "$match": { 
    "DateTime": { "$gte": new Date("2018-05-01"), "$lt": new Date("2018-06-01") }
  }},
  { "$group": {
    "_id": "$EndpointId",
    "FirstActivity" : { "$min" : "$DateTime" },
    "LastActivity" : { "$max" : "$DateTime" },
    "RequestCount": { "$sum": 1 },
    "TagCount": {
      "$sum": {
        "$size": { "$setUnion": ["$Tags.Uid",[]] }
      }
    },
    "SensorCount": {
      "$sum": {
        "$sum": {
          "$map": {
            "input": { "$setUnion": ["$Tags.Uid",[]] },
            "as": "tag",
            "in": {
              "$size": {
                "$reduce": {
                  "input": {
                    "$filter": {
                      "input": {
                        "$map": {
                          "input": "$Tags",
                          "in": {
                            "Uid": "$$this.Uid",
                            "Type": "$$this.Sensors.Type"
                          }
                        }
                      },
                      "cond": { "$eq": [ "$$this.Uid", "$$tag" ] }
                    }
                  },
                  "initialValue": [],
                  "in": { "$setUnion": [ "$$value", "$$this.Type" ] }
                }
              }
            }
          }
        }
      }
    }
  }}
])

Или, если вам действительно нужно накапливать эти "уникальные" значения "Датчики" и "Теги из разных документов, тогда вам все еще нужны начальные операторы $unwind, чтобы получить правильную группировку, но далеко не так, как у вас сейчас:

db.endpoints.aggregate([
  // In reality you would $match to limit the selection of documents
  { "$match": { 
    "DateTime": { "$gte": new Date("2018-05-01"), "$lt": new Date("2018-06-01") }
  }},
  { "$unwind": "$Tags" },
  { "$unwind": "$Tags.Sensors" },
  { "$group": {
    "_id": {
      "EndpointId": "$EndpointId",
      "Uid": "$Tags.Uid",
      "Type": "$Tags.Sensors.Type"
    },
    "FirstActivity": { "$min": "$DateTime" },
    "LastActivity": { "$max": "$DateTime" },
    "RequestCount": { "$addToSet": "$_id" }
  }},
  { "$group": {
    "_id": {
      "EndpointId": "$_id.EndpointId",
      "Uid": "$_id.Uid",
    },
    "FirstActivity": { "$min": "$FirstActivity" },
    "LastActivity": { "$max": "$LastActivity" },
    "count": { "$sum": 1 },
    "RequestCount": { "$addToSet": "$RequestCount" }
  }},
  { "$group": {
    "_id": "$_id.EndpointId",
    "FirstActivity": { "$min": "$FirstActivity" },
    "LastActivity": { "$max": "$LastActivity" },
    "TagCount": { "$sum": 1 },
    "SensorCount": { "$sum": "$count" },
    "RequestCount": { "$addToSet": "$RequestCount" }
  }},
  { "$addFields": {
    "RequestCount": {
      "$size": {
        "$reduce": {
          "input": {
            "$reduce": {
              "input": "$RequestCount",
              "initialValue": [],
              "in": { "$setUnion": [ "$$value", "$$this" ] }
            }
          },
          "initialValue": [],
          "in": { "$setUnion": [ "$$value", "$$this" ] }
        }
      }
    }
  }}
],{ "allowDiskUse": true })

И от MongoDB4.0 вы можете использовать $toString на ObjectId в пределах _id и просто объединять уникальные ключи для них, чтобы сохранить RequestCount с помощью $mergeObjects.Это чище и немного масштабируемее, чем загрузка содержимого вложенного массива и его выравнивание

db.endpoints.aggregate([
  // In reality you would $match to limit the selection of documents
  { "$match": { 
    "DateTime": { "$gte": new Date("2018-05-01"), "$lt": new Date("2018-06-01") }
  }},
  { "$unwind": "$Tags" },
  { "$unwind": "$Tags.Sensors" },
  { "$group": {
    "_id": {
      "EndpointId": "$EndpointId",
      "Uid": "$Tags.Uid",
      "Type": "$Tags.Sensors.Type"
    },
    "FirstActivity": { "$min": "$DateTime" },
    "LastActivity": { "$max": "$DateTime" },
    "RequestCount": {
      "$mergeObjects": {
        "$arrayToObject": [[{ "k": { "$toString": "$_id" }, "v": 1 }]]
      }
    }
  }},
  { "$group": {
    "_id": {
      "EndpointId": "$_id.EndpointId",
      "Uid": "$_id.Uid",
    },
    "FirstActivity": { "$min": "$FirstActivity" },
    "LastActivity": { "$max": "$LastActivity" },
    "count": { "$sum": 1 },
    "RequestCount": { "$mergeObjects": "$RequestCount" }
  }},
  { "$group": {
    "_id": "$_id.EndpointId",
    "FirstActivity": { "$min": "$FirstActivity" },
    "LastActivity": { "$max": "$LastActivity" },
    "TagCount": { "$sum": 1 },
    "SensorCount": { "$sum": "$count" },
    "RequestCount": { "$mergeObjects": "$RequestCount" }
  }},
  { "$addFields": {
    "RequestCount": {
      "$size": {
        "$objectToArray": "$RequestCount"
      }
    }
  }}
],{ "allowDiskUse": true })

Любая форма возвращает те же данные, хотя порядок ключей в результате может отличаться:

{
        "_id" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",
        "FirstActivity" : ISODate("2018-05-06T19:05:02.666Z"),
        "LastActivity" : ISODate("2018-05-06T19:05:02.666Z"),
        "RequestCount" : 2,
        "TagCount" : 4,
        "SensorCount" : 16
}

Результат получен из этих образцов документов, которые вы изначально предоставили в качестве примера источника в оригинальном вопросе по теме :

{
    "_id" : ObjectId("5aef51dfaf42ea1b70d0c4db"),    
    "EndpointId" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",    
    "DateTime" : ISODate("2018-05-06T19:05:02.666Z"),
    "Url" : "test",
    "Tags" : [ 
        {
            "Uid" : "C1:3D:CA:D4:45:11",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:02.666Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-95")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-59")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("11.029802536740132")
                }, 
                {
                    "Type" : 4,
                    "Value" : NumberDecimal("27.25")
                }, 
                {
                    "Type" : 6,
                    "Value" : NumberDecimal("2924")
                }
            ]
        },         
        {
            "Uid" : "C1:3D:CA:D4:45:11",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:02.666Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-95")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-59")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("11.413037961112279")
                }, 
                {
                    "Type" : 4,
                    "Value" : NumberDecimal("27.25")
                }, 
                {
                    "Type" : 6,
                    "Value" : NumberDecimal("2924")
                }
            ]
        },          
        {
            "Uid" : "E5:FA:2A:35:AF:DD",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:02.666Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-97")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-58")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("10.171658037099185")
                }
            ]
        }
    ]
}

/* 2 */
{
    "_id" : ObjectId("5aef51e0af42ea1b70d0c4dc"),    
    "EndpointId" : "89799bcc-e86f-4c8a-b340-8b5ed53caf83",    
    "Url" : "test",
    "Tags" : [ 
        {
            "Uid" : "E2:02:00:18:DA:40",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:04.574Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-98")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-65")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("7.845424441900629")
                }, 
                {
                    "Type" : 4,
                    "Value" : NumberDecimal("0.0")
                }, 
                {
                    "Type" : 6,
                    "Value" : NumberDecimal("3012")
                }
            ]
        }, 
        {
            "Uid" : "12:3B:6A:1A:B7:F9",
            "Type" : 1,
            "DateTime" : ISODate("2018-05-06T19:05:04.574Z"),
            "Sensors" : [ 
                {
                    "Type" : 1,
                    "Value" : NumberDecimal("-95")
                }, 
                {
                    "Type" : 2,
                    "Value" : NumberDecimal("-59")
                }, 
                {
                    "Type" : 3,
                    "Value" : NumberDecimal("12.939770381907275")
                }
            ]
        }
    ]
}

Суть в том, что вы можете использоватьсначала заданная здесь форма, которая будет накапливаться «в каждом документе», а затем «накапливаться на конечную точку» в пределах одной стадии и является наиболее оптимальной, или вам действительно требуется идентифицировать такие вещи, как "Uid" на тегах или "Type" надатчик, в котором эти значения встречаются более одного раза в любой комбинации документов, сгруппированных по конечной точке.

Ваши образцы данных, предоставленные на сегодняшний день, показывают только, что эти значения "уникальны в каждом документе", поэтому первая заданная форма будетбыть наиболее оптимальным, если это относится ко всем оставшимся данным.

В канунЕсли это не так, то «раскручивание» двух вложенных массивов с целью «агрегирования деталей по документам» - единственный способ приблизиться к этому.Вы можете ограничить диапазон дат или другие критерии, поскольку большинство «запросов» обычно имеют некоторые границы и фактически не работают с «целыми» данными коллекции, но основной факт остается фактом: «массивы» будут «разматываться», создавая, по сути, копию документа для каждогочлен массива.

Точка оптимизации означает, что вам нужно сделать это только «дважды», поскольку существует только два массива.Последовательное выполнение $group до $unwind до $group всегда является верным признаком того, что вы делаете что-то действительно неправильно.Как только вы «разберете что-то на части», вам нужно будет только «собрать все вместе» один раз .В серии поэтапных шагов, показанных здесь, находится подход Once , который оптимизирует.

За рамками вашего вопроса все еще остается:

  • Добавить другие реалистичныеограничения на запрос, чтобы уменьшить количество обрабатываемых документов, возможно, даже сделать это «пакетами» и объединить результаты
  • Добавьте параметр allowDiskUse в конвейер, чтобы использовать временное хранилище.(фактически продемонстрировано в командах)
  • Учтите, что "вложенные массивы", вероятно, не лучший метод хранения для анализа, который вы хотите выполнить.Всегда эффективнее, когда вы знаете, что вам нужно $unwind, чтобы просто записать данные в этой «развернутой» форме непосредственно в коллекцию.
0 голосов
/ 19 мая 2018

Если вы имеете дело с данными порядка 10 000 000 документов, вы легко столкнетесь с ограничениями размера конвейера агрегации.В частности, в соответствии с документацией MongoDB , существует ограничение на использование ОЗУ конвейера в 100 МБ.Если в каждом документе содержится не менее 10 байт данных, этого достаточно, чтобы достичь этого предела, и ваши документы будут абсолютно превышать этот объем.

Для решения этой проблемы есть несколько вариантов:

1) Вы можете использовать опцию allowDiskUse, как указано в документации.

2) Вы можете проецировать ваши документы дальше между этапами раскрутки, чтобы ограничить размер документа (очень маловероятно, что этого будет достаточно само по себе).

3) Вы можете периодически создавать сводные документы на подмножествах ваших данных, а затем выполнять агрегирование этих сводных документов.Например, если вы запускаете сводные документы для подмножеств размером 1000, вы можете уменьшить количество документов в конвейерах с 10 000 000 до 10 000.

4) Вы можете просмотреть sharding свою коллекцию и запуститьэти совокупные операции в кластере для снижения нагрузки на любой отдельный сервер.

Варианты 1 и 2 являются очень краткосрочными решениями.Их легко реализовать, но в долгосрочной перспективе они не сильно помогут.Варианты 3 и 4, однако, намного сложнее и сложнее в реализации, но они обеспечат наибольшую масштабируемость и, скорее всего, будут продолжать удовлетворять ваши потребности в долгосрочной перспективе.

Однако предупредите, чтоесли вы планируете приблизиться к варианту 4, вам нужно быть очень подготовленным.Очищенная коллекция не может быть удалена, и ее испортить может привести к непоправимой потере данных.Рекомендуется иметь выделенного администратора с опытом работы с кластерами MongoDB.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...