Node.js 使用MongoDB的用户分割引擎
我有一个分析系统,跟踪客户及其属性以及他们以事件形式的行为。它是使用Node.js和MongoDB(使用Mongoose)实现的 现在我需要实现一个分段功能,允许根据特定条件将存储的用户分组到分段中。例如,Node.js 使用MongoDB的用户分割引擎,node.js,mongodb,filtering,analytics,rule-engine,Node.js,Mongodb,Filtering,Analytics,Rule Engine,我有一个分析系统,跟踪客户及其属性以及他们以事件形式的行为。它是使用Node.js和MongoDB(使用Mongoose)实现的 现在我需要实现一个分段功能,允许根据特定条件将存储的用户分组到分段中。例如,purchases>3和country='荷兰' 在前端,这看起来像这样: { _id: '591638bf833f8c843e4fef24', name: 'Gmail Users', condition: {'email': { $regex : '.*gmail.
purchases>3和country='荷兰'
在前端,这看起来像这样:
{
_id: '591638bf833f8c843e4fef24',
name: 'Gmail Users',
condition: {'email': { $regex : '.*gmail.*'}}
}
这里的一个重要要求是实时更新分段,而不仅仅是定期更新。这基本上意味着,每次用户的属性更改或触发新事件时,我都必须再次检查他属于哪些分段
我目前的方法是将这些段的条件存储为MongoDB查询,然后我可以在用户集合上执行这些查询,以确定哪些用户属于某个段
例如,过滤掉所有使用Gmail的用户的部分如下所示:
{
_id: '591638bf833f8c843e4fef24',
name: 'Gmail Users',
condition: {'email': { $regex : '.*gmail.*'}}
}
当用户符合条件时,我会直接在用户文档上存储他属于“Gmail用户”部分:
{
username: 'john.doe',
email: 'john.doe@gmail.com',
segments: ['591638bf833f8c843e4fef24']
}
但是,通过这样做,每次用户的数据更改时,我都必须对所有段执行所有查询,以便检查他是否是段的一部分。从性能的角度来看,这感觉有点复杂和麻烦
你能想出其他方法吗?也许可以使用规则引擎,在应用程序中而不是在数据库中进行处理?不幸的是,我不知道更好的方法,但你可以稍微优化一下这个解决方案 我也会这样做:
- 将段条件存储在集合中
- 找到匹配的用户后,将段id存储在用户文档中(
)segments
- 您不需要对整个集合运行分段查询。如果使用
将用户id放入查询中,Mongodb将首先获取用户,然后检查其余的分段条件。您需要确保Mongodb使用用户的_id作为索引,为此您可以使用它来检查或强制执行。不幸的是,如果有N个段(+1用于用户更新),则需要运行N+1查询$和
- 我将获取每个片段并将它们存储在缓存(redis)中。如果有人更改了段,我也会更新缓存。(或者只是使缓存无效,然后下一个查询将处理其余部分,具体取决于实现)。关键是,我将在不获取数据库的情况下拥有每个段,如果用户更新了记录,我将使用Node.js检查每个段,并根据条件验证用户,我可以在原始更新查询中更新用户的
数组,这样就不需要任何额外的数据库操作。 我知道实现这样的东西可能会很痛苦,但它不会使数据库过载段
module.exporst = function() {
return new Promise(resolve) {
Redis.get('cache:segments', function(err, segments) {
// handle error
// Segments are cached
if(segments) {
segments = JSON.parse(segments);
return resolve(segments);
}
//fetch segments and save it to the cache
Segments.find().exec(function(err, segments) {
// handle error
segments = JSON.stringify(segments);
// Save to the database but set 60 seconds as an expiration
Redis.set('cache:segments', segments, 'EX', 60, function(err) {
// handle error
return resolve(segments);
})
});
})
}
}
用户更新
// ...
let user = user.findOne(_id: ObjectId(req.body.userId));
// etc ...
// fetch segments from cache or from the database
let segments = yield segmentCache();
let userSegments = [];
segments.forEach(function(segment) {
if(checkSegment(user, segment)) {
userSegments.push(segment._id)
}
});
// Override user's segments with userSegments
这就是神奇发生的地方,不知何故,您需要以一种可以在if语句中使用的方式来定义条件
提示:Lodash具有以下功能:u.gt、u.gte、u.eq
检查段
module.exports = function(user, segment) {
let keys = Object.keys(segment.condition);
keys.forEach(function(key) {
if(user[key] === segment.condition[key]) {
return false;
}
})
return true;
}
您已经在segments集合中的文档中存储了整个段“查询”-为什么不在同一文档中包含一个字段,该字段将枚举用户文档中哪些字段会影响特定段中的成员资格 由于更改用户数据的操作将知道哪些字段正在更改,因此它只能获取使用正在更改的字段计算的分段,这大大减少了您必须重新运行的分段“查询”的大小
请注意,用户数据的更改可能会将其添加到当前不是其成员的段中,因此仅检查当前存储在用户中的段是不够的。谢谢您的回答!使用$and查询的想法绝对是一个很好的优化想法。关于段的缓存,我想知道如何在缓存中重新计算段?您将如何检查用户是否属于某个段。你是想在redis上还是在应用程序中使用node.js运行查询?@benjiman不客气。哈哈,这就是为什么我写这是一个痛苦的驴,因为它的实施是不平凡的。今天晚些时候我会更新我的答案,提供一些细节。太棒了,谢谢你了!我目前正在旅行,但我明天会仔细查看,并给您回复消息:)当用户因更改而需要添加到新段(但未从现有段中删除)时,这将不起作用。仅供参考:为了在我发现非常有用的条件下进行匹配。您已经将查询存储在段集合中,为什么不同时存储影响用户是否在段中的一个或多个字段?当用户的数据更改时,您知道哪些字段正在更改,只需将字段与段进行比较即可-即,与您现在的方向相反。顺便说一句,这不是“实时”-您可以获得“实时”的唯一方法结果是,如果您每次在UI中有请求时都进行分段查询。@benjiman我正在做类似的事情。。。你能告诉我如何制作一个像condition maker这样的页面吗。。。我的意思是选择过滤器和它们的运算符的值。。。一