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

#mongodb #date #mongodb-query #aggregation-framework #query-optimization

#mongodb #Дата #mongodb-запрос #aggregation-framework #оптимизация запроса

Вопрос:

У меня есть коллекция, которая содержит документы с вложенными массивами. В subfield массиве содержится 1 миллион документов с тысячами объектов. Документы довольно большие, но для пояснения рассмотрим следующие два документа:

 [
  {
    "id": "myid",
    "field": {
      "total": 1,
      "subfield": [
        {
          "somefield": "1000",
          "time": "2020-08-06T08:33:57.977 0530",
          "val": [
            {
              "x": "someval",
              "a": "val1",
              "b": "val2"
            }
          ]
        },
        {
          "somefield": "2000",
          "time": "2020-05-08T04:13:27.977 0530",
          "val": [
            {
              "x": "someval2",
              "a": "val1",
              "b": "val2"
            }
          ]
        }
      ]
    }
  },
  {
    "id": "myid2",
    "field": {
      "total": 1,
      "subfield": [
        {
          "somefield": "1001",
          "time": "2020-07-31T10:15:50.184 0530",
          "val": [
            {
              "x": "someval2",
              "a": "val1",
              "b": "val2"
            },
            {
              "x": "someval2",
              "a": "val1",
              "b": "val2"
            }
          ]
        }
      ]
    }
  }
]
  

Пример использования:

Мне нужно спроецировать только id документы с time датой (сгруппированные по дате), превышающей значение, и field. subfield.val.b или field. subfield.val.a с определенным значением.

У меня есть запрос для достижения моего варианта использования с использованием операторов $unwind , $toDate $dateToString .,,,,,.

Но использование $unwind для больших массивов приводит к тому, что общий набор использует много памяти и замедляет работу. Теперь это занимает более 15 минут.(Я не добавил никаких индексов, потому что даже если я создаю индексы для created , когда я запускаю explain для агрегации, выигрышный запрос не использует предоставленный индекс)

Мой текущий запрос:

 db.collection.aggregate([
  {
    $unwind: {
      path: "$field.subfield",
      
    }
  },
  {
    $unwind: {
      path: "$field.subfield.val",
      
    }
  },
  {
    $addFields: {
      created_at: {
        $toDate: "$field.subfield.time"
      }
    }
  },
  {
    $match: {
      $and: [
        {
          $expr: {
            $gt: [
              {
                "$dateToString": {
                  "date": "$created_at",
                  "format": "%Y-%m-%d"
                }
              },
              "2020-04-28"
            ]
          }
        },
        {
          $or: [
            {
              "field.subfield.val.a": {
                "$eq": "val1"
              }
            },
            {
              "field.subfield.val.b": {
                "$eq": "val1"
              }
            }
          ]
        }
      ]
    }
  },
  {
    $group: {
      _id: "$id"
    }
  }
])
  

Запрос в MongoDB Playground

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

Моя версия сервера MongoDB — 4.0.3

Какие другие возможные оптимизации можно выполнить?

Спасибо!

Комментарии:

1. Мое предложение состоит в том, чтобы сделать что-то вроде этого , вы можете запрашивать напрямую в элементах массива, нет необходимости разматывать массив, поэтому для to и from вы можете сначала напрямую сопоставить / запросить. второе предложение заключается в том, почему вы не можете преобразовать эту дату "2020-04-28" в дату iso на стороне клиента, а затем сопоставить.

2. @ turivishal Спасибо за ваше предложение! У меня также есть другие запросы для запуска в той же коллекции, которые сравнивают строку даты в формате % Y:% m:% d:% H:% M:% S. Вот почему я решил преобразовать его на стороне сервера.

Ответ №1:

Вы пробовали использовать $ elemMatch? Это работает действительно похоже на функцию js Array.some()

Дополнительная информация в разделе https://docs.mongodb.com/manual/reference/operator/query/elemMatch

 {
    $match:{
      histories:{
        $elemMatch:{
          created_at:{
            $gt:'2020-04-28'
          }
        }
      }
    }
  }  

Ответ №2:

Возможные оптимизации, которые можно выполнить:

  1. Поскольку $unwind замедляет запрос, $filter может использоваться для получения результатов сопоставления из вложенного массива.
  2. Сравнения строк даты являются тяжелыми, поэтому лучше хранить даты как объект даты MongoDB и выполнять все сравнения с самим типом даты, а не с типом строки даты.
 db.collection.aggregate([
  {
    "$project": {
      "obj1": {
        "$filter": {
          "input": "$field.subfield",
          "as": "el",
          "cond": {
            "$and": [
              {
                "$gt": [
                  "$$el.time",
                  new Date("2020-04-29")
                ]
              }
            ]
          }
        }
      },
      id: 1,
      
    }
  },
  {
    $match: {
      $or: [
        {
          "obj1.val.a": {
            "$eq": "val1"
          }
        },
        {
          "obj1.val.b": {
            "$eq": "val1"
          }
        }
      ]
    }
  },
  {
    $project: {
      id: 1,
      _id: 0
    }
  }
])
  

Пример игровой площадки