Как $ unset вложенный объектный граф в Mongo и выставить определенную глубину объектного графа в проекции - PullRequest
1 голос
/ 31 октября 2019

Мой вопрос касается оптимизации устаревших данных во вложенном документе с использованием Mongo, я использую реализацию Java, но я не думаю, что это имеет значение.

Я использовал коллекцию stat для отслеживаниямои статистические данные за минуту, месяц, год и общее количество, и у каждой статистики есть свой собственный документ, например, именем статистики может быть «память» или «запросы», что угодно.

Вот пример ...

{
    "_id" : ObjectId("5b47269748cbb4a1e57d5f0a"),
    "stat" : "my-stat-1",
    "app" : {
        "total" : {
            "total" : NumberLong(15201),
            "yearly" : {
                "2018" : 8396,
                "2019" : NumberLong(6805)
            },
            "monthly" : {
                "Jul 2018" : 306,
                "Aug 2018" : 389,
                "Sep 2018" : 107,
                "Oct 2018" : 6959,
                "Nov 2018" : 532,
                "Dec 2018" : 103,
                "Jan 2019" : 67
            },
            "minutes" : {
                "2019-10-28T15:06" : 1,
                "2019-10-29T15:07" : 1,
                "2019-10-28T15:08" : 2,
                "2019-10-28T15:09" : 3,
                "2019-10-28T15:11" : 2,
                "2019-10-28T15:12" : 2,
                "2019-10-28T15:25" : 3,
                "2019-10-28T15:26" : 9,
                "2019-10-28T15:27" : 2,
                "2019-10-28T16:48" : 5
            }
        },
        "api-1" : {
            "total" : 713,
            "yearly" : {
                "2018" : 187,
                "2019" : 526
            },
            "monthly" : {
                "Jul 2018" : 71,
                "Aug 2018" : 77,
                "Sep 2018" : 3,
                "Nov 2018" : 12,
                "Dec 2018" : 24,
            },
            "minutes" : {}
        },
        "api-2" : {
            "total" : 3490,
            "yearly" : {
                "2018" : 1021,
                "2019" : 2469
            },
            "monthly" : {
                "Jul 2018" : 211,
                "Aug 2018" : 119,
                "Sep 2018" : 37,
                "Oct 2018" : 77,
                "Nov 2018" : 499,
                "Dec 2018" : 78,
                "Jan 2019" : 66,
            },
            "minutes" : {
                "2019-10-28T20:10" : 14,
                "2019-10-28T20:11" : 1,
                "2019-10-28T20:20" : 18,
                "2019-10-28T20:21" : 3,
                "2019-10-28T20:22" : 3,
                "2019-10-30T11:45" : 3,
                "2019-10-30T17:02" : 7,
                "2019-10-30T19:55" : 20
            }
        },
        ...
    }
}

Я пытался сфокусировать «документ», собрав все соответствующие статистические данные, я мог бы использовать коллекцию с такой структурой. .

  • дата (минутная детализация)
  • statName
  • значение

Но преимущество в моем объектно-ориентированном подходе заключалось в том, что я моглегко получить мои ежемесячные, годовые и общие статистические данные без агрегации, плюс я получаю все свои соответствующие статистические данные обратно в одном красиво упакованном документе.

Мой сервис карт просто подключится к app.total. а затем очень быстро перечислите значения - это работает хорошо.

Проблема в том, что это реальная боль, когда дело доходит до удаления устаревших минутных данных (я не планирую удалять ежемесячную или годовую статистику).

Я думаю, что сделал ошибку, чтобы сохранить статистику мелкой детализации в объекте, а не в массиве, но я действительно, действительно, не хочу сейчас менять структуру. Я хочу узнать, могу ли я эффективно работать с тем, что у меня есть.

Преимущество использования структуры объекта для статистики минут заключается в том, что я могу использовать операторы обновления $ min и $ max для условного обновлениязаписи, например, чтобы перезаписать статистику только в том случае, если новое значение больше, чем постоянная запись eixsting. Поскольку существует более одного сервера, и поскольку я не хотел выполнять чтение в первую очередь, это казалось хорошим способом справиться с этим. Вот где профессионалы, кажется, останавливаются!

Я всегда говорил, что мелкие детали гранулярности будут полезны для максимума в 2-3 дня, поэтому я ранее написал метод, который удалял минуты, перебирая мои 'сбор статистики, затем попытка удалить любые «минуты» старше двух дней.

Проблема, которую я обнаружил, заключалась в том, что я не смог найти способ подстановки вложенных полей для сброса, особенно когда я не знаюзаранее, какие ключи API существуют в документе, например что-то вроде этого ...

db.getCollection("stats").update{},{"$unset":{"app.*.minutes.2019-10-28*",""})

Я не знаю, что находится в объекте, пока не прочитал весь этот чертов документ, но на самом деле я не знаюЯ хочу, чтобы весь документ вернулся, чтобы посмотреть, что нужно удалить.

Что было бы полезно, если бы я мог создать проекцию, в которой говорилось бы: «найдите мне все дочерние узлы в приложении на максимальной глубине одного», этоПозвольте мне обнаружить имена API без загрузки во всей статистике. например, что-то вроде этого ...

db.getCollection("stats").find({},{"app.*":1})

, чтобы выставить

"app.total"
"app.api-1"
"app.api-2"

Теоретически, я мог бы построить выражение модификации $ unset для этих путей, например, какое-то поле регулярного выражения(хотя это не похоже на то, что это возможно) ...

"app.total.minutes.2019-10-28*"
"app.api-1.minutes.2019-10-28*"
"app.api-2.minutes.2019-10-28*"

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

