---
title: "Function: Advanced Keep the Last N User Items"
description: Keep the last N user notifications related to a user ID.
pubDate: 2026-08-17T09:53:44.266Z
antora:
  editUrl: https://github.com/couchbaselabs/docs-devex/edit/release/8.0/modules/eventing/pages/eventing-handler-advanced-keepLastN.adoc
  xref: xref:server:eventing:eventing-handler-advanced-keepLastN.adoc[]
---

[Consult the llms.txt file for a full list of contents](/llms.txt)
[View original HTML](/server/current/eventing/eventing-handler-advanced-keepLastN.html)

# Function: Advanced Keep the Last N User Items

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 form `nu:#:#`

  * 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](eventing-handler-keepLastN.md), and a Couchbase Server version 6.6.1+/7.0.0+ that uses true CAS.

The following example uses true CAS.

```javascript
// 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"} |

```sqlpp
  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"} );
```

```json
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"
    }
  }]
}
```