Keep the last N user notifications related to a user ID.
The advancedKeepLastN
function:
-
Demonstrates how to keep a user record with the last N activities
-
Requires Eventing Storage (or a metadata collection) and a source collection
-
Requires a binding of type
bucket alias
-
Operates on any mutation where the KEY starts with
nu:
in the formnu:#:#
-
The KEY
nu:#:#
has 2 numbers. The first is an increasing notification number and the second is the user ID.
-
-
Only keeps N records for each user
-
Removes the earliest notification record for a user whenever a new record is inserted for that user
The following example assumes that N always increases across time and ignores any duplicates. It keeps only the 3 most recent notifications for each user ID.
-
advancedKeepLastN
-
Input data
-
Output data
There are two variants of this function available: a Couchbase Server version 6.6 that implements userspace CAS, and a Couchbase Server version 6.6.1+/7.0.0+ that uses true CAS.
The following example uses true CAS.
// Configure the settings for the advancedKeepLastN function as follows:
//
// Version 7.1+
// "Function Scope"
// *.* (or try bulk.data if non-privileged)
// Version 7.0+
// "Listen to Location"
// bulk.data.source
// "Eventing Storage"
// rr100.eventing.metadata
// Binding(s)
// 1. "binding type", "alias name...", "bucket.scope.collection", "Access"
// "bucket alias", "src_col", "bulk.data.source", "read and write"
//
// Version 6.6.1
// "Source Bucket"
// source
// "MetaData Bucket"
// metadata
// Binding(s)
// 1. "binding type", "alias name...", "bucket", "Access"
// "bucket alias", "src_col", "source", "read and write"
/*
* Process all mutations; updateNotifyArrayInKV(...) only processes data with KEYS like nu:#:#
*/
function OnUpdate(doc, meta) {
const MAX_ARRAY = 3; // Keep 'N' items
const DEBUG = false; // If true, the debug log can be too long
updateNotifyArrayInKV(doc, meta, MAX_ARRAY, DEBUG);
}
/*
* Manipulate the in-memory document to only keep 'MAX_ARRAY' items
*/
function addToNtfyArray(user_doc, user_id, insert_json, MAX_ARRAY, DEBUG) {
var ntfy_id = insert_json.nid;
if (user_doc.notifications && user_doc.notifications[0] &&
user_doc.notifications[0].nid >= ntfy_id &&
user_doc.notifications.length === MAX_ARRAY) {
// Do nothing; this is older data
return null;
} else {
// Find the 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 duplicated data
if (DEBUG) log('Ignore DUP ntfy_id', ntfy_id, 'user_id', user_id, 'insert_json', insert_json);
return null;
}
if (i == user_doc.notifications.length || user_doc.notifications[i].nid > ntfy_id) {
// Add to middle or end of array
user_doc.notifications.splice(i, 0, insert_json);
break;
}
}
}
while (user_doc.notifications.length > MAX_ARRAY) {
// Ensure proper size
user_doc.notifications.shift();
}
return user_doc;
}
/*
* Creates, gets, and updates (via replace) the KV tracking array document
*/
function updateNotifyArrayInKV(doc, meta, MAX_ARRAY, DEBUG) {
// 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
};
// In version 6.6.1, use CAS in Eventing to avoid race conditions
var res = null;
var req_id = "user_plus_ntfys:" + user_id;
var req_meta = {
id: req_id
};
var user_doc = null;
var user_meta = null;
while (res === null) {
res = couchbase.get(src_col, req_meta);
if (DEBUG) log('couchbase.get(src_col,', req_meta, ') success==' + res.success, res);
if (res.success) {
user_doc = res.doc;
// EXAMPLE user_meta
// {"id":"user_plus_ntfys:2","cas":"1609965779014844416","data_type":"json"}
user_meta = res.meta;
} else {
if (!res.error.key_not_found) {
// Do nothing; this is a big error
log("FAILED to insert id: " + meta.id, doc, 'res', res)
return;
}
// Create the document and initialize it
user_doc = {
"type": "user_plus_ntfys",
"id": user_id,
"notifications": []
};
res = couchbase.insert(src_col, req_meta, user_doc);
if (DEBUG) log('couchbase.insert(src_col,', req_meta, user_doc, ') success==' + res.success, res);
// Redo the loop to force couchbase.get
res = null;
}
if (res !== null) {
// Successful couchbase.get(...) for both user_doc and user_meta
// Manipulate the copy of the user_doc to keep only MAX_ARRAY
var new_doc = addToNtfyArray(user_doc, user_id, insert_json, MAX_ARRAY, DEBUG);
if (new_doc == null) {
// Ignore or skip duplicated data
break;
}
// Try to replace the user_doc with new_doc; pass CAS to test for race conditions
res = couchbase.replace(src_col, user_meta, new_doc);
if (DEBUG) log('couchbase.replace(src_col,', user_meta, new_doc, ') success==' + res.success, res);
if (res.success) {
// CAS matches and operation is successful
break;
} else {
// Redo loop and try again
res = null;
}
}
}
}
Create a new test document set using the Query Editor to insert the data items. You do not need an Index.
key | data |
---|---|
nu:1:1 |
{"somekey":"someValue"} |
nu:2:2 |
{"somekey":"someValue"} |
nu:3:1 |
{"somekey":"someValue"} |
nu:4:1 |
{"somekey":"someValue"} |
nu:5:1 |
{"somekey":"someValue"} |
nu:6:2 |
{"somekey":"someValue"} |
nu:7:2 |
{"somekey":"someValue"} |
nu:8:1 |
{"somekey":"someValue"} |
nu:9:2 |
{"somekey":"someValue"} |
nu:10:2 |
{"somekey":"someValue"} |
UPSERT INTO `bulk`.`data`.`source` (KEY,VALUE)
VALUES ( "nu:1:1", {"somekey":"someValue"} ),
VALUES ( "nu:2:2", {"somekey":"someValue"} ),
VALUES ( "nu:3:1", {"somekey":"someValue"} ),
VALUES ( "nu:4:1", {"somekey":"someValue"} ),
VALUES ( "nu:5:1", {"somekey":"someValue"} ),
VALUES ( "nu:6:2", {"somekey":"someValue"} ),
VALUES ( "nu:7:2", {"somekey":"someValue"} ),
VALUES ( "nu:8:1", {"somekey":"someValue"} ),
VALUES ( "nu:9:2", {"somekey":"someValue"} ),
VALUES ( "nu:10:2", {"somekey":"someValue"} );
NEW/OUTPUT: KEY user_plus_ntfys:1
{
"type": "user_plus_ntfys",
"id": 1,
"notifications": [{
"nid": 4,
"doc": {
"somekey": "someValue"
}
}, {
"nid": 5,
"doc": {
"somekey": "someValue"
}
}, {
"nid": 8,
"doc": {
"somekey": "someValue"
}
}],
"random": 0.9071605464143964
}
NEW/OUTPUT: KEY user_plus_ntfys:2
{
"type": "user_plus_ntfys",
"id": 2,
"notifications": [{
"nid": 7,
"doc": {
"somekey": "someValue"
}
}, {
"nid": 9,
"doc": {
"somekey": "someValue"
}
}, {
"nid": 10,
"doc": {
"somekey": "someValue"
}
}]
}