"app.total.minutes.2019-10-28T15:01"
"app.total.minutes.2019-10-28T15:02"
"app.total.minutes.2019-10-28T15:03"
"app.total.minutes.2019-10-28T15:04"
"app.total.minutes.2019-10-28T15:05"

но тогда я должен был бы повторить, что для каждого ключа API также внутри коллекции статов 10

Мое текущее решение - загрузить полную коллекцию "my-stat-1" в память,затем я перебираю набор ключей 'app', затем перебираю набор ключей 'minutes' и, если дата старше 2 дней, я добавляю его в список, который затем $ отменяет весь список одним оператором.

Это, конечно, неэффективно, но, не меняя структуру документа, стоит ли что-то еще, что я должен рассмотреть, чтобы вообще оптимизировать удаление старых минутных записей?

1 Ответ

1 голос
/ 05 ноября 2019

Некоторые идеи о подходе, где у вас есть (1) специальное решение и (2) новая структура.

Я буду ссылаться на это поле ниже:

"minutes" : {
                "2019-10-28T15:06" : 1,
                "2019-10-29T15:07" : 1,
                ...
}

В решении ad-hoc :

Замените поле "минут" новой версией:

"minutes" : [
              {
                "date" : ISODate("2019-10-28T00:00:00Z"),
                "counts_by_min" : [
                                    {
                                      "timestamp" : "2019-10-28T20:10",
                                       "count" : 14
                                    },
                                    {
                                      "timestamp" : "2019-10-28T21:11",
                                       "count" : 6
                                    }
                                  ]
               },
               {
                 "date" : ISODate("2019-10-29T00:00:00Z"),
                 "counts_by_min" : [ ...] 
               }
]

Обратите внимание, что поле минутмассив с набором временных меток для каждого дня (или, при необходимости, это может быть диапазон дат). Поле «timestamp» и «count» содержат те же данные, что и исходные данные документа. Лучше, если отметка времени также является полем даты.

Теперь, (1) как мы получаем данные в коллекцию, и (2) как мы их обрабатываем? Как это помогает?

Аспект обработки станет проще (см. Пример кода ниже).

Способ передачи данных в коллекцию и использования с другими приложениями, я не могу сказать,;У меня нет никакой информации, связанной с этим. Но я предлагаю ввести это новое поле «минут» параллельно (вместе с существующей версией) и работать с остальными приложениями, чтобы применять их поэтапно. Я предполагаю, что это будет какой-то переход.


Процесс:

Запрос потребует ввода даты, на основе которой данные "минут" будутбыть удаленным из входных документов.

входной документ (на основе опубликованного оригинала; пропуски некоторых полей):

   {
        "_id" : 1,
        "stat" : "my-stat-1",
        "app" : {
                "total" : {
                        "monthly" : {
                                "Jul 2018" : 306
                        },
                        "minutes" : [ ]
                },
                "api-1" : {
                        "monthly" : {
                                "Jul 2018" : 71
                        },
                        "minutes" : [
                                {
                                        "date" : ISODate("2019-10-28T00:00:00Z"),
                                        "counts_by_min" : [
                                                {
                                                        "timestamp" : "2019-10-28T20:10",
                                                        "count" : 14
                                                },
                                                {
                                                        "timestamp" : "2019-10-28T21:11",
                                                        "count" : 6
                                                }
                                        ]
                                },
                                {
                                        "date" : ISODate("2019-10-29T00:00:00Z"),
                                        "counts_by_min" : [
                                                {
                                                        "timestamp" : "2019-10-29T20:10",
                                                        "count" : 14
                                                }
                                        ]
                                }
                        ]
                }
        }
}

дата вводаи запрос:

Обратите внимание, что запрос обновления является исходным документом. И запрос запускается из оболочки.

var MINS_FILTER = ISODate("2019-10-28T00:00:00Z");

db.stats3.aggregate( [
  { $addFields: { app: { $objectToArray: "$app" } } },
  { $unwind: "$app" },
  { $addFields: { "app.v.minutes": { $filter: {
                                                input: "$app.v.minutes",
                                                as: "mins",
                                                cond: { $ne: [ "$$mins.date", MINS_FILTER ] }
  } } } },
  { $group: { _id: { stat: "$stat", _id: "$_id" }, app: { $push: { k: "$app.k", v: "$app.v" } } } },
  { $addFields: { _id: "$_id._id", stat: "$_id.stat", app: { $arrayToObject: "$app" } } },
]).forEach( doc => db.stats3.save(doc) );

В обновленном документе в коллекции удалены данные дня (ISODate("2019-10-28T00:00:00Z")).

{
        "_id" : 1,
        "app" : {
                "total" : {
                        "monthly" : {
                                "Jul 2018" : 306
                        },
                        "minutes" : [ ]
                },
                "api-1" : {
                        "monthly" : {
                                "Jul 2018" : 71
                        },
                        "minutes" : [
                                {
                                        "date" : ISODate("2019-10-29T00:00:00Z"),
                                        "counts_by_min" : [
                                                {
                                                        "timestamp" : "2019-10-29T20:10",
                                                        "count" : 14
                                                }
                                        ]
                                }
                        ]
                }
        },
        "stat" : "my-stat-1"
}



Примечание к дизайну:

В шаблоне Bucket используется концепция группировки данных в «корзины». Это очень полезно в приложениях IoT. Это также характерно для гибких данных схемы MongoDB и ее дизайна. Я думаю, что это имеет отношение к вашей ситуации, если вы планируете реструктурировать данные.

Подробнее здесь: Шаблон дизайна ковша .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...