Производительность получения последней каждой группы в коллекции MongoDB - PullRequest
0 голосов
/ 24 февраля 2020

Предисловие

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


Задача

Предполагается, что конвейер запрашивает последнюю доступную запись для каждой b в коллекции. Схема этой коллекции имеет следующую форму:

{
  at: Date,
  a: String,
  b: String,
  value: Any,
}

Мой текущий конвейер:

[
  {
    "$match": {
      "a": {
        "$in": [
          "a001", "a002", "a003", "a004", "a005", "a006", "a007", "a008", "a009", "a010",
          "a011", "a012", "a013", "a014", "a015", "a016", "a017"
        ]
      },
      "b": { "$exists": true },
      "$and": [
        { "at": { "$gte": ISODate("2020-02-23T10:15:11.396Z") } },
        { "at": { "$lt": ISODate("2020-02-24T10:15:11.396Z") } }
      ]
    }
  },
  { "$sort": { "at": 1 } },
  {
    "$group": {
      "_id": { "a": "$a", "b": "$b" },
      "values": {
        "$last": {
          "k": "$a",
          "v": { "at": "$at", "value": "$value" }
        }
      }
    }
  },
  {
    "$group": {
      "_id": "$_id.b",
      "entries": { "$push": "$values" }
    }
  },
  {
    "$project": {
      "_id": false,
      "b": "$_id",
      "entries": { "$arrayToObject": "$entries" }
    }
  }
]

Условие at используется для указания диапазона дат в пределах где найти последние записи. Порядок вставки ненадежен - поскольку он не обязательно соответствует упорядоченному списку at значений - поэтому, насколько я могу судить, _id для меня бесполезен.

Альтернативно условие "b": { "$exists": true } также массив строк, соответствующих указанным c маякам, как a.

Индекс определяется в полях a, b и at:

{ at: 1, a: 1, b: 1 }

Теперь, к сожалению, имея 7 миллионов записей на данный момент - и считая быстро - чуть больше половины миллиона из которых соответствуют фильтру, этот запрос запускается работать очень медленно, обычно в диапазоне от 3 до 5 секунд , что, конечно, не особенно оптимально в производственной среде.

Запуск конвейера приводит к чему-то вроде следующего:

