我的问题是使用Mongo优化嵌套文档中陈旧的数据,我使用的是Java实现,但我认为这并不重要。
我使用了一个' stat‘集合来记录我的分钟、月、年和总统计数据,并且每个stat都有自己的文档,例如,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
}
},
...
}
}我试图保持“文档”的焦点,通过将所有相关的统计数据放在一起,我可以使用具有这种结构的集合.
但是,基于对象的方法的优点是,我可以轻松地获得我的月、年和总统计数据,而不需要聚合,而且我还可以在一个很好的包文档中获得所有适用的统计数据。
我的图表服务只需连接到app.total。然后非常快地枚举这些值--它运行得很好。
问题是,这是一个真正的痛苦,当涉及删除陈腐的分钟数据(我没有计划删除每月或每年的统计数据)。
我想我犯了一个错误,我把我的微小粒度统计数据存储在一个对象中,而不是一个数组中,但是我真的,真的不想在这个时候改变结构。我想知道我是否能有效地运用我所拥有的东西。
为“分钟”stat使用对象结构的优点是,我可以使用$min和$max更新运算符有条件地更新记录,例如,只有在新值高于异常持久记录时才覆盖统计数据。因为有不止一台服务器,而且我不想先执行读操作,这似乎是一种很好的处理方法。这是专业人士似乎停止的地方!
我一直说,分钟粒度统计数据将有助于最大2-3天,所以我以前写了一个方法,删除了分钟,通过迭代我的‘统计’集合,然后试图清除任何‘分钟’大于两天。
我发现的问题是我找不到一种未设置嵌套字段的通配符方法,特别是当我事先不知道文档中存在哪些API键时,例如这样的.
db.getCollection("stats").update{},{"$unset":{"app.*.minutes.2019-10-28*",""})在读取整个该死的文档之前,我不知道对象中有什么内容,但实际上,我不希望整个文档返回,只是为了看需要删除什么。
有用的是,如果我能够创建一个投影,说“在app下找到所有的子节点到一个最大深度”,这将使我能够在不加载所有统计数据的情况下发现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*"但是考虑到regex问题,我可能得用水合物把这些水补充到$unset 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键,我必须在stat集合中重复这一点。
我目前的解决方案是将完整的" My -stat-1“集合加载到内存中,然后遍历'app‘密钥集,然后遍历’my‘键集,如果日期超过2天,我将其添加到一个列表中,然后在一个语句中对整个列表进行$unset。
当然,这是低效的,但在不改变文档结构的情况下,我还需要考虑什么来一般性地优化旧的分钟记录的删除吗?
发布于 2019-11-05 14:46:59
关于一种方法的一些想法,其中你有一个(1)特别的解决方案,和(2)一个新的结构。
我将在下面提及这一领域:
"minutes" : {
"2019-10-28T15:06" : 1,
"2019-10-29T15:07" : 1,
...
}在临时解决方案中:
将“分钟”字段替换为更新版本的“分钟”字段:
"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" : [ ...]
}
]需要注意的是,分钟字段是一个数组,每天都有一桶时间戳(或者这可以是一个日期范围,视需要而定)。“时间戳”字段和“计数”具有与原始文档数据相同的数据。如果时间戳也是一个日期字段,则更好。
现在,(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
}
]
}
]
}
}
}输入日期和查询:
注意,查询更新是原始文档。并且,查询从shell中运行。
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"
}关于设计的注记:
桶设计模式使用将数据组合成“桶”的概念。这在IoT应用程序中非常有用。它还专门用于MongoDB的灵活模式数据及其设计。我认为这与你的情况有关,以防你计划重组数据。
更多信息在这里:桶型设计模式。
https://stackoverflow.com/questions/58633671
复制相似问题