+

    Timers are asynchronous compute, which offers Eventing Functions the ability to execute in reference to wall-clock events. Timers also measure and track the amount of elapsed time and can be used while archiving expired documents at a preconfigured time.

    When using timers, it is required that all nodes of the cluster are synchronized at computer startup, and periodically afterwards using a clock synchronization tool like NTP.

    A few important aspects related to timers are listed below:

    • Timers follow the same timeout semantics as their Parent Functions. So, if an Eventing Function has an execution timeout of 60 seconds, each of the timers created from the Eventing Function inherits the same execution timeout value of 60 seconds.

    • Timers may run on a different node than the one on which it was created.

    • One execution of timers is guaranteed despite node failures and cluster rebalances.

    • During Function backlogs, timers get eventually executed.

    • The Eventing Storage (or Metadata) collection stores information about timers and its association with an Eventing Function’s handler.

    • Ensure that the Eventing Storage collection is not deleted or flushed, or the keys in Eventing Storage collection get updated.

    • Each active timer an additional amount of space between 832 and 1856 bytes (832 bytes + sizeof(context)) is needed. Where the context by default is not larger than 1024 bytes.

    • Timers are represented by two documents in KV plus a common tiny root document for that is shared among 1-N timers scheduled to fire in the same 7 second period of time. Thus two or three inserts and then two or three deletes must be performed in KV for each timer (regardless if the timer is ultimately fired or canceled).

    • The timer context size may be adjusted up or down on a per Function handler basis in the Function’s settings.

    • With an increase in the usage of timers, the Eventing Storage collection’s space assignment must also be increased to accommodate all active timers and any potential backlog. If the use-case mandates large numbers of timers in the system, it is required that the space assigned to the Eventing Storage collection be suitably high as well.

    • Due to runtime or programmatic errors in the Function handler code, if triggering of a timer fails, then timer execution may get permanently blocked.

    • Bindings for Bucket aliases to keyspaces can be reused in timers. Bucket aliases, created during the Eventing Function definition, can be accessed by the timer constructs in the handler code.

    • Timers get deleted (and thus all KV documents that define them) when the associated Function is deleted or undeployed.

    The Timer Construct

    The Timers language construct is added to support requirements of Couchbase Eventing Functions.

    To create a timer use the below syntax:

    createTimer(callback, date, reference, context)

    In the createTimer syntax:

    • callback - This function is called when the timer fires. The callback function must be a top-level function that takes a single argument, the context (see below).

    • date - This is a JavaScript Date object representing the time for the timer to fire. The date of a timer must always be in future when the timer is created, otherwise the behavior is unspecified.

    • reference - This is a unique string that must be passed in to help identify the timer that is being created. References are always scoped to the function and callback they are used with and need to be unique only within this scope. The call returns the reference string if timer was created successfully. If multiple timers are created with the same unique reference, old timers with the same unique reference are implicitly cancelled. If the reference parameter is set to JavaScript null value, a unique reference will be generated.

    • context - This is any JavaScript object that can be serialized. The context specified when a timer is created is passed to the callback function when the timer fires. The default maximum size for Context objects is 1kB. Larger objects would typically be stored as keyspace objects, and document key can be passed as context.

    • return value - If the reference parameter was null, this call returns the generated unique reference. Otherwise, the passed in reference parameter is the return value.

    • Exceptions Thrown - The createTimer() function throws an exception if the timer creation fails for an unexpected reason, such as an error writing to the Eventing Storage collection.

    To cancel a timer you can either use createTimer() with the same reference of an existing timer or you can use the below syntax:

    cancelTimer(callback, reference)

    In the cancelTimer syntax:

    • callback - This function that was scheduled to be called when the timer fires, as supplied to the createTimer() call that is now being cancelled.

    • reference - This is the reference that was either passed in to the createTimer() call, or generated and returned by the createTimer() call in response to a null value for the incoming reference parameter.

    • return value - A boolean value indicating if the specified timer could be cancelled successfully. A false return value typically indicates the timer never existed or had already fired prior to the cancellation request.

    • Exceptions Thrown - The cancelTimer() function throws an exception if the timer cancellation fails for an unexpected reason, such as an error writing to the Eventing Storage collection. (Note that cancelling stale or non-existent timers will be treated as a no-op and will not throw an exception).

    • The cancelTimer operation will only deletes one of the two or three documents that define the timer in KV, the other two documents will be cleaned up when the original timer (and any peer timers) was scheduled to fire.

    • the cancelTimer operation will remove two of the possible three documents that define the timer in KV leaving only the tiny common root document that was shared among all timers scheduled to fire in a given seven second period. Again this final root document will be cleaned up when the original timer (and any peer timers) were initially scheduled to fire.

    Example use of createTimer and cancelTimer:

    createTimer(DocTimerCallback, oneMinuteFromNow, meta.id, context)
    cancelTimer(DocTimerCallback, meta.id)

    In the sample function calls above:

    • DocTimerCallback is the name of the timer callback function used in the Function handler code.

    • oneMinuteFromNow is a JavaScript Date object.

    • meta.id is a generic reference string that can be used in the Couchbase cluster.

    • context is the JavaScript object that is used in the Function handler code.

    Below we will show a control document and a working Eventing Function that creates a Timer scheduled to execute 60 seconds in the future. When the Timer fires the callback will create a document in the alias 'dst_col' (which must be a 'read and write' binding to an existing collection in the function).

    Create a document with KEY type_of_interest::1 and DATA

    {
      "type": "type_of_interest",
      "id": 1,
      "needed_condition": true,
      "cancel_timer": false
    }

    Create an Eventing Function and deploy it (optionally adjust "cancel_timer" to true to invoke cancelTimer before the Timer fires)

    function DocTimerCallback(context) {
       log('From DocTimerCallback: timer fired', context);
    
       // Create a new document as per our received context in another collection
       dst_col[context.docId] = context.random_text; // upsert a portion of the context
    }
    
    function OnUpdate(doc,meta) {
       // You would typically filter to mutations of interest
       if (doc.type != 'type_of_interest') return;
    
       // You would typically look at some key conditions to decide what to do
       if (doc.needed_condition === true && doc.cancel_timer === false) {
           log('From OnUpdate: creating timer', meta.id);
    
           // Create a timestamp 60 seconds from now
           var oneMinuteFromNow = new Date(); // Get current time & add 60 sec. to it.
           oneMinuteFromNow.setSeconds(oneMinuteFromNow.getSeconds() + 60);
    
           // Create a document to use as out for our context
           var context = {docId : meta.id, random_text : "arbitrary text"};
           createTimer(DocTimerCallback, oneMinuteFromNow, meta.id, context);
        }
        if (doc.cancel_timer === true) {
           // Cancel an existing timer (if it is active) by reference meta.id
           if (cancelTimer(DocTimerCallback, meta.id)) {
               log('From OnUpdate: cancel request, timer was canceled',meta.id);
           } else {
               log('From OnUpdate: cancel request, no such timer may have fired',meta.id);
           }
        }
    }

    Sharding of Timers

    Timers get automatically sharded across Eventing nodes and therefore are elastically scalable. Triggering of timers at or after a specified time interval is guaranteed. However, triggering of timers may either be on the same node (where the timer was created), or on a different node. Relative ordering between two specific timers cannot be maintained.

    Debugging and Logs

    Timers cannot be debugged using the Visual Debugger. For debugging, Couchbase recommends enclosing of timers in a try-catch block. When logging is enabled, timer related logs get captured as part of the Eventing Function’s application logs.

    Elapsed Timestamps

    During runtime, when a Function handler code contains a timestamp in the past (elapsed timestamp), the system executes the code in the next available time window, as soon as the required resources are available.

    Handling Delays

    During Function backlogs, execution of timers may be delayed. To handle these delays, you need to program additional time window in your code. If your business logic is time-sensitive after this additional time window the code should refrain from its Function execution.

    The following is a sample code snippet, which performs a timestamp check (my_deadline) before code execution.

    func callback(context) {
      //context.my_deadline is the parameter in the timer payload
      if (new Date().getTime() > context.my_deadline) {
         // timestamp is back-dated, do not execute the rest of the timer
         return;
      }
    }

    Wall-clock Accuracy

    Timers are not wall-clock accurate events. The timer implementation is designed to handle large numbers of distributed timers (i.e., millions of timers) and only promise to run timers as soon as possible, e.g. no timers lost in a healthy system without crashing nodes.

    Couchbase currently scans for active timers every 7 seconds this creates a maximum delay of 7 seconds + the time it takes too process timers ahead of the given timer on a given thread. Thus, in an Eventing system in a steady state you will typically experience an average timer firing delay of about 3-4 seconds after the scheduled time.

    However, if timer is created and scheduled too close the wall clock of the system, Couchbase may delay the actual scheduling by an additional 1 to 2 scan periods (up to a 14 second delay after the scheduled time) to avoid races.

    The additional overall delay is an implementation artifact and may change between releases.

    Examples

    The Eventing Examples section provides two examples that show the use of Timers. The first example Document Expiry and second example is Document Archive.