#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 с), поэтому я, вероятно, соглашусь с этим, ноя открыт для других предложений!