Есть ли способ выполнить несколько эффективных (с использованием индекса) находок в одном запросе?

#node.js #mongodb #performance #mongodb-query

#node.js #mongodb #Производительность #mongodb-запрос

Вопрос:

У меня есть коллекция mongodb «событий», которые произошли в мире, где «событие» — это изменение значения некоторой метрики, которое заставляет нас присвоить ему другой статус. Например, если widgetCount падает ниже 10, мы можем создать событие «предупреждение», когда это произойдет, а затем, если оно снова превысит 10, мы зарегистрируем событие «ok». Запись события содержит информацию о том, где и когда произошло событие, и какая метрика изменилась, чтобы вызвать событие. Моему узловому приложению часто требуется знать самое последнее событие для каждой метрики в данном местоположении, и может быть около 15 метрик, которые меня интересуют сразу. У меня возникают трудности с получением запроса (или запросов) для извлечения этой информации для обеспечения хорошей работы, несмотря на наличие правильных индексов.

Допустим, запись события выглядит следующим образом:

 {
  location: 'Anytown',
  metric: 'widgetCount',
  time: new Date('2019-01-01 00:00:00'),
  value: 42,
  status: 'ok'
}

 

И в коллекции есть индекс on {location: 1, metric: 1, time: -1} , а также on {location: 1, time: -1} .

Когда я find() использую каждую комбинацию местоположения / метрики отдельно от узла, каждый запрос полностью сопоставляется с первым индексом, и для выполнения в mongo требуется всего 1-5 мс (я знаю из проверки профилирования в журнале mongo), так что, возможно, 40 мс для всех показателей в местоположении. Однако накладные расходы на выполнение 15 разных запросов увеличивают общее время поиска в узле для всех показателей в местоположении примерно до 3 секунд на местоположение, даже если они распараллелены в a Promise.all() . Отдельные запросы выглядят следующим образом (в данном случае я ищу положение вещей около 1 апреля в полночь, а не прямо сейчас):

 // Repeat this 15 times in a Promise.all(), once for each metric of interest
db.getCollection('events')
.find({
  location: 'Anytown',
  metric: 'widgetCount',
  time: {$lt: new Date('2019-04-01 00:00:00')}
})
.sort({time: -1})
.limit(1)
.toArray()
 

Я понял, что, поскольку все находки имеют некоторые общие условия (местоположение и временные рамки), я мог бы использовать конвейер агрегирования, сначала сопоставляя по общим критериям, сортируя по времени, а затем выполняя фасеты, где каждый фасет соответствует отдельной метрике. Это несколько повышает общую производительность за счет сокращения пятнадцати запросов до одного — при таком подходе общее время поиска в узле для местоположения составляет около 1,5 с. Однако время, затрачиваемое в mongo, увеличивается на порядок — примерно до 400 мс — потому что индекс используется только для начального $match шага, затем вторичный $match в каждом аспекте должен быть удовлетворен сканированием строки. Фасетный подход выглядит следующим образом:

 db.getCollection('events').aggregate([
  {$match: {location: 'Anytown', time: {$lt: new Date('2019-04-01 00:00:00')}},
  {$sort: {time: -1}},
  {$facet: {
    widgetCount: [{$match: {metric: 'widgetCount'}}, {limit: 1}],
    // ...
    // repeat for each different metric I'm interested in
    // ...
  }}
]).toArray()
 

Я хотел бы каким-то образом сообщить монго: «Сделайте эти 15 разных находок в этой коллекции одновременно, а затем верните мне все результаты в одном объекте (или массиве)». В мире моей мечты я мог бы добиться низкого времени выполнения отдельных запросов при моем первом подходе в сочетании с низкими затратами на запросы при моем втором подходе. Есть ли способ сделать это?В качестве альтернативы, есть ли способы уменьшить нагрузку на запросы, чтобы первый подход с 15 отдельными запросами работал лучше?

Примечание: я также попытался выполнить вариант $facet подхода, в котором $facet первый этап конвейера, и каждый аспект $match предоставляет все критерии запроса, в надежде, что mongo может использовать индекс внутри каждого аспекта, если фасет $match был первой обнаруженной инструкцией, но это оказалось намного медленнее, чемлюбой из вышеперечисленных подходов, потому что это было сделано полностью с помощью сканирования строк. tl; dr: По-видимому, mongo не будет использовать индексы внутри фасета ни при каких обстоятельствах.

В настоящее время я работаю с mongodb 3.4 и узлом 10.14.1, чего бы это ни стоило.

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

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

2. Спасибо, @JohnnyHK, изучение poolSize было интересной идеей, но повышение ее с значения по умолчанию от 5 до 50, похоже, не улучшает производительность ни локально, ни в моей серверной среде. С момента написания моего вопроса я заметил, что в моей серверной среде (где задержка в сети не вызывает беспокойства) мультиверсия find() действительно примерно в два раза быстрее, чем фасетная версия (в среднем 0,29 с на местоположение от узла против 0,59 с), поэтому я, вероятно, соглашусь с этим, ноя открыт для других предложений!