我使用一个简单的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每秒消耗数千个事件日志,因此我们希望有效地聚合数千个客户端的数据,以生成具有每月和每日粒度的聚合结果的自定义报告。我们要生成的报告的一些示例是:
由于我们希望保持所有内容的快速和整洁,以便进行日志记录和聚合,因此我们提出了一些方案(扰流警报:我们认为方案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种情况 为每个指标分配一个时间序列集合:我们将必须为设备,类型,国家等创建一个集合。使用此tutorial和官方MongoDB docs about time-series,我们可以记录每月和每天每个指标的递增记录。
我们的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
}
}]
以上方法是否更具扩展性? 由于密钥不是预定义的,聚合会更困难吗? 由于未预定义每个国家/地区的密钥,是否可以添加索引以提高性能? 任何帮助,建议,技巧或类似的开源项目将不胜感激。 预先感谢您为这个很长的问题表示歉意。