我有一个分析系统,可以以事件的形式跟踪客户及其属性以及他们的行为。它是使用 Node.js 和 MongoDB(带有 Mongoose)实现的。
现在我需要实现一个分段功能,该功能允许根据某些条件将存储的用户分组为分段。例如,purchases > 3 AND Country = 'Netherlands'
在前端,这看起来像这样:
这里的一个重要要求是分段实时更新,而不仅仅是定期更新。这基本上意味着,每次用户的属性发生变化或触发新事件时,我都必须再次检查他属于哪些分割市场。
我当前的方法是将分段的条件存储为 MongoDB 查询,然后我可以在用户集合上执行该查询,以确定哪些用户属于某个分段。
例如,过滤掉所有使用 Gmail 的用户的分段如下所示:
{
_id: '591638bf833f8c843e4fef24',
name: 'Gmail Users',
condition: {'email': { $regex : '.*gmail.*'}}
}
当用户符合条件时,我会直接将他属于“Gmail 用户”部分的信息存储在用户的文档中:
{
username: 'john.doe',
email: '<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bbd1d4d3d595dfd4defbdcd6dad2d795d8d4d6" rel="noreferrer noopener nofollow">[email protected]</a>',
segments: ['591638bf833f8c843e4fef24']
}
但是,通过这样做,每次用户的数据发生更改时,我都必须对所有段执行所有查询,这样我就可以检查他是否是该段的一部分。从性能的角度来看,这感觉有点复杂和麻烦。
您能想到任何替代方法来解决这个问题吗?也许使用规则引擎并在应用程序中而不是在数据库中进行处理?
最佳答案
不幸的是,我不知道更好的方法,但您可以稍微优化这个解决方案。
我也会做同样的事情:
- 将分段条件存储在集合中
- 找到匹配的用户后,将分段 ID 存储在用户的文档中(
分段
)
An important requirement here is that the segments get updated in realtime and not just periodically.
您别无选择,每次段更改时都需要运行分段查询。
I would have to execute all queries for all segments every time a user's data changes
这就是我要改变你的解决方案的地方,实际上只是稍微优化一下:
您不需要对整个集合运行分段查询。如果您使用
$and
将用户 ID 放入查询中,Mongodb 将首先获取用户,然后检查其余的分段条件。您需要确保 Mongodb 使用用户的 _id 作为索引,为此您可以使用.explain()
检查它或.hint()
来强制它。不幸的是,如果您有 N 个分割,则需要运行 N+1 查询(+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 具有以下功能:_.gt、_.gte、_.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;
}
关于node.js - 使用 MongoDB 的用户分割引擎,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44088353/