Node.js 使用MongoDB的用户分割引擎

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.

我有一个分析系统,跟踪客户及其属性以及他们以事件形式的行为。它是使用Node.js和MongoDB(使用Mongoose)实现的

现在我需要实现一个分段功能,允许根据特定条件将存储的用户分组到分段中。例如,
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这样的页面吗。。。我的意思是选择过滤器和它们的运算符的值。。。一