[
  {
    "b": "b001",
    "entries": {
      "a008": { "at": "2020-02-24T11:00:17.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} },
      "a013": { "at": "2020-02-24T11:00:15.000Z", "value": {} }
    }
  },
  {
    "b": "b002",
    "entries": {
      "a010": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a011": { "at": "2020-02-24T11:00:14.000Z", "value": {} },
      "a008": { "at": "2020-02-24T11:00:17.000Z", "value": {} },
      "a013": { "at": "2020-02-24T11:00:15.000Z", "value": {} },
      "a009": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  },
  {
    "b": "b003",
    "entries": {
      "a008": { "at": "2020-02-24T09:48:24.000Z", "value": {} },
      "a013": { "at": "2020-02-24a00:56:43.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  },
  {
    "b": "b004",
    "entries": {
      "a013": { "at": "2020-02-24T11:00:15.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  },
  {
    "b": "b005",
    "entries": {
      "a008": { "at": "2020-02-24T11:00:07.000Z", "value": {} },
      "a013": { "at": "2020-02-24a00:33:28.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  },
  {
    "b": "b006",
    "entries": {
      "a010": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a009": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a001": { "at": "2020-02-24T09:27:14.000Z", "value": {} },
      "a008": { "at": "2020-02-24T11:00:17.000Z", "value": {} },
      "a013": { "at": "2020-02-24T11:00:15.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} },
      "a011": { "at": "2020-02-24T11:00:14.000Z", "value": {} }
    }
  },
  {
    "b": "b007",
    "entries": {
      "a001": { "at": "2020-02-24a00:58:40.000Z", "value": {} }
    }
  },
  {
    "b": "b008",
    "entries": {
      "a010": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a001": { "at": "2020-02-24T07:38:34.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} },
      "a011": { "at": "2020-02-24T11:00:14.000Z", "value": {} },
      "a013": { "at": "2020-02-24T11:00:15.000Z", "value": {} },
      "a009": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a008": { "at": "2020-02-24T11:00:17.000Z", "value": {} }
    }
  },
  {
    "b": "b009",
    "entries": {
      "a012": { "at": "2020-02-24a00:59:58.000Z", "value": {} }
    }
  },
  {
    "b": "b010",
    "entries": {
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} },
      "a013": { "at": "2020-02-24a00:39:40.000Z", "value": {} }
    }
  },
  {
    "b": "b011",
    "entries": {
      "a005": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  },
  {
    "b": "b012",
    "entries": {
      "a001": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a011": { "at": "2020-02-24T11:00:14.000Z", "value": {} },
      "a009": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a008": { "at": "2020-02-24T11:00:17.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} },
      "a013": { "at": "2020-02-24T11:00:15.000Z", "value": {} },
      "a010": { "at": "2020-02-24a00:59:50.000Z", "value": {} }
    }
  },
  {
    "b": "b013",
    "entries": {
      "a008": { "at": "2020-02-24T11:00:17.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} },
      "a013": { "at": "2020-02-24T11:00:15.000Z", "value": {} },
      "a010": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a011": { "at": "2020-02-24T11:00:14.000Z", "value": {} },
      "a009": { "at": "2020-02-24T11:00:10.000Z", "value": {} }
    }
  },
  {
    "b": "b014",
    "entries": {
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  },
  {
    "b": "b015",
    "entries": {
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} },
      "a013": { "at": "2020-02-24a00:12:24.000Z", "value": {} }
    }
  },
  {
    "b": "b016",
    "entries": {
      "a001": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a005": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  },
  {
    "b": "b017",
    "entries": {
      "a010": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a008": { "at": "2020-02-24T11:00:17.000Z", "value": {} },
      "a013": { "at": "2020-02-24T11:00:15.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} },
      "a009": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a001": { "at": "2020-02-24a00:45:28.000Z", "value": {} },
      "a011": { "at": "2020-02-24T11:00:14.000Z", "value": {} }
    }
  },
  {
    "b": "b018",
    "entries": {
      "a011": { "at": "2020-02-24a00:34:29.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} },
      "a008": { "at": "2020-02-24T11:00:17.000Z", "value": {} },
      "a001": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a013": { "at": "2020-02-24T11:00:15.000Z", "value": {} },
      "a009": { "at": "2020-02-24T11:00:00.000Z", "value": {} },
      "a010": { "at": "2020-02-24T11:00:10.000Z", "value": {} }
    }
  },
  {
    "b": "b019",
    "entries": {
      "a013": { "at": "2020-02-24a00:58:54.000Z", "value": {} },
      "a012": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  },
  {
    "b": "b020",
    "entries": {
      "a001": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a005": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  },
  {
    "b": "b021",
    "entries": {
      "a012": { "at": "2020-02-24a00:41:54.000Z", "value": {} },
      "a001": { "at": "2020-02-24T11:00:10.000Z", "value": {} },
      "a005": { "at": "2020-02-24a00:59:28.000Z", "value": {} },
      "a013": { "at": "2020-02-24a00:47:21.000Z", "value": {} },
      "a008": { "at": "2020-02-24T09:09:47.000Z", "value": {} }
    }
  },
  {
    "b": "b022",
    "entries": {
      "a001": { "at": "2020-02-24a00:59:50.000Z", "value": {} },
      "a005": { "at": "2020-02-24T11:00:08.000Z", "value": {} }
    }
  }
]

Несоответствие дат в этих результатах и ​​запросе / объяснении, потому что я сделал это в другое время. Это не ошибка.

Объяснение конвейера приводит к следующему:

{
  "stages": [
    {
      "$cursor": {
        "query": {
          "a": {
            "$in": [
              "a001", "a002", "a003", "a004", "a005", "a006", "a007", "a008", "a009", "a010",
              "a011", "a012", "a013", "a014", "a015", "a016", "a017"
            ]
          },
          "b": { "$exists": true },
          "$and": [
            { "at": { "$gte": ISODate("2020-02-23T10:15:11.396Z") } },
            { "at": { "$lt": ISODate("2020-02-24T10:15:11.396Z") } }
          ]
        },
        "sort": { "at": 1 },
        "fields": { "at": 1, "b": 1, "a": 1, "value": 1, "_id": 0 },
        "queryPlanner": {
          "plannerVersion": 1,
          "namespace": "database.collection",
          "indexFilterSet": false,
          "parsedQuery": {
            "$and": [
              { "at": { "$lt": ISODate("2020-02-24T10:15:11.396Z") } },
              { "at": { "$gte": ISODate("2020-02-23T10:15:11.396Z") } },
              { "b": { "$exists": true } },
              {
                "a": {
                  "$in": [
                    "a001", "a002", "a003", "a004", "a005", "a006", "a007", "a008", "a009", "a010",
                    "a011", "a012", "a013", "a014", "a015", "a016", "a017"
                  ]
                }
              }
            ]
          },
          "queryHash": "EEAA11B5",
          "planCacheKey": "C2A53FFA",
          "winningPlan": {
            "stage": "FETCH",
            "filter": { "b": { "$exists": true } },
            "inputStage": {
              "stage": "IXSCAN",
              "keyPattern": { "at": 1, "a": 1, "b": 1 },
              "indexName": "at_1_a_1_b_1",
              "isMultiKey": false,
              "multiKeyPaths": { "at": [], "a": [], "b": [] },
              "isUnique": false,
              "isSparse": false,
              "isPartial": false,
              "indexVersion": 2,
              "direction": "forward",
              "indexBounds": {
                "at": [ "[new Date(1582452911396), new Date(1582539311396))" ],
                "a": [
                  "[\"a001\", \"a001\"]",
                  "[\"a002\", \"a002\"]",
                  "[\"a003\", \"a003\"]",
                  "[\"a004\", \"a004\"]",
                  "[\"a005\", \"a005\"]",
                  "[\"a006\", \"a006\"]",
                  "[\"a007\", \"a007\"]",
                  "[\"a008\", \"a008\"]",
                  "[\"a009\", \"a009\"]",
                  "[\"a010\", \"a010\"]",
                  "[\"a011\", \"a011\"]",
                  "[\"a012\", \"a012\"]",
                  "[\"a013\", \"a013\"]",
                  "[\"a014\", \"a014\"]",
                  "[\"a015\", \"a015\"]",
                  "[\"a016\", \"a016\"]",
                  "[\"a017\", \"a017\"]"
                ],
                "b": [ "[MinKey, MaxKey]" ]
              }
            }
          },
          "rejectedPlans": []
        },
        "executionStats": {
          "executionSuccess": true,
          "nReturned": 511710,
          "executionTimeMillis": 3410,
          "totalKeysExamined": 559174,
          "totalDocsExamined": 511710,
          "executionStages": {
            "stage": "FETCH",
            "filter": { "b": { "$exists": true } },
            "nReturned": 511710,
            "executionTimeMillisEstimate": 583,
            "works": 559174,
            "advanced": 511710,
            "needTime": 47463,
            "needYield": 0,
            "saveState": 4476,
            "restoreState": 4476,
            "isEOF": 1,
            "docsExamined": 511710,
            "alreadyHasObj": 0,
            "inputStage": {
              "stage": "IXSCAN",
              "nReturned": 511710,
              "executionTimeMillisEstimate": 334,
              "works": 559174,
              "advanced": 511710,
              "needTime": 47463,
              "needYield": 0,
              "saveState": 4476,
              "restoreState": 4476,
              "isEOF": 1,
              "keyPattern": { "at": 1, "a": 1, "b": 1 },
              "indexName": "at_1_a_1_b_1",
              "isMultiKey": false,
              "multiKeyPaths": { "at": [], "a": [], "b": [] },
              "isUnique": false,
              "isSparse": false,
              "isPartial": false,
              "indexVersion": 2,
              "direction": "forward",
              "indexBounds": {
                "at": [ "[new Date(1582452911396), new Date(1582539311396))" ],
                "a": [
                  "[\"a001\", \"a001\"]",
                  "[\"a002\", \"a002\"]",
                  "[\"a003\", \"a003\"]",
                  "[\"a004\", \"a004\"]",
                  "[\"a005\", \"a005\"]",
                  "[\"a006\", \"a006\"]",
                  "[\"a007\", \"a007\"]",
                  "[\"a008\", \"a008\"]",
                  "[\"a009\", \"a009\"]",
                  "[\"a010\", \"a010\"]",
                  "[\"a011\", \"a011\"]",
                  "[\"a012\", \"a012\"]",
                  "[\"a013\", \"a013\"]",
                  "[\"a014\", \"a014\"]",
                  "[\"a015\", \"a015\"]",
                  "[\"a016\", \"a016\"]",
                  "[\"a017\", \"a017\"]"
                ],
                "b": [ "[MinKey, MaxKey]" ]
              },
              "keysExamined": 559174,
              "seeks": 47464,
              "dupsTested": 0,
              "dupsDropped": 0
            }
          }
        }
      }
    },
    {
      "$group": {
        "_id": { "a": "$a", "b": "$b" },
        "values": {
          "$last": {
            "k": "$a",
            "v": { "at": "$at", "value": "$value" }
          }
        }
      }
    },
    {
      "$group": {
        "_id": "$_id.b",
        "entries": { "$push": "$values" }
      }
    },
    {
      "$project": {
        "_id": false,
        "b": "$_id",
        "entries": { "$arrayToObject": [ "$entries" ] }
      }
    }
  ],
  "serverInfo": {
    "host": "host",
    "port": 27017,
    "version": "4.2.3",
    "gitVersion": "6874650b362138df74be53d366bbefc321ea32d4"
  },
  "ok": 1
}

Кажется важным следующее:

  • The index at_1_a_1_b_1 используется . Отлично.
  • Шаг сортировки оптимизирован с помощью MongoDB. Хорошо, умно, спасибо.
  • время выполнения (executionTimeMillis) составляет 3.41s . Меньше звездного.
  • Всего проверенных ключей (totalKeysExamined) составляет 559174 . Полагаю, этого не происходит. Индексы должны быть проверены, чтобы определить, соответствуют ли они данному фильтру.
  • Всего проверенных и возвращенных документов (totalDocsExamined и nReturned) оба 511710 . Об этом я абсолютно не знаю. Почему это так высоко? Почему на самом деле нужно проверять и возвращать все эти документы, когда я выбираю только $last каждой группы? Странно, но, возможно, нормально.

Альтернатива

Если вы считаете, что конвейер настолько оптимален, насколько возможно, - я искренне надеюсь, что это не так, возможно, сервер просто недостаточно мощный, чтобы запускать его с такой высокой частотой запросов. Он имеет Intel Xeon X5670 и 32 ГБ оперативной памяти. Загрузка процессора MongoDB намного выше 100% при каждом запуске конвейера.

...