node.js - 使用 MongoDB 的用户分割引擎

标签 node.js mongodb filtering analytics rule-engine

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

现在我需要实现一个分段功能,该功能允许根据某些条件将存储的用户分组为分段。例如,purchases > 3 AND Country = 'Netherlands'

在前端,这看起来像这样:

enter image description here

这里的一个重要要求是分段实时更新,而不仅仅是定期更新。这基本上意味着,每次用户的属性发生变化或触发新事件时,我都必须再次检查他属于哪些分割市场。

我当前的方法是将分段的条件存储为 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/

相关文章:

node.js - Mongoose 保存方法不起作用

linux - tcpdump - 如何根据 tcp 连接时间/持续时间进行过滤

javascript - Firebase 实时数据库分页

node.js - 等待返回 undefined object 的 Promise.resolve?

node.js - 使用node.js utf8 console.log 输出

mongodb - 在运行 Mocha 测试之前删除 MongoDB 数据库

node.js - METEOR - 是否有处理人口的机制

javascript - 预期收到 SlashCommandChannelOption 构建器,但未定义

node.js - 如何在新的 Grunt 0.4 中编写助手

haskell - 向 map 函数添加谓词