1

我有一个包含所有用户通知数据的巨大存储桶。像这样:

┌────┬─────────┬─────────────────────────┐
│ id │ user_id │          data           │
├────┼─────────┼─────────────────────────┤
│  1 │       1 │ {"somekey":"someValue"} │
│  2 │       2 │ {"somekey":"someValue"} │
│  3 │       1 │ {"somekey":"someValue"} │
│  4 │       1 │ {"somekey":"someValue"} │
│  5 │       1 │ {"somekey":"someValue"} │
│  6 │       2 │ {"somekey":"someValue"} │
│  7 │       2 │ {"somekey":"someValue"} │
│  8 │       1 │ {"somekey":"someValue"} │
│  9 │       2 │ {"somekey":"someValue"} │
│ 10 │       2 │ {"somekey":"someValue"} │
└────┴─────────┴─────────────────────────┘

所以,每当我想插入一条新记录时,例如user_id=2,我想删除最早的记录,以便每个用户user_id=2只有N记录(当然,如果记录总数少于N,则不会删除)

4

2 回答 2

2

@ehsan,另一种选择是使用事件服务并将您的文档提供给事件功能。您可以使用 id(用于通知)和 user_id 的复合键。

例如,我使用“nu:#:#”形式的键。然后 Eventing 将处理您的数据或通知,以构建像 @MatthewGroves 提议的用户文档。

事实上,您可以选择在成功添加输入文档后删除它们。

考虑您的输入键和文档,如下所示:

┌──────────┬─────────────────────────┐
│ key      │  data                   │
├──────────┼─────────────────────────┤
│  nu:1:u1 │ {"somekey":"someValue"} │
│  nu:2:u2 │ {"somekey":"someValue"} │
│  nu:3:u1 │ {"somekey":"someValue"} │
│  nu:4:u1 │ {"somekey":"someValue"} │
│  nu:5:u1 │ {"somekey":"someValue"} │
│  nu:6:u2 │ {"somekey":"someValue"} │
│  nu:7:u2 │ {"somekey":"someValue"} │
│  nu:8:u1 │ {"somekey":"someValue"} │
│  nu:9:u2 │ {"somekey":"someValue"} │
│ nu:10:u2 │ {"somekey":"someValue"} │
└──────────┴─────────────────────────┘

现在我们可以使用带有参数的事件函数 MAX_ARRAY = 3(调整为您想要的)来控制每个用户保留的最大通知数量。

注意我还添加了一个参数 MAX_RETRY = 16 以在存在争用时重试该操作(通过检查包含 Math.random() 的字段来完成一个穷人的 CAS)。

我假设通知 id 总是递增,因为 JavaScript 处理 2^53 -1(或 9,007,199,254,740,991)这不应该是一个问题。

一个有效的事件函数如下所示:

/*
KEY nu:10:2 // Example input document where 10 is the notify_id 2 is the user_id
{
    "somekey": "someValue"
}

KEY user_plus_ntfys:2 // Example output doc where "id": 2 is the user_id built from above
{
   "type": "user_plus_ntfys",
   "id": 2,
   "notifications" : [
     {"nid": 7, "doc": { "somekey": "someValue"}},
     {"nid": 9, "doc": { "somekey": "someValue"}},
     {"nid": 10, "doc": { "somekey": "someValue"}}
   ]
}
*/

function OnUpdate(doc, meta) {
    const MAX_RETRY = 16;
    const MAX_ARRAY = 3;
    
    // will process ALL data like nu:#:#
    var parts = meta.id.split(':');
    if (!parts || parts.length != 3 || parts[0] != "nu") return;
    var ntfy_id = parseInt(parts[1]);
    var user_id = parseInt(parts[2]);
    //log("Doc created/updated " +  meta.id + " ntfy_id " + ntfy_id  + " user_id " + user_id);

    var insert_json = {"nid": ntfy_id, doc};
    for (var tries=0; tries < 16; tries++) {
        var user_doc = addToNtfyArray(src_bkt, user_id, insert_json, MAX_ARRAY);
        if (user_doc == null) {
            // do nothing
            return;
        }
        var random_csum = user_doc.random;
        // this is a read write alias to the functons source bucket
        src_bkt["user_plus_ntfys:" + user_id] = user_doc;
        user_doc = src_bkt["user_plus_ntfys:" + user_id];
        if (random_csum !== user_doc.random) {
            // failure need to retry
            tries++;
        } else {
            // success could even delete the input notification doc here
            return;
        }
    }
    log ("FAILED to insert id: " + meta.id, doc)
}

function addToNtfyArray(src_bkt, user_id, insert_json, max_ary) {
    var ntfy_id = insert_json.nid;
    var random_csum;
    var user_doc = src_bkt["user_plus_ntfys:" + user_id];
    if (!user_doc) {
        // generate unique random #
        random_csum = Math.random();
        user_doc = { "type": "user_plus_ntfys", "id": user_id, "notifications" : [], "random": random_csum };
        user_doc.notifications.push(insert_json);
    } else {
        if (user_doc.notifications[0].nid >= ntfy_id && user_doc.notifications.length === max_ary) {
            // do nothing this is older data, we assume that nid always increases
            return null;
        } else {
            // find insert position
            for(var i=0; i<=user_doc.notifications.length + 1 ; i++) {
                if (i < user_doc.notifications.length && user_doc.notifications[i].nid === ntfy_id) {
                    // do nothing this is duplicate data we already have it, assume no updates to notifys
                    return null;
                }  
                if (i == user_doc.notifications.length || user_doc.notifications[i].nid > ntfy_id) {
                    // add to array middle or end
                    user_doc.notifications.splice(i, 0, insert_json);
                    random_csum = Math.random();
                    // update unique random #
                    user_doc.random = random_csum;
                    break;
                }
            }
        }
        while (user_doc.notifications.length > max_ary) {
            // ensure proper size
            user_doc.notifications.shift();
        }
    }
    return user_doc;
}
于 2020-07-08T00:46:14.717 回答
0

可能有更好的数据建模方法。所有这些数据都需要放在单独的文档中吗?如果“N”是一个相对较小的数字,您可以将所有这些放入单个文档中的数组中。喜欢:

{
   "type": "user",
   "name": "ehsan",
   "notifications" : [
     {"somekey":"someValue"},
     {"somekey":"someValue"},
     {"somekey":"someValue"} 
   ]
}

那么这个过程将是:

  1. 获取文档
  2. 向通知数组添加一条记录
  3. 确定是否需要删除旧记录(然后将其删除)
  4. 保存更新的文档。

这种方法的优点是简单且不需要更新多条数据。您对其建模的方式可以工作,但您需要 ACID 事务(Couchbase 的 Node SDK 中尚不可用)或者可能需要一个 Eventing 函数来检查以确保每个用户在任何时候都没有太多的通知文档创建一个新的通知。

于 2020-06-09T13:26:53.380 回答