MongoDB агрегат / индекс, основанный на нескольких факторах в дате - PullRequest
0 голосов
/ 04 сентября 2018

Я ищу наиболее эффективный способ индексации / агрегирования большой коллекции телешоу, чтобы получить средние оценки для каждого шоу, которое транслировалось в определенную дату.

В идеале пользователь должен предоставить дату в формате ISO (ГГГГ-ММ-ДД), и агрегация должна возвращать список передач, которые транслировались в этот день, вместе с полем, содержащим их 30-дневные средние.

В настоящее время у меня есть 2 коллекции, Dates и Shows. Вот упрощенный пример:

// Dates (provides parent_id to link show records by date)
{ _id: 1, dataDate: 2018-09-01T00:00:00-04:00 }
{ _id: 2, dataDate: 2018-09-02T00:00:00-04:00 }

// Shows 
{  
  parent_id: 1,
  name: "SHOW A",
  start: 2018-09-01T19:00:00-04:00,
  end: 2018-09-01T20:00:00-04:00,
  rating: 100
},{ 
  parent_id: 1,
  name: "SHOW B",
  start: 2018-09-01T20:00:00-04:00,
  end: 2018-09-01T21:00:00-04:00,
  rating: 150
},{ 
  parent_id: 1,
  name: "SHOW C",
  start: 2018-09-01T21:00:00-04:00,
  end: 2018-09-01T22:00:00-04:00,
  rating: 200
}, {  
  parent_id: 2,
  name: "SHOW A",
  start: 2018-09-02T19:00:00-04:00,
  end: 2018-09-02T20:00:00-04:00,
  rating: 100
},{ 
  parent_id: 2,
  name: "SHOW B",
  start: 2018-09-02T20:00:00-04:00,
  end: 2018-09-02T21:00:00-04:00,
  rating: 150
},{ 
  parent_id: 2,
  name: "SHOW C",
  start: 2018-09-02T21:00:00-04:00,
  end: 2018-09-02T22:00:00-04:00,
  rating: 200
}

Вот мой текущий подход -

  • Поиск даты, запрошенной пользователем в Dates коллекции
  • Использовать идентификатор из записи даты для сопоставления шоу по parent_id
  • Для каждого шоу просмотрите последние 30 эпизодов (на основе имени, времени начала / окончания)
  • Сгруппируйте результаты $ lookup, используя $ avg
  • Объединить среднее поле с исходной записью шоу

Прямо сейчас это занимает значительное количество времени для завершения. У меня есть 2 индекса на коллекцию, { parent_id: 1 } и { start: -1, name: 1 }. Если я удаляю последние 3 этапа 2-го оператора сопоставления (который проверяет название шоу и час начала / окончания), он возвращается почти мгновенно. Однако мне нужно проверить эти переменные, чтобы исключить повторные запуски (показы с тем же именем, которые транслировались в разное время дня) от включения в окончательный результат. Есть ли лучший способ проиндексировать это? Или здесь есть какое-то конкретное утверждение, которое замедляет его?

let dataDate = DateTime.fromISO('2018-09-01').setZone('America/New_York');
let avgDate = DateTime.fromISO('2018-09-01').setZone('America/New_York').minus({ days: 30 });

let parent = Dates.findOne({ dataDate: dataDate.toJSDate() });

db.shows.aggregate([{
        $match: {
            parent_id: parent._id
        }
    }, {
        $lookup: {
            from: 'shows',
            let: { 
                name: '$name', 
                start: { $hour: { date: '$start', timezone: 'America/New_York' } }, 
                end: { $hour: { date: '$end', timezone: 'America/New_York' } } },
            pipeline: [{
                $match: {
                    $expr: {
                        $and: [
                            { $lt: [ '$start', dataDate.toJSDate() ] },
                            { $gte: [ '$start', avgDate.toJSDate() ] },
                            { $eq: [ '$name', '$$name' ] },
                            { $eq: [ { $hour: { date: '$start', timezone: 'America/New_York' } }, '$$start' ] },
                            { $eq: [ { $hour: { date: '$end', timezone: 'America/New_York' } }, '$$end' ] },
                        ]
                    }
                }
            }, {
                $group: {
                    _id: null,
                    averageRating: { $avg: `$$rating` }
                }
            }],
            as: 'average'
        }
    }, {
        $replaceRoot: { newRoot: { $mergeObjects: [{ $arrayElemAt: ['$average', 0] }, "$$ROOT"] } }
    }, {
        $project: {
            channel_id: 1,
            start: 1,
            end: 1,
            todayRating: `$$rating`,
            averageRating: 1,
            name: 1,
        }
    }])
...