Разработка схемы временных рядов с MongoDB - PullRequest
0 голосов
/ 17 сентября 2018

Я использую простой Express.js для сбора и анализа аналитики с нескольких тысяч устройств по всему миру.Наш уровень данных построен с использованием MongoDB.

Каждое устройство подключается к серверу с помощью RESTful API как:

app.post('/collect', async (req,res) => {
  const {
    device,
    device_id,
    country, 
    type
    // and more
  } = req.body;
  try {
    await saveToDB({
      device,
      device_id,
      type
    });
    res.json({status: 'OK'});
  } catch (error) {
    res.json({status: 'ERROR'});
  }
});

Каждое устройство сообщает о наборе данных, крошечное подмножество которого равно:

  • устройство (мобильное, планшетное)
  • страна (страна, в которой находится устройство)
  • тип (тип зарегистрированного события)

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

  • С 13 мая по 4 сентября какое устройство регистрировало больше данных?
  • С февраля по ноябрь какие события регистрировались?
  • Какая страна наиболее популярна для устройства с идентификатором, равным 2?

Поскольку мы хотим, чтобы все было быстро и аккуратно для ведения журналов и агрегирования, мы предложили несколько сценариев (предупреждение о спойлере: мы считаем, что сценарий № 3 работает) .

Сценарий №1: Зарегистрируйте каждый запрос в плоской коллекции как:

{device: 'FOO', device_id: 1,country: 'USA',type: 'LOG', timestamp: 1537214920518},
{device: 'BAR', device_id: 1,country: 'UK',type: 'LOG', timestamp: 1537214920518},

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

Сценарий нет2 Распределение коллекции временных рядов по метрике: нам нужно будет создать коллекцию для устройств, типов, стран и т. Д.Используя этот учебник , а также официальные документы MongoDB о временных рядах , мы можем регистрировать прогрессивно увеличивающуюся запись по метрике в месяц и день.

Наш вызов API имеетдля выполнения большого количества записей для каждой коллекции, и мы должны запросить несколько коллекций для каждого отчета следующим образом:

app.post('/collect', (req,res) => {
    try{
    saveToDevices(req.body);
    saveToCountries(req.body);
    res.json({status: 'OK'});
  } catch (error) {
    res.json({status: 'OK'});
  }
});

Пример коллекции страновых метрик будет:

{
  month: ISODate("2018-08-01T00:00:00.000Z"),
  country: 'US',
  device_id: 1,
  count: 100,
  days: [{
      date: ISODate("2018-08-18T00:00:00.000Z"),
      count: 10}]
}

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

Сценарий № 3 Это оригинальный подход, которому я склонен следовать.Мы используем документ на устройство в месяц с вложенными вложенными документами с разбивкой по дням.

{
  month: ISODate("2018-08-01T00:00:00.000Z"),
  device_id: 1,
  count: 100, // total hits per month
  days: [{
      date: ISODate("2018-08-18T00:00:00.000Z"),
      countries: [{
        name: 'US',
        count: 100
      }],
      devices: [{
        name: 'mobile',
        count: 100
      }],
      count: 10 //total hits for that day
  }]
}

Этот подход выглядит превосходным, он требует одной записи на каждое событие журнала, и мы можем фактически лениво предварительно выделить каждый документ.Тем не менее, у нас есть несколько проблем.Каждый документ может значительно, особенно потому, что мы собираем несколько десятков точек данных.Кроме того, будем ли мы собирать вложенные значения в массив объектов или более уместно обобщать эти точки данных в виде простых ключей?Например, записи стран будут выглядеть следующим образом:

days: [{
  date: ISODate("2018-08-18T00:00:00.000Z"),
  countries: {
    'UK': 100,
    'US': 200
  }
}]

Является ли вышеуказанный подход более масштабируемым?Так как ключи не предопределены, агрегация будет сложнее?Можно ли добавить индексы для повышения производительности, поскольку ключи для каждой страны не определены заранее?Любая помощь, совет, советы или аналогичный проект с открытым исходным кодом будут высоко оценены.Заранее спасибо и извиняюсь за довольно длинный вопрос.

...