我有一个巨大的存储桶,其中包含所有用户的通知数据。 像这样:
┌────┬─────────┬─────────────────────────┐
│ 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
,则不会删除(
@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"} │
└──────────┴─────────────────────────┘
现在,我们可以使用带有参数的 Eventing 函数,例如 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;
}
可能有更好的数据建模方法。所有这些数据是否需要放在单独的文档中?如果"N"是一个相对较小的数字,则可以将所有这些放入单个文档中的数组中。喜欢:
{
"type": "user",
"name": "ehsan",
"notifications" : [
{"somekey":"someValue"},
{"somekey":"someValue"},
{"somekey":"someValue"}
]
}
那么这个过程将是:
- 获取文档
- 将记录添加到通知数组
- 确定是否需要删除旧记录(然后将其删除(
- 保存更新的文档。
此方法具有简单且无需更新多个数据的优点。你建模它的方式可以工作,但你需要ACID事务(在Couchbase的Node SDK中尚不可用(或者一个事件函数来检查,以确保每个用户在创建新通知时没有太多的通知文档。