背景
我们的系统是运营商级的并且非常健壮,它已经过负载测试,每秒可以处理 5000 个事务,并且对于每个事务,一个文档被插入到单个 MongoDB 集合中(在这个应用程序中没有更新或查询,它是只写的)。这相当于每天大约 700 毫米的文档,这是我们的基准。
MongoDB 部署尚未分片,我们有 1 个副本集,其中 1 个主服务器和 2 个从属服务器,所有这些都是 ec2 上的 m2.2xlarge 类型实例。每个实例都由一个 1TB RAID0 条带支持,该条带由 8 个卷(无 PIOPS)组成。我们将 node-mongodb-native 驱动程序与 c++ 本机 BSON 解析器一起使用,以获得最佳写入性能,并尝试相应地对文档结构进行建模。
笔记
- 文档很小(120 字节)
- 该文档包括一个“时间桶”(h[our]、d[ay]、m[onth]、y[ear])以及“t[ime]”字段
- 我们在集合上有一个索引,可以通过“c[customer]”和“a”查询,这是一个高度随机但非唯一的标签
- 我们已经研究了将数据划分为单独的集合,尽管在此示例中所有数据都是热的。
- 我们也在研究预聚合,尽管这无法实时完成。
要求
- 对于报告,我们需要计算每个月唯一“a”标签的数量,以及在任何给定时期内按客户划分的总数
- 一份报告需要大约 60 秒来运行存储超过 2 小时的 9.5MM 文档的样本(完整集合)。详情如下:
文档
{
_id: ObjectID(),
a: ‘string’,
b: ‘string’,
c: ‘string’ or <int>,
g: ‘string’ or <not_exist>,
t: ISODate(),
h: <int>,
d: <int>,
m: <int>,
y: <int>
}
指数
col.ensureIndex({ c: 1, a: 1, y: 1, m: 1, d: 1, h: 1 });
聚合查询
col.aggregate([
{ $match: { c: 'customer_1', y: 2013, m: 11 } },
{ $group: { _id: { c: '$c', y: '$y', m: '$m' }, a: { $addToSet: '$a' }, t: { $sum: 1 } } },
{ $unwind: '$a' },
{ $group: { _id: { c: '$_id.c', y: '$_id.y', m: '$_id.m', t: '$t' }, a: { $sum: 1 } } },
{ $sort: { '_id.m': 1 } },
{
$project: {
_id: 0,
c: '$_id.c',
y: '$_id.y',
m: '$_id.m',
a: 1,
t: '$_id.t'
}
},
{ $group: { _id: { c: '$c', y: '$y' }, monthly: { $push: { m: '$m', a: '$a', t: '$t' } } } },
{ $sort: { '_id.y': 1 } },
{
$project: {
_id: 0,
c: '$_id.c',
y: '$_id.y',
monthly: 1
}
},
{ $group: { _id: { c: '$c' }, yearly: { $push: { y: '$y', monthly: '$monthly' } } } },
{ $sort: { '_id.c': 1 } },
{
$project: {
_id: 0,
c: '$_id.c',
yearly: 1
}
}
]);
聚合结果
[
{
"yearly": [
{
"y": 2013,
"monthly": [
{
"m": 11,
"a": 3465652,
"t": 9844935
}
]
}
],
"c": "customer_1"
}
]
63181ms
聚合解释
{
"cursor" : "BtreeCursor c_1_a_1_y_1_m_1_d_1_h_1",
"isMultiKey" : false,
"n" : 9844935,
"nscannedObjects" : 0,
"nscanned" : 9844935,
"nscannedObjectsAllPlans" : 101,
"nscannedAllPlans" : 9845036,
"scanAndOrder" : false,
"indexOnly" : true,
"nYields" : 27,
"nChunkSkips" : 0,
"millis" : 32039,
"indexBounds" : {
"c" : [ [ "customer_1", "customer_1" ] ],
"a" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ],
"y" : [ [ 2013, 2013 ] ],
"m" : [ [ 11, 11 ] ],
"d" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ],
"h" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ]
}
}
问题
鉴于插入的频率很高,并且我们需要随着时间的推移执行范围聚合查询。考虑到应用程序可以在一个小时内插入 30MM 文档,时间桶是一种好的做法吗?
我们了解到 MongoDB 可以在几秒钟内查询数十亿个文档:
- 我们对 9.5MM 文档的聚合查询肯定会在 1 秒左右返回吗?
- 我们是否使用了正确的技术来实现这一点?如果不是,我们应该将精力集中在哪里,以便几乎立即获得报告结果?
- 在这个阶段是否可以不分片?
MapReduce(并行)会是更好的选择吗?