У меня есть коллекция mongodb «событий», которые произошли в мире, где «событие» - это изменение значения некоторой метрики, которое заставляет нас присвоить ей другой статус. Например, если widgetCount падает ниже 10, мы можем создать событие «предупреждение», когда это произойдет, а затем, если оно поднимется выше 10, мы запишем событие «ОК». Запись о событии содержит информацию о том, где и когда произошло событие, и какой показатель изменился, чтобы вызвать событие. Моему приложению узла часто нужно знать самое последнее событие для каждой метрики в данном месте, и может быть около 15 метрик, которые меня интересуют одновременно. У меня возникают трудности с получением запроса (или запросов) для получения этой информации, чтобы она работала хорошо, несмотря на наличие правильных индексов.
Допустим, запись о событии выглядит следующим образом:
{
location: 'Anytown',
metric: 'widgetCount',
time: new Date('2019-01-01 00:00:00'),
value: 42,
status: 'ok'
}
И у коллекции есть индекс на {location: 1, metric: 1, time: -1}
, а также на {location: 1, time: -1}
.
Когда я find()
каждая комбинация местоположения / метрики индивидуально от узла, каждый запрос полностью соответствует первому индексу, и для выполнения в монго требуется всего 1-5 мс (я знаю из проверки профилирования в журнале монго), поэтому возможно, 40 мс для всех показателей в месте. Однако непроизводительные затраты на выполнение 15 различных запросов увеличивают общее время извлечения в узле для всех метрик в местоположении до примерно 3 с на местоположение, даже если они распараллелены в 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 с при использовании этого подхода. Однако время, проведенное в монго, увеличивается на порядок - примерно до 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
каждого аспекта предоставляет все критерии запроса, в надежде, что Монго может использовать индекс внутри каждый фасет, если фасет $match
был первой встреченной инструкцией, но это оказалось намного медленнее, чем любой из вышеперечисленных подходов, потому что это было сделано полностью с помощью сканирования строк. tl; dr: Очевидно, что mongo не будет использовать индексы внутри фасета ни при каких обстоятельствах.
В настоящее время я нахожусь на mongodb 3.4 и узле 10.14.1, чего бы это ни стоило.