{"id":8489,"date":"2020-05-22T10:50:02","date_gmt":"2020-05-22T17:50:02","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=8489"},"modified":"2025-06-13T22:40:23","modified_gmt":"2025-06-14T05:40:23","slug":"implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/","title":{"rendered":"Implementing a Scheduler Via Couchbase Eventing (Part 1)"},"content":{"rendered":"<p>This is the first of a multi-part series to leverage the <a href=\"https:\/\/www.couchbase.com\/products\/eventing\/\">Couchbase Eventing Service<\/a> to run multiple scheduled tasks at specific recurring intervals in a <em>cron<\/em> like fashion completely inside the database without requiring additional infrastructure via a single general-purpose Eventing Function.<\/p>\n<p>In this installment, we will focus on running fixed user routines, JavaScript functions defined inside an Eventing Function.<\/p>\n<p>Later in subsequent articles we will extend the <em>cron<\/em> like Eventing Function to schedule and execute database driven dynamic N1QL statements and then finally in we will explore scheduling database driven dynamic JavaScript functions.<\/p>\n<h3>Background<\/h3>\n<p>The Couchbase Eventing Service provides a framework for writing your own routines, simple JavaScript functions, to process document changes. \u00a0This service provides all the needed infrastructure to create scalable and robust cloud-based functions allowing you to focus on developing pure business logic to interact in near real-time to changes in your data. Your functions are able to access the Couchbase data service (KV), the Couchbase query service (N1QL), and REST endpoints external to the Couchbase system.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-large wp-image-8490\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/04\/cron_implementation_1_image_0_input-output-overview-6.5-1024x405.png\" alt=\"Eventing Life Cycle 6.5 I\/O\" width=\"900\" height=\"356\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/cron_implementation_1_image_0_input-output-overview-6.5-1024x405.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/cron_implementation_1_image_0_input-output-overview-6.5-300x119.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/cron_implementation_1_image_0_input-output-overview-6.5-768x304.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/cron_implementation_1_image_0_input-output-overview-6.5-1536x608.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/cron_implementation_1_image_0_input-output-overview-6.5-20x8.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/cron_implementation_1_image_0_input-output-overview-6.5-1320x522.png 1320w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/cron_implementation_1_image_0_input-output-overview-6.5.png 1870w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><\/p>\n<p>The <a href=\"https:\/\/www.json.org\/\">JSON<\/a> data model in Couchbase came from <a href=\"https:\/\/en.wikipedia.org\/wiki\/JavaScript\">JavaScript<\/a>, thus it is only natural that the Eventing Service exposes the ability to write JavaScript code to analyze and manipulate JSON documents on any type of change events including Inserts, Updates, Merges, and Deletes (together referred to as mutations).<\/p>\n<p>Eventing Functions typically allow you to deploy and execute custom code fragments to that react to thousands and even millions of mutations per seconds in your documents. Several typical use cases are <a href=\"https:\/\/docs.couchbase.com\/server\/6.5\/eventing\/eventing-overview.html\">documented<\/a> for developing high velocity at-scale Eventing Functions that respond to mutations of Couchbase documents.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8739 size-large\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/cron_implementation_1_image_0_lifecycle_overview_notitle_b-1024x398.png\" alt=\"Eventing Life Cycle 6.5\" width=\"900\" height=\"350\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/cron_implementation_1_image_0_lifecycle_overview_notitle_b-1024x398.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/cron_implementation_1_image_0_lifecycle_overview_notitle_b-300x117.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/cron_implementation_1_image_0_lifecycle_overview_notitle_b-768x298.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/cron_implementation_1_image_0_lifecycle_overview_notitle_b-1536x597.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/cron_implementation_1_image_0_lifecycle_overview_notitle_b-20x8.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/cron_implementation_1_image_0_lifecycle_overview_notitle_b-1320x513.png 1320w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/cron_implementation_1_image_0_lifecycle_overview_notitle_b.png 1825w\" sizes=\"auto, (max-width: 900px) 100vw, 900px\" \/><\/p>\n<p>This article will focus instead on a very low velocity use case of the Eventing Service building a reliable \u201cin database\u201d distributed crontab, allowing you to execute JavaScript functions that interact with Couchbase services on a regular periodic schedule.<\/p>\n<h3>Scheduling business logic to run at a specified date or time<\/h3>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\">Cron<\/a>, named after \u201c<em>Chronos<\/em>,\u201d the Greek word for time is one of the most useful utilities in a Linux system. In Linux the <em>cron<\/em> utility is driven by a crontab (cron table) file, a configuration file that specifies shell commands to run periodically on a given schedule.<\/p>\n<p>One drawback in running <em>cron<\/em> is that it is not designed to be a distributed service; it runs on single box, as such it presents a single point of failure. If the system is offline for several hours all scheduled tasks are missed.<\/p>\n<p>Yes, there are some distributed <em>cron<\/em> implementations such as Google\u2019s Cloud Service, AWS\u2019 Scheduled Tasks, and Azure Functions \/ Time Triggers. But each cloud vendors offerings have their own idioms are not directly portable.<\/p>\n<p>In addition, the methodology of configuration and control needs to be secured, for example if you control a distributed <em>cron<\/em> system via a REST API over HTTP\/S you need to account for this in your security plan.<\/p>\n<h3>Using the Couchbase itself to run periodic commands<\/h3>\n<p>With a minor amount of code and planning you can leverage Couchbase\u2019s Eventing service to provide flexible <em>cron<\/em> like functionality for your scheduled database operations or maintenance. Building the scheduler into the database allows you to achieve the following benefits:<\/p>\n<ul>\n<li>Portability across Cloud providers, if you rehost your Couchbase cluster your scheduler is not impacted.<\/li>\n<li>Supportability, if you utilize Couchbase you have single vendor to provided support and other services.<\/li>\n<li>Distributed, no single point of failure and all Couchbase services support distributed replicas.<\/li>\n<li>Guaranteed execution, your task gets executed even after recovery from a node failure.<\/li>\n<\/ul>\n<h3>Couchbase scheduling, Timers the secret sauce<\/h3>\n<p>Timers are Couchbase Eventing Service constructs by which developers can specify a routine (business logic) to be triggered at a future time. We will use this functionality to implement a pure Couchbase configurable <em>crontab<\/em> system that allows you the ability to trigger repetitive tasks as part of your workflows whether you need to execute a simple N1QL query or build a complex rules engine.<\/p>\n<p>In all of the subsequent designs we will limit our <em>cron<\/em> implementations to a resolution of 15 seconds or greater. We have this limitation because although timers scale to the millions and are guaranteed to fire and execute, they are not wall-clock accurate currently have a bounded steady state delay of less than 14 seconds <a id=\"footnote_1\"><\/a><sup>[1]<\/sup>.<\/p>\n<p>Of course, if you need a tighter schedule, i.e. less than 15 seconds, then you should merely process the mutation itself in Eventing logic without the use of a timer construct to schedule a call back in the future.<\/p>\n<p>As of this writing the current Couchbase release is version 6.5.1 which <em><strong>two limitations that we must work around<\/strong><\/em> when making a robust <em>cron<\/em> system.<\/p>\n<ol>\n<li>In the 5.5.x, 6.0.x and 6.5.x releases a function that is invoked by a timer callback cannot reliably create a fresh timer (a user space work around can be done via a second cooperative Function).<\/li>\n<li>In the 6.5.x releases creating timers in the future (as in one hour+) in an otherwise idle system can result in a growing number of metadata bucket operations which can eventually block mutations for a given Eventing function (in 6.5.X a user space work around can be accomplished via a second cooperative Function). The severity is governed by:\n<ul>\n<li>The number of vBuckets holding an active timer. Therefore if there are only a few timers in the future the issue may not be noticeable or materialize.\u00a0 <em>This is the case with just a few cron schedules but for completeness in case you add date functionality I put in a fix for this issue for the code supplied in this article<\/em>.<\/li>\n<li>Whether an Eventing timer has fired recently on a vBucket (which clears the issue for the given vBucket on a per function basis). Therefore systems with lots of near term timer activity will not experience this issue even if timers are scheduled far into the future.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Fortunately in version 6.6.0 both of the above issues or restrictions are lifted and a scheduler can be made in a single simple unified Eventing Function.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8730\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/04\/ev_cr_1u_design_choices-1.png\" alt=\"Eventing cron update\" width=\"740\" height=\"325\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_design_choices-1.png 1479w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_design_choices-1-300x132.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_design_choices-1-1024x449.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_design_choices-1-768x337.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_design_choices-1-20x9.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_design_choices-1-1320x579.png 1320w\" sizes=\"auto, (max-width: 740px) 100vw, 740px\" \/><\/p>\n<h3>Prerequisites<\/h3>\n<p>In this article we will be using the latest GA version, i.e. Couchbase version 6.5.1 (you may need to make some changes to the Eventing Functions described for earlier Couchbase versions). The example in this article will run against the travel-sample data set which is delivered with the Couchbase server.<\/p>\n<p style=\"border: 1px solid black;padding: 8px\"><strong>PRO TIP<\/strong>: <u>For Advanced users only<\/u>, <em>if you are familiar with Couchbase Eventing and also our CLI \/ REST tools you can skip the bulk of this blog and download a ZIP file to quickly setup and run the scheduler system presented below. \u00a0Right-click on the following link and choose <strong>Save Link As<\/strong>\u00a0to download the file<\/em> \u00a0<a href=\"https:\/\/github.com\/couchbaselabs\/blog-source-code\/raw\/master\/Strabala\/CronFiles\/cron_impl_2func_CLI.zip\"><strong>cron_impl_2func_CLI.zip<\/strong><\/a>, <em>move it to an Eventing node, extract the ZIP file, and refer to the extracted README.txt file.<\/em><\/p>\n<p>However, if you are not familiar with Couchbase or the Eventing service please walk through GET STARTED and one Eventing example specifically refer to the following:<\/p>\n<ul>\n<li>Setup a working Couchbase 6.5.1 server as per the directions in <a href=\"https:\/\/docs.couchbase.com\/server\/current\/getting-started\/start-here.html\">Start Here!<\/a><\/li>\n<li>Make sure you can run a N1QL query against the <strong>travel-sample<\/strong> data set as per the directions in <a href=\"https:\/\/docs.couchbase.com\/server\/current\/getting-started\/try-a-query.html\">Run Your First N1QL Query<\/a>.<\/li>\n<li>Understand how to deploy a basic Eventing function as per the directions in the <a href=\"https:\/\/docs.couchbase.com\/server\/current\/eventing\/eventing-examples-docarchive.html\">Document Archival<\/a> example that also uses the <strong>travel-sample<\/strong> data set.<\/li>\n<li>Make sure you have the <strong>travel-sample<\/strong> bucket in the Buckets view of the UI.<\/li>\n<li>Make sure you a bucket called <strong>metadata<\/strong> in the Buckets view of the UI it should have the minimum size of 200MB.<\/li>\n<li>In the Buckets view of the UI create a bucket called <strong>crondata<\/strong> with the minimum size of 200MB. For detailed steps on how to create buckets, see <a href=\"https:\/\/docs.couchbase.com\/server\/6.5\/manage\/manage-buckets\/create-bucket.html\">Create a Bucket<\/a>.<\/li>\n<li>Set <em>allow_interbucket_recursion<\/em> to <em>true<\/em> in order to allow two (2) Eventing functions to alter the same KV document <a id=\"footnote_2\"><\/a><sup>[2]<\/sup>.\n<pre class=\"striped:false wrap-toggle:false lang:sh highlight:0 decode:true\">curl -X POST -u \"$CB_USERNAME:$CB_PASSWORD\" 'https:\/\/localhost:8091\/_p\/event\/api\/v1\/config' -d '{ \"allow_interbucket_recursion\":true }'\r\n<\/pre>\n<\/li>\n<\/ul>\n<h3>Implementation #1, hard coded \u2018cron\u2019 like scheduling<\/h3>\n<p>For our first implementation, e.g. Part 1 of the series, we will design a simple control structure which is merely a KV JSON document and also two (2) Eventing Functions that will respond to and act upon the information in the control structure.<\/p>\n<p>Below is a design of a JSON document, or control structure, that will allow us to have multiple scheduled \u201cevents\u201d. Each schedule event will have its own control document with a unique KEY such as recurring_event::1, recurring_event::1, \u2026 recurring_event::N. The JSON structure itself contains information to \u201creconstitute the key\u201d as our scheduling system will respond to changes or updates (mutations) to the control documents, such as toggling the \u201cactive\u201d state to enable or disable the action or changing \u201cverbose\u201d field which controls the amount and style of logging.<\/p>\n<p>The following is an example control document with KEY <strong>recurring_event::1<\/strong> that will execute the JavaScript function <strong>doCronActionA<\/strong> at 14:54 (2:30 pm) every day.<\/p>\n<table style=\"width: 100%;border: 1px solid #456789;padding: 1px;font-size: 80%;margin-bottom: 26px\">\n<tbody>\n<tr>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px;width: 33%\">JSON Control Record<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\">Description<\/th>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">{<\/td>\n<td style=\"padding: 4px\"><\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0&#8220;type&#8221;:&#8221;recurring_event&#8221;,<\/td>\n<td style=\"padding: 4px\">The KEY will be &lt;&lt;type&gt;&gt;::&lt;&lt;id&gt;&gt;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0&#8220;id&#8221;:1,<\/td>\n<td style=\"padding: 4px\"><\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0&#8220;hour&#8221;:14,<\/td>\n<td style=\"padding: 4px\">The hour of the day 0-23, *, *2X, *4X to trigger<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0&#8220;min&#8221;:54,<\/td>\n<td style=\"padding: 4px\">The minute in the hour 0-59, *, *2X, *4X to trigger<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0\u00a0 &#8220;action&#8221;:&#8221;doCronActionA&#8221;,<\/td>\n<td style=\"padding: 4px\">JavaScript function to run when the timer fires<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0&#8220;active&#8221;:true,<\/td>\n<td style=\"padding: 4px\">Flag to enable or disable this schedule<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0\u00a0 &#8220;verbose&#8221;: {<\/td>\n<td style=\"padding: 4px\">[OPTIONAL] logging control<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0\u00a0 \u00a0&#8220;user_func&#8221;:2,<\/td>\n<td style=\"padding: 4px\">Logging level for the action logic : 0=none, etc. etc.<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0\u00a0 \u00a0&#8220;scheduler&#8221;:3<\/td>\n<td style=\"padding: 4px\">Logging level for the cron logic : 0=none, etc. etc.<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0\u00a0 },<\/td>\n<td style=\"padding: 4px\"><\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0\u00a0 &#8220;dynamic&#8221;: {<\/td>\n<td style=\"padding: 4px\">[DYNAMIC] system control and statistics<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0\u00a0 \u00a0&#8220;state&#8221;:&#8221;arm&#8221;,<\/td>\n<td style=\"padding: 4px\">&#8220;arm&#8221;|&#8221;rearm&#8221;|&#8221;pending&#8221; any value != &#8220;pending&#8221; start a schedule<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0\u00a0 \u00a0&#8220;next_sched&#8221;: 0,<\/td>\n<td style=\"padding: 4px\">Number of seconds since epoch to next desired schedule<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0\u00a0 \u00a0&#8220;prev_sched&#8221;: 0,<\/td>\n<td style=\"padding: 4px\">Number of seconds since epoch for previous schedule<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0\u00a0 \u00a0&#8220;prev_etime&#8221;: 0,<\/td>\n<td style=\"padding: 4px\">Number of seconds since epoch for previous schedule actual exec time<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0\u00a0 \u00a0&#8220;prev_delay&#8221;: 0,<\/td>\n<td style=\"padding: 4px\">Number of seconds that the timer was delayed from the schedule<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0 \u00a0\u00a0 \u00a0&#8220;prev_atime&#8221;: 0<\/td>\n<td style=\"padding: 4px\">Number of seconds taken by the user &#8216;action&#8217;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">\u00a0\u00a0 }<\/td>\n<td style=\"padding: 4px\"><\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">}<\/td>\n<td style=\"padding: 4px\"><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Like traditional Linux <em>crontab<\/em> you can set hour and min to legal integers, and you can also set <em>hour<\/em> to &#8220;*&#8221; to process for all hours or set <em>min<\/em> to &#8220;*&#8221; to process for all minutes.<\/p>\n<p>Although we will not support the full <em>crontab<\/em> syntax we do support two non-standard settings as follows if you <u>set both<\/u> <em>hour<\/em> and <em>min<\/em> to &#8220;*4X&#8221; we will execute and re-arm four (4) times a minute and if you set them both to &#8220;*2X&#8221; we will execute and re-arm two (2) times a minute.\u00a0 Below is a table of supported schedules with description:<\/p>\n<table style=\"width: 100%;border: 1px solid #456789;padding: 1px;font-size: 80%;margin-bottom: 26px\">\n<tbody>\n<tr>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\" width=\"60\">hour<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\" width=\"72\">min<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\" width=\"510\">Values can be numbers or strings<\/th>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">13<\/td>\n<td style=\"padding: 4px\" width=\"72\">32<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run at 13:32 (or 1:32 pm)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">*<\/td>\n<td style=\"padding: 4px\" width=\"72\">15<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run every hour at 15 minutes past<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">8<\/td>\n<td style=\"padding: 4px\" width=\"72\">12<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run once a day at 8:32 (or 8:32 am)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">*<\/td>\n<td style=\"padding: 4px\" width=\"72\">*<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run once a minute<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">*2X<\/td>\n<td style=\"padding: 4px\" width=\"72\">*2X<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run twice a minute \u2013 requires both hour and min set to \u201c*2X\u201d<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">*4X<\/td>\n<td style=\"padding: 4px\" width=\"72\">*4X<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run four times a minute \u2013 requires both hour and min set to \u201c*2X\u201d<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Eventually we will use the Query Workbench to insert the <em>cron<\/em> control documents all of which must have a unique KEY of <strong>recurring_event::#<\/strong> to a scheduled time of execution of 14:54 ( 2:54 pm), for the action doCronActionA, we could use the following N1QL statement.<\/p>\n<p><u>Don\u2019t worry about actually running any N1QL statements right now,<\/u> we will perform the N1QL statements later after we have built and deployed our Eventing Function.<\/p>\n<p>You can create a control record (or records) in the bucket <strong>travel-sample<\/strong>, and then list it, arm, disarm it, adjust the schedule it follows, change the verbosity level for logging, or delete it as follows:<\/p>\n<table style=\"width: 100%;border: 1px solid #456789;padding: 1px;font-size: 80%;margin-bottom: 26px\">\n<tbody>\n<tr>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px;width: 30%\">Action<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\">N1QL statement<\/th>\n<\/tr>\n<tr>\n<td style=\"padding: 4px;vertical-align: top\">Create a schedule<\/td>\n<td style=\"padding: 4px\">INSERT INTO `travel-sample` (KEY,VALUE) VALUES (<br \/>\n&#8220;recurring_event::1&#8221;, {<br \/>\n&#8220;type&#8221;:&#8221;recurring_event&#8221;,<br \/>\n&#8220;id&#8221;:1,<br \/>\n&#8220;hour&#8221;:&#8221;14&#8243;,<br \/>\n&#8220;min&#8221;:&#8221;54&#8243;,<br \/>\n&#8220;action&#8221;:&#8221;doCronActionA&#8221;,<br \/>\n&#8220;active&#8221;:true<br \/>\n}<br \/>\n);<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">Make an index to query data without specifying keys<\/td>\n<td style=\"padding: 4px\">CREATE primary INDEX on `crondata` ;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">Show all schedules order by id<\/td>\n<td style=\"padding: 4px\">SELECT * FROM `crondata` WHERE type=&#8221;recurring_event&#8221; order by id ;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">Show specific schedule<\/td>\n<td style=\"padding: 4px\">SELECT * FROM `crondata` WHERE type=&#8221;recurring_event&#8221; AND id=1 ;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">Arm or set active<\/td>\n<td style=\"padding: 4px\">UPDATE `crondata` SET active = true WHERE type=&#8221;recurring_event&#8221; AND id=1 ;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">Disarm or set inactive<\/td>\n<td style=\"padding: 4px\">UPDATE `crondata` SET active = false WHERE type=&#8221;recurring_event&#8221; AND id=1 ;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">Adjust time of trigger<\/td>\n<td style=\"padding: 4px\">UPDATE `crondata` SET hour = 11, min = 30 WHERE type=&#8221;recurring_event&#8221; AND id=1 ;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">Adjust logging of the &#8220;action&#8221;<\/td>\n<td style=\"padding: 4px\">UPDATE `crondata` SET verbose.user_data = 0 WHERE type=&#8221;recurring_event&#8221; AND id=1 ;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">Adjust logging of the scheduler logic<\/td>\n<td style=\"padding: 4px\">UPDATE `crondata` SET verbose.scheduler = 0 WHERE type=&#8221;recurring_event&#8221; AND id=1 ;<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">Delete the schedule<\/td>\n<td style=\"padding: 4px\">DELETE FROM `crondata` WHERE type=&#8221;recurring_event&#8221; AND id=1 ;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Assume we have four (4) active schedules, running the first N1QL statement, above will list all of them e.g.<\/p>\n<pre class=\"striped:false wrap-toggle:false lang:sh highlight:0 decode:true\">SELECT active, action, hour, min, type, id, verbose.user_func, verbose.scheduler\r\nFROM `crondata` where type=\"recurring_event\" order by id ;<\/pre>\n<p>Would return something like the following output (table view in the Query Workbench):<\/p>\n<table style=\"width: 100%;border: 1px solid #456789;padding: 1px;font-size: 80%;margin-bottom: 26px\">\n<tbody>\n<tr>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\">active<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px;width: 20%\">action<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\">hour<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\">id<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\">min<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\">scheduler<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px;width: 20%\">type<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\">user_func<\/th>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">true<\/td>\n<td style=\"padding: 4px\">&#8220;doCronActionA&#8221;<\/td>\n<td style=\"padding: 4px\">14<\/td>\n<td style=\"padding: 4px\">1<\/td>\n<td style=\"padding: 4px\">54<\/td>\n<td style=\"padding: 4px\">1<\/td>\n<td style=\"padding: 4px\">&#8220;recurring_event&#8221;<\/td>\n<td style=\"padding: 4px\">2<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">true<\/td>\n<td style=\"padding: 4px\">&#8220;doCronActionB&#8221;<\/td>\n<td style=\"padding: 4px\">*<\/td>\n<td style=\"padding: 4px\">2<\/td>\n<td style=\"padding: 4px\">*<\/td>\n<td style=\"padding: 4px\">1<\/td>\n<td style=\"padding: 4px\">&#8220;recurring_event&#8221;<\/td>\n<td style=\"padding: 4px\">1<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">true<\/td>\n<td style=\"padding: 4px\">&#8220;doCronActionC&#8221;<\/td>\n<td style=\"padding: 4px\">*2X<\/td>\n<td style=\"padding: 4px\">3<\/td>\n<td style=\"padding: 4px\">*2X<\/td>\n<td style=\"padding: 4px\">4<\/td>\n<td style=\"padding: 4px\">&#8220;recurring_event&#8221;<\/td>\n<td style=\"padding: 4px\">4<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\">true<\/td>\n<td style=\"padding: 4px\">&#8220;doCronActionD&#8221;<\/td>\n<td style=\"padding: 4px\">*<\/td>\n<td style=\"padding: 4px\">4<\/td>\n<td style=\"padding: 4px\">0<\/td>\n<td style=\"padding: 4px\">0<\/td>\n<td style=\"padding: 4px\">&#8220;recurring_event&#8221;<\/td>\n<td style=\"padding: 4px\">1<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>In the above table we have four actions the first runs once a day, the second runs every minute, the third every 30 seconds, and the fourth runs once an hour.\u00a0 In a future installment in this series we will add &#8220;day of week&#8221; capability.<\/p>\n<p>The JSON Control Record&#8217;s nested object &#8220;<em>verbose<\/em>&#8221; if not supplied will default to\u00a0 { &#8220;user_func&#8221;:1, &#8220;scheduler&#8221;:1 } indication a low or terse logging level for the action function and also the scheduling logic.\u00a0 A value of 0 will suppress all log messages, i.e. doCronActionD, while larger values will be more verbose, i.e. as defined in doCronActionC.<\/p>\n<p>The JSON Control Record&#8217;s nested object &#8220;<em>dynamic<\/em>&#8221; if typically never supplied and will default to { &#8220;state&#8221;: &#8220;arm&#8221;, &#8220;next_sched&#8221;: 0, &#8220;prev_sched&#8221;: 0, &#8220;prev_etime&#8221;: 0, &#8220;prev_delay&#8221;: 0, &#8220;prev_atime&#8221;: 0 } this is a scratch pad for the running Eventing logic schedule and also provides useful statistics on execution times as such it should be treated as read-only.<\/p>\n<p>At this point we have a high-level control design, but we need logic to process our control structures, this is where the Couchbase Eventing Service, specifically an Eventing Function comes into play.<\/p>\n<h3>The Eventing Functions<\/h3>\n<p>This design requires two (2) Eventing functions: a main JavaScript function &#8220;cron_impl_2func_651&#8221; and a small helper JavaScript function &#8220;cron_impl_2func_651_help&#8221;. We will discuss each section of the JavaScript functions that comprises the initial implementation combined JavaScript code almost 610 lines (with about 44% of the lines are comments and whitespace)<\/p>\n<p><u>Don\u2019t worry about doing a cut-n-paste right now<\/u>, as later I will provide a\u00a0 link to download (for import) the two required Eventing Functions and all the required settings in two files named &#8220;cron_impl_2func_651.json&#8221; &#8220;cron_impl_2func_651_help.json&#8221; and also if you prefer the two full unified functions that can be cut-n-pasted directly.<\/p>\n<p>Our Main Eventing Function &#8220;cron_impl_2func_651&#8221; will be composed of nine (9) JavaScript functions<\/p>\n<ul>\n<li>Three (3) business logic functions, (two which are empty shells).\n<ul>\n<li>doCronActionA(doc) &#8211; an N1QL example user action to execute<\/li>\n<li>doCronActionB(doc) &#8211; an empty user action shell for experiments<\/li>\n<li>doCronActionC(doc) &#8211; an empty user action shell for experiments<\/li>\n<\/ul>\n<\/li>\n<li>One (1) entry point for Eventing.\n<ul>\n<li>OnUpdate(doc, meta) &#8211; the standard Eventing entry point for Inserts or Updates<\/li>\n<\/ul>\n<\/li>\n<li>One (1) <em>cron<\/em> syntax parser to generate the next schedule.\n<ul>\n<li>getNextRecurringDate(hour_str, min_str) &#8211; cron logic to find the next scheduled Date<\/li>\n<\/ul>\n<\/li>\n<li>Three (3) support functions to check that the business logic exists or format results.\n<ul>\n<li>verifyFunctionExistsViaEval(curDoc, id) &#8211; make sure we have a function to run<\/li>\n<li>toNumericFixed(number, precision) &#8211; format a float to a compact style<\/li>\n<li>toLocalISOTime(d) &#8211; format a date to a compact style<\/li>\n<\/ul>\n<\/li>\n<li>One (1) callback function when timers are executed.\n<ul>\n<li>Callback(doc) &#8211; a callback function for scheduled timers<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>Our Helper Eventing Function &#8220;cron_impl_2func_651_help&#8221; will be composed of one (1) JavaScript function<\/p>\n<ul>\n<li>One (1) entry point for Eventing.\n<ul>\n<li>OnUpdate(doc, meta) &#8211; the standard Eventing entry point for Inserts or Updates<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>In the subsequent sections we will walk through each individual of the above JavaScript functions.<\/p>\n<h3>We need a JavaScript function, e.g. the business logic to run on a periodic schedule.<\/h3>\n<p>The first thing we want is a routine or function which has our business logic that we will execute based upon out crontab rules.\u00a0 We will call the JavaScript method <strong>doCronActionA(doc)<\/strong>, however it can be called anything for example <em>doPeriodicLedgerBalance(doc),<\/em> the only requirements for our \u201caction\u201d functions that implements our scheduled business logic are as follows:<\/p>\n<ul>\n<li>Has one parameter: doc, a control document as described above of type=\u201drecurring_event\u201d.<\/li>\n<li>The actual JavaScript name matches the \u201caction\u201d field in the control document.<\/li>\n<li>Returns <strong><em>true<\/em><\/strong> on success and <em><strong>false<\/strong><\/em> on failure<\/li>\n<li>Utilizes <em><strong>doc.verbose.user_func<\/strong><\/em> to control logging if 0 it is silent, if 1 it emits a single line, if 2 it emits whatever log information is needed to debug the function, etc. etc..<\/li>\n<\/ul>\n<p>We will write our function <strong>doCronActionA(doc)<\/strong>, to run an embedded N1QL query ) to combine airline counts by country and then make a single KV document of calculated data.<\/p>\n<pre class=\"striped:false wrap-toggle:false lang:sh highlight:0 decode:true\">SELECT country, count( * ) AS cnt FROM `travel-sample` WHERE `type` = 'airline' GROUP BY country;<\/pre>\n<p>On my test system a small single node non-<a href=\"https:\/\/docs.couchbase.com\/server\/6.5\/learn\/services-and-indexes\/services\/services.html#services-and-multi-dimensional-scaling\">MDS<\/a> server (running all Couchbase services) the above N1QL takes about 20 ms. (for clarity sake pretend it is super complex it takes 10 seconds to complete).<\/p>\n<p>The idea here is that the final calculated and summarized KV document can be quickly loaded by a 100K (or a million) Eventing mutations per second without the additional overhead communication with the Query service nodes and of processing N1QL statements on each mutation.<\/p>\n<p>It should be obvious that the goal of this particular business logic, <strong>doCronActionA(doc)<\/strong>, is to create a semi-static cache that updates periodically on a schedule.<\/p>\n<p>All we are really doing (and it\u2019s fairly fast) is getting a count of airlines by country from the travel-sample document set.\u00a0 As we use N1QL we build up a document and eventually write it out to KV as a summarized document.\u00a0\u00a0 The key point to drive home here is that we do not want to repeat the same work for millions of mutations each, especially since some calculations might take 10 seconds of Query service compute time each time we kick off an embedded N1QL query from an Eventing function.<\/p>\n<p>Below we show the JavaScript function we want to run once a day (or perhaps once an hour, etc.).\u00a0 Note the name of the function matches the name in the control structure action field.\u00a0 For more details on Eventing terminology and language constructs please refer to the Couchbase documents and examples at <a href=\"https:\/\/docs.couchbase.com\/server\/6.5\/eventing\/eventing-overview.html\">Eventing Service: Fundamentals<\/a>.<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">function doCronActionA(doc) {\r\n  try {\r\n    \/\/ Check that doc has desired values\r\n    if (!doc.type || doc.type !== \"recurring_event\" || !doc.active || doc.active !== true) return;\r\n    if (doc.verbose.user_func &gt;= 1)\r\n      log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);\r\n\r\n    \/\/ this is a 6.5 N1QL query (feature not available in GA prior to 6.5)\r\n    \/\/ Create an embedded N1QL iterator by issuing a SELECT statement to get the\r\n    \/\/ counts of airlines by country.  Make a new document and write it out to KV \r\n\r\n    \/\/ We will use the iterator to create a KV document representing the results of a\r\n    \/\/ HARD lengthy embedded N1QL query and write it back to KV, the idea is to keep\r\n    \/\/ a calculation up to date once a day such that it that can be read 'quickly' \r\n    \/\/ by other Eventing Functions, other Couchbase services or SDKs.   \r\n\r\n    \/\/ Consider if we had 1 million docs in a minute do we really want to use N1QL\r\n    \/\/ to recalculate something that is almost static for all 1 million documents, of \r\n    \/\/ course not, so we make an intermediate value that can be read into Eventing\r\n    \/\/ and used via a single 'light weight' KV read.\r\n\r\n    var q_iter = SELECT country,\r\n      count( * ) cnt\r\n    FROM `travel-sample`\r\n    WHERE `type` = 'airline'\r\n    GROUP BY country;\r\n\r\n    \/\/ loop through the result set and update the map 'accumulate'\r\n    var accumulate = {};\r\n    var idx = 0;\r\n    for (var val of q_iter) {\r\n      if (doc.verbose.user_func &gt;= 2)\r\n        log(doc.action + ' N1QL idx ' + idx + ', country ' + val.country + \" cnt \" + val.cnt);\r\n      accumulate[val.country] = val.cnt;\r\n      idx++;\r\n    }\r\n    \/\/ close out embedded N1QL iterator\r\n    q_iter.close();\r\n\r\n    \/\/ Now let\u2019s make a cached KV document representing a HARD length embedded N1QL\r\n    \/\/ query and write it back to KV, we need a KEY and a type and id and then we \r\n    \/\/ upsert it into the `travel-sample` bucket.\r\n\r\n    var cachedoc = {};\r\n    cachedoc.type = \"cron_cache\";\r\n    cachedoc.id = \"airlines_by_country\";\r\n    cachedoc.date = new Date();\r\n    cachedoc.data = accumulate;\r\n    var ckey = cachedoc.type + '::' + cachedoc.id;\r\n    ts_bkt[ckey] = cachedoc;\r\n    if (doc.verbose.user_func &gt;= 2) {\r\n      log(doc.action + ' upsert to KV with KEY ' + ckey + ' cachedoc ', cachedoc);\r\n    }\r\n  } catch (e) {\r\n    log(doc.action + ' Error exception:', e);\r\n    return false;\r\n  }\r\n  return true;\r\n}<\/pre>\n<p>The above function merely 1) queries the travel-sample bucket to extract data in this case the count of airlines for each country, 2) creates a new KV document and key and writes it out to travel-sample bucket for later use.<\/p>\n<p>In addition, as part of this example we have built logging that responds to a numeric verbosity setting which a) logs a single line if control document has a value for doc.verbose.user_func == 1 or b) emits more information if the doc.verbose.user_func value &gt;= 2.<\/p>\n<p>This is a generic framework that can run one (1) <em>cron<\/em> action or even a thousand (1000) of <em>cron<\/em> actions. As such I have provided two additional \u201cempty\u201d function shells \u2013 as pointed out before they could have been named anything.<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">function doCronActionB(doc) {\r\n  try {\r\n    \/\/ check that doc has desired values\r\n    if (doc.type !== \"recurring_event\" || doc.active !== true) return;\r\n    if (doc.verbose.user_func &gt;= 1)\r\n      log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);\r\n\r\n    \/\/ YOUR LOGIC HERE\r\n\r\n  } catch (e) {\r\n    log(doc.action + ' Error exception:', e);\r\n    return false;\r\n  }\r\n  return true;\r\n}<\/pre>\n<p>and<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">function doCronActionC(doc) {\r\n  try {\r\n    \/\/ check that doc has desired values\r\n    if (doc.type !== \"recurring_event\" || doc.active !== true) return;\r\n    if (doc.verbose.user_func &gt;= 1)\r\n      log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);\r\n\r\n    \/\/ YOUR LOGIC HERE\r\n\r\n  } catch (e) {\r\n    log(doc.action + ' Error exception:', e);\r\n    return false;\r\n  }\r\n  return true;\r\n}<\/pre>\n<p>These above functions doCronActionB\u00a0 and doCronActionC\u00a0 are trivial as they merely log information to the Eventing Application log of the Eventing function.\u00a0 Refer to <a href=\"https:\/\/docs.couchbase.com\/server\/6.5\/eventing\/eventing-debugging-and-diagnosability.html#logging-functions\">Logging Functions<\/a> for more details. Of course, you need a control document of type=&#8221;recurring_event&#8221; with active=true and an action like action = &#8220;doCronActionB&#8221; to actually enable and execute them.<\/p>\n<h3>We need an Eventing entry point or Handler<\/h3>\n<p>As of version 6.5 two entry points or handlers that are supported by the Eventing Service <strong>OnUpdate(doc, meta)<\/strong> and <strong>OnDelete(meta)<\/strong> we are only interested in the <strong>OnUpdate(doc,meta)<\/strong> for this example.<\/p>\n<p>The <strong>OnUpdate(doc,meta)<\/strong> handler gets called when any document in the source bucket is created or modified (mutated) and immediately filters out documents of no interest. <a id=\"footnote_3\"><\/a><sup>[3]<\/sup><\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">function OnUpdate(doc, meta) {\r\n  \/\/ fix for 6.5.X growing bucket ops\r\n  if (doc.type === \"_tmp_vbs\") genNoopTimers(doc, meta, 30);\r\n  if (!cron_bkt[\"fix_timer_scan_issue::1\"]) {\r\n      cron_bkt[\"fix_timer_scan_issue::1\"] = {};\r\n  }\r\n    \r\n  try {\r\n    \/\/ Check if further analysis is needed we only trigger on an active recurring_event \r\n    if (doc.type !== \"recurring_event\" || doc.active !== true) return;\r\n\r\n    var update_doc = false;\r\n    if (!doc.dynamic) {\r\n      \/\/ Add if missing doc.dynamic with defaults\r\n      doc.dynamic = {\r\n        \"state\": \"arm\",\r\n        \"next_sched\": 0,\r\n        \"prev_sched\": 0,\r\n        \"prev_etime\": 0,\r\n        \"prev_delay\": 0,\r\n        \"prev_atime\": 0\r\n      };\r\n      \/\/ we need to update the document once we have the next schedule\r\n      update_doc = true;\r\n    }\r\n    if (!doc.verbose) {\r\n      \/\/ Add if missing doc.dynamic with defaults\r\n      doc.verbose = {\r\n        \"user_func\": 1,\r\n        \"scheduler\": 1\r\n      };\r\n      \/\/ we need to update the document once we have the next schedule\r\n      update_doc = true;\r\n    }\r\n    \/\/ Do not process dynamic.state pending\r\n    if (!doc.dynamic || !doc.dynamic.state || doc.dynamic.state === \"pending\") return;\r\n\r\n    var mid = doc.type + \"::\" + doc.id; \/\/ this is the same as meta.id or the KEY\r\n    var hour = doc.hour;\r\n    var min = doc.min;\r\n\r\n    \/\/ Do an eval check the JavaScript function exists. The eval occurs in a common \r\n    \/\/ utility function shared with RecurringCallback\r\n    if (!verifyFunctionExistsViaEval(doc, mid)) {\r\n      \/\/ doc.action did not exist, we have already logged the issue\r\n      return;\r\n    }\r\n\r\n    \/\/ Get the next valid execution time\r\n    var date_timer = getNextRecurringDate(hour, min);\r\n    var next_sched = Math.round(date_timer.getTime() \/ 1000);\r\n    if (!update_doc &amp;&amp; next_sched !== doc.dynamic.next_sched) {\r\n      \/\/ the next_sched should be the same as the setting from the helper application, however\r\n      \/\/ if we undeploy\/deploy or pause\/resume we might haver to reschedule to the next time slot\r\n      log('OnUpdate U ' + mid + ' calculated next_sched !== doc.dynamic.next_sched, delta ' +\r\n        (next_sched - doc.dynamic.next_sched) + ', reschedule');\r\n      update_doc = true;\r\n    }\r\n\r\n    if (update_doc) {\r\n      \/\/ this mutation is recursive and will be suppressed, we ensure we have a dynamic structure\r\n      doc.dynamic.next_sched = next_sched;\r\n\r\n      \/\/ rather then the call a function, to trap and retry if there is a resource issue\r\n      \/\/ cron_bkt[mid] = doc;\r\n      if (!tryBucketKvWriteWithLog('OnUpdate F', mid, doc)) {\r\n        \/\/ Failed to write doc to cron_bkt[key] the error has been logged\r\n        \/\/ and there is nothing more we can do.\r\n        return;\r\n      }\r\n    }\r\n\r\n    \/\/ Schedule an Eventing timer\r\n    var timer_id = createTimer(Callback, date_timer, null, doc);\r\n    if (doc.verbose.scheduler &gt;= 1) {\r\n      log('OnUpdate A ' + mid + ' rcv mutation (initial or rearm) schedule timer at ' +\r\n        toLocalISOTime(date_timer));\r\n    }\r\n    if (doc.verbose.scheduler &gt;= 2) {\r\n      log('OnUpdate B ' + mid + ' recurring timer was created, timer_id ' + timer_id);\r\n    }\r\n  } catch (e) {\r\n    log('OnUpdate E ' + meta.id + ', Error exception:', e);\r\n  }\r\n}<\/pre>\n<p>The key here is that the <em>cron<\/em> logic in our handler only cares about documents that have doc.type of &#8220;recurring_event and also a doc.active of true.\u00a0 In addition, in this example we have built tracing for the <em>cron<\/em> housekeeping logic which is only logged to the Application log if the control document has a value for doc.verbose &gt;= 3.<\/p>\n<p>If you only run a few schedules you can turn off the user space work or &#8220;<em>fix for 6.5.X growing bucket ops<\/em>&#8221; by commenting four lines of code in the above OnUpdate block for &#8220;cron_impl_2func_651&#8221; as follows:<\/p>\n<pre class=\"toolbar-overlay:false lang:js decode:true\">function OnUpdate(doc, meta) {\r\n  \/\/ fix for 6.5.X growing bucket ops\r\n  \/\/ if (doc.type === \"_tmp_vbs\") genNoopTimers(doc, meta, 30);\r\n  \/\/ if (!cron_bkt[\"fix_timer_scan_issue::1\"]) {\r\n  \/\/     cron_bkt[\"fix_timer_scan_issue::1\"] = {};\r\n  \/\/ }\r\n<\/pre>\n<h3>We need code to work around possible growing bucket ops for 6.5.X<\/h3>\n<p>As of version 6.5.X we need a &#8220;<em>fix for 6.5.X growing bucket ops<\/em>&#8221; which happens on idle systems with lots of timers scheduled in the future.\u00a0 This code ensures that an Eventing timer has fired recently on a vBucket (which clears the issue for the given vBucket on a per function basis).<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">\/\/ FIXUP: ADDIN FUNCTON\r\nfunction noopTimer(context) {\r\n    \/\/ fix for 6.5.X growing bucket ops\r\n    try {\r\n        if (context.type === \"_tmp_vbs\" &amp;&amp; context.vb === 0) { \r\n            \/\/ log(\"noopTimer timers firing, printing only for vBucket 0\");\r\n        }\r\n    } catch (e) {\r\n        log(\"OnUpdate Exception in callback noopTimer:\", e);\r\n    }\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\nfunction rearmTimer(context) {\r\n    \/\/ fix for 6.5.X growing bucket ops\r\n    try {\r\n        if (context.type === \"_tmp_vbs\" &amp;&amp; context.vb === 0) { \r\n            \/\/ Update\/touch all docs in the helper_bucket the helper function will then\r\n            \/\/ mutate all 1024 of type == vbs_seed (64 on MacOS) to create a recuring cycle.\r\n            \/\/ log(\"noopTimer timer fired all 1024 vBuckets, logging only vb 0\", context);\r\n            \r\n            \/\/ generate a mutation to re-arm the HELPER function: fix_scan_issue\r\n            \/\/ which will in turn make new mutations for this Function\r\n            var cur = cron_bkt[context.key];\r\n            if (cur &amp;&amp; cur.ts_millis === context.ts_millis) {\r\n                \/\/ log(\"rearmTimer update fix_timer_scan_issue::1 in helper_bucket alias only for vBucket 0\");\r\n                var now = new Date();\r\n                cron_bkt[\"fix_timer_scan_issue::1\"] = { \"last_update\": now };\r\n            } else {\r\n                \/\/ NOOP we had multiple timer cycles, just let this one quietly stop.\r\n            }\r\n        }\r\n    } catch (e) {\r\n        log(\"OnUpdate Exception in callback rearmTimer:\", e);\r\n    }\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\nfunction genNoopTimers(doc, meta, seconds) {\r\n    \/\/ fix for 6.5.X growing bucket ops\r\n    try {\r\n        \/\/ redundant but play it safe\r\n        if (doc.type === \"_tmp_vbs\") {\r\n            \/\/ Since we are using an different function a timer on all our vBuckets do immeadiately (can take up to 15 seconds)\r\n            \/\/ If we used cross bucket recursion to rearm all the timers in a recurring fashion we would add a delay of at least 40 seconds.\r\n            createTimer(noopTimer, new Date(), null, doc);\r\n            if (doc.vb === 0) { \r\n                \/\/ Update\/touch all docs in the helper_bucket the helper function will then\r\n                \/\/ mutate all 1024 of type == vbs_seed (64 on MacOS) to create a recuring cycle.\r\n                \/\/ log(\"noopTimer timer fired all 1024 vBuckets, logging only vb 0\", context);\r\n            \r\n                \/\/ generate a mutation to re-arm the HELPER function: fix_scan_issue\r\n                \/\/ which will in turn make new mutations for this Function\r\n                \r\n                \/\/ log(\"genNoopTimers make timer to rearm fix_timer_scan_issue::1\");\r\n                createTimer(rearmTimer, new Date(new Date().getTime() + seconds * 1000), null, doc);\r\n            }\r\n        }\r\n    } catch (e) {\r\n        log(\"OnUpdate Exception in genNoopTimers:\", e);\r\n    }\r\n}<\/pre>\n<h3>\u00a0We need a utility to calculate the next time in the schedule<\/h3>\n<p>The next function getNextRecurringDate(hour, min) will determine a time to execute the action as defined as part of our schedule.\u00a0 This is not a full implementation of <em>cron<\/em>, rather it contains the key standard features to execute once a day, once an hour, once a minute.\u00a0 It also contains some non-standard syntax to provide the ability to execute twice a minute or four times a minute.<\/p>\n<p>As previously described the function <strong>getNextRecurringDate(hour, min)<\/strong> allows for the following (the table is duplicated below), with the last two being non-standard.<a id=\"footnote_4\"><\/a><sup>[4]<\/sup><\/p>\n<table style=\"width: 100%;border: 1px solid #456789;padding: 1px;font-size: 80%;margin-bottom: 26px\">\n<tbody>\n<tr>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\" width=\"60\">hour<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\" width=\"72\">min<\/th>\n<th style=\"padding: 4px;font-weight: bold;border-bottom: solid 1px\" width=\"510\">Values can be numbers or strings<\/th>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">13<\/td>\n<td style=\"padding: 4px\" width=\"72\">32<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run at 13:32 (or 1:32 pm)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">*<\/td>\n<td style=\"padding: 4px\" width=\"72\">15<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run every hour at 15 minutes past<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">8<\/td>\n<td style=\"padding: 4px\" width=\"72\">12<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run once a day at 8:32 (or 8:32 am)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">*<\/td>\n<td style=\"padding: 4px\" width=\"72\">*<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run once a minute<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">*2X<\/td>\n<td style=\"padding: 4px\" width=\"72\">*2X<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run twice a minute \u2013 requires both hour and min set to \u201c*2X\u201d<\/td>\n<\/tr>\n<tr>\n<td style=\"padding: 4px\" width=\"60\">*4X<\/td>\n<td style=\"padding: 4px\" width=\"72\">*4X<\/td>\n<td style=\"padding: 4px\" width=\"510\">Run four times a minute \u2013 requires both hour and min set to \u201c*2X\u201d<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Below is an implementation of the required logic for determining the next time to trigger an Eventing timer in our schedule, in the event the user logic in our first example <strong>doCronActionA(doc) <\/strong>doesn\u2019t complete timely, e.g. real-time overrun, the next quanta of the schedule will be selected.\u00a0 Note both Timers and their Parent Functions. So, if an Eventing Function has a default execution timeout of 60 seconds, if need be this setting can be adjusted or raised.<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">function getNextRecurringDate(hour_str, min_str) {\r\n  \/\/ Note Javascript Dates are in milliseconds\r\n  var date_now = new Date();\r\n  var date_ret = new Date();\r\n  var hour;\r\n  var min;\r\n\r\n  try {\r\n    hour = parseInt(hour_str);\r\n  } catch (e) {}\r\n  try {\r\n    min = parseInt(min_str);\r\n  } catch (e) {}\r\n\r\n  \/\/ Note, this is only a simplistic partial 'crontab' syntax with some slight extensions\r\n  \/\/ it allows once a day, once an hour, once a minute.  It also contains some non-standard \r\n  \/\/ syntax to provide the ability to execute twice a minute or four times a minute.\r\n\r\n  if (hour_str === '*4X' &amp;&amp; min_str === '*4X') {\r\n    \/\/ once every 15 seconds or four times a minute\r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(15);\r\n    while (date_ret.getTime() &lt; date_now.getTime()) {\r\n      date_ret.setSeconds(date_ret.getSeconds() + 15);\r\n    }\r\n    return date_ret;\r\n  } else\r\n  if (hour_str === '*2X' &amp;&amp; min_str === '*2X') {\r\n    \/\/ once every 30 seconds or twice a minute\r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(30);\r\n    while (date_ret.getTime() &lt; date_now.getTime()) {\r\n      date_ret.setSeconds(date_ret.getSeconds() + 30);\r\n    }\r\n    return date_ret;\r\n  } else\r\n  if (hour_str === '*' &amp;&amp; min_str === '*') {\r\n    \/\/ once a minute \r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(0);\r\n    date_ret.setMinutes(date_ret.getMinutes() + 1);\r\n  } else\r\n  if (hour_str !== '*' &amp;&amp; isNaN(hour) === false &amp;&amp; min_str === '*') {\r\n    \/\/ once a minute only for a given hour\r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(0);\r\n    date_ret.setMinutes(date_ret.getMinutes() + 1);\r\n    if (date_ret.getTime() &lt; date_now.getTime()) { date_ret.setHours(hour); } if (date_ret.getTime() &gt; date_now.getTime()) {\r\n      date_ret.setDate(date_ret.getDate() + 1);\r\n      date_ret.setSeconds(0);\r\n      date_ret.setMinutes(0);\r\n      date_ret.setHours(hour);\r\n    }\r\n  } else\r\n  if (hour_str === '*' &amp;&amp; min_str !== '*' &amp;&amp; isNaN(min) === false) {\r\n    \/\/ once a hour at a given minute\r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(0);\r\n    date_ret.setMinutes(min);\r\n    \/\/ schedule for next hour\r\n    date_ret.setHours(date_ret.getHours() + 1);\r\n  } else\r\n  if (isNaN(hour) === false &amp;&amp; isNaN(min) === false) {\r\n    \/\/ once a day for a given hour and a given minute \r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(0);\r\n    date_ret.setMinutes(min);\r\n    date_ret.setHours(hour);\r\n    if (date_ret.getTime() &lt; date_now.getTime()) {\r\n      \/\/ schedule for tomorrow\r\n      date_ret.setDate(date_ret.getDate() + 1);\r\n    }\r\n  } else {\r\n    log('getNextRecurringDate illegal input hour_str &lt;' + hour_str + '&gt; min_str &lt;' + min_str + '&gt;');\r\n    throw new Error('getNextRecurringDate illegal input hour_str &lt;' + hour_str + '&gt; min_str &lt;' + min_str + '&gt;');\r\n    return null;\r\n  }\r\n  return date_ret;\r\n}<\/pre>\n<h3>We need a few small utilities<\/h3>\n<p>The common utility function that merely checks if our JavaScript exists used by both <strong>OnUpdate(doc,meta), <\/strong>shown above, and the timer <strong>Callback(doc), <\/strong>shown later.\u00a0 Below is <strong>verifyFunctionExistsViaEval(curDoc, id) <\/strong>which takes two arguments a JSON control document and the KEY for that document.<\/p>\n<p>This lets us know immediately, on deployment, if there was an issue with a naming mismatch between the JSON control record document and the actual name of the business logic function in the JavaScript code.<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">function verifyFunctionExistsViaEval(curDoc, id) {\r\n  var result = false;\r\n  try {\r\n    \/\/ check for function if missing this is invalid return result\r\n    result = eval(\"typeof \" + curDoc.action + \" === 'function';\");\r\n    if (result === false) {\r\n      if (curDoc.verbose.scheduler &gt;= 1)\r\n        log(\"Warn\/Disable (No Action and No Re-Arm), because required 'action' of \" +\r\n          curDoc.action + \"(doc) does not exist, id is\", id);\r\n      return result;\r\n    }\r\n  } catch (e) {\r\n    log('verifyFunctionExistsViaEval Error exception:', e);\r\n  }\r\n  return result;\r\n}<\/pre>\n<p>Note, if an attempt to run a non-existent function the end user will get a warning in the Application log <em>cron_impl_2func_651.log<\/em> \u00a0to correct the issue.<\/p>\n<p style=\"border: 1px solid black;max-width: 1000px;padding: 4px;font-size: 60%\">2020-04-22T16:20:38.725-07:00 [INFO] &#8220;Warn\/Disable (No Action and No Re-Arm), because required &#8216;action&#8217; of doCronMyNewFunction(doc) does not exist, id is&#8221; &#8220;recurring_event::1&#8221;<\/p>\n<p>This correction can be done via a Pause\/Resume adding the function and then adjusting the control document with the specified id or KEY (via a toggle active to false then true) -or- adjusting the control document to point to an existing function in your handler.<\/p>\n<p>Next the utility <strong>toNumericFixed(number, precision)<\/strong> just allows nice compact formatting of floats for our log messages.<\/p>\n<pre class=\"\">function toNumericFixed(number, precision) {\r\n  var multi = Math.pow(10, precision);\r\n  return Math.round((number * multi).toFixed(precision + 1)) \/ multi;\r\n}<\/pre>\n<p>Finally, the utility <strong>toLocalISOTime(d)<\/strong> just allows nice compact formatting of Dates for our log messages.<\/p>\n<pre class=\"\">function toLocalISOTime(d) {\r\n  var tzoffset = (new Date()).getTimezoneOffset() * 60000; \/\/offset in milliseconds\r\n  return (new Date(d.getTime() - tzoffset)).toISOString().slice(0, -1);\r\n}<\/pre>\n<h3>We need a timer callback to execute the user logic and re-arm the timer<\/h3>\n<p>The final JavaScript function in &#8220;cron_impl_2func_651&#8221; is the Timer callback, which is called when the scheduled timer fires. The callback function must be a top-level function that takes a single argument, the context.<\/p>\n<p>In this case in our OnUpdate handler we referenced a JavaScript function of <strong>Callback(doc)<\/strong> with a context of doc (our active scheduler control document of type=&#8221;recurring_event&#8221;)<\/p>\n<p>In version 6.6 we can create another timer within a timer but for all previous versions we will need to trigger a mutation to a &#8220;helper&#8221; function (we carefully avoid infinite recursion).\u00a0 In 6.6 the helper function is not need and the logic is substantially simplified.<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">function Callback(doc) {\r\n  try {\r\n    var fired_at = new Date();\r\n\r\n    \/\/ Check if further analysis is needed we only trigger on a recurring_event that is active\r\n    if (doc.type !== \"recurring_event\") return;\r\n    \/\/ doc must have 'action', 'dynamic {}', verbose {}, dynamic.state\r\n    if (!doc.action || !doc.dynamic || !doc.verbose || !doc.dynamic.state) return;\r\n    \/\/ process any doc.dynamic.state BUT pending \r\n    if (doc.dynamic.state === \"pending\") return;\r\n\r\n    \/\/ ==================\r\n    \/\/ Check if still active\r\n\r\n    \/\/ We make sure that in KV the 'doc' still exists and that it is still active if not just \r\n    \/\/ return thus skipping the action and not Re-arming the timer. Note `travel-sample` is \r\n    \/\/ aliased to the map 'cron_bkt\r\n\r\n    var mid = doc.type + '::' + doc.id; \/\/ make our KEY\r\n    var curDoc = null;\r\n    try {\r\n      \/\/ read the current version of doc from KV, e.g. curDoc\r\n      curDoc = cron_bkt[mid];\r\n    } catch (e) {} \/\/ needed for pre 6.5, note pure 6.5+ deployment returns null sans exception\r\n\r\n    var reason = null;\r\n    if (!curDoc || curDoc === null) {\r\n      reason = \"cron document is missing\";\r\n    } else\r\n    if (!curDoc.active) {\r\n      reason = \"cron document has active = false\";\r\n    } else\r\n    if (!curDoc.dynamic.state || curDoc.dynamic.state !== doc.dynamic.state) {\r\n      reason = \"cron document wrong dynamic.state expected \" + doc.dynamic.state;\r\n    } else\r\n    if (crc64(doc) !== crc64(curDoc)) {\r\n      reason = \"cron document changed\";\r\n    }\r\n\r\n    if (reason !== null) {\r\n      if (!curDoc || curDoc === null || curDoc.verbose.scheduler &gt;= 1) {\r\n        log('Callback X ' + mid + \" ignore\/stop this timer's schedule because \" + reason);\r\n      }\r\n      if (!curDoc || curDoc === null || curDoc.verbose.scheduler &gt;= 4) {\r\n        log('Callback Y ' + mid + ' timer doc', doc);\r\n        log('Callback Z ' + mid + ' KV curDoc', curDoc);\r\n      }\r\n      return;\r\n    }\r\n\r\n    \/\/ ==================\r\n    \/\/ Verify user routine exists and if so eval it \r\n\r\n    \/\/ Assume curDoc.action contains something like \"doCronActionA\" and we have a function in \r\n    \/\/ this handler like \"doCronActionA(doc)\". Below we use curDoc as the end user should be \r\n    \/\/ able to alter the eval'd JavaScript function.  We will execute two (2) evals.\r\n\r\n    \/\/ First eval check the JavaScript function exists.  The eval occurs in a common \r\n    \/\/ utility function shared with Callback\r\n    if (!verifyFunctionExistsViaEval(curDoc, mid)) {\r\n      \/\/ curDoc.action did not exist, we have already logged the issue\r\n      return;\r\n    }\r\n\r\n    \/\/ Second eval execute and process the user function we execute the defined function \r\n    \/\/ with an argument of curDoc\r\n    var beg_act = new Date();\r\n    var result = null;\r\n    eval(\"result = \" + curDoc.action + \"(curDoc);\");\r\n    var end_act = new Date();\r\n    var atime_ms = end_act.getTime() - beg_act.getTime();\r\n\r\n    if (curDoc.verbose.scheduler &gt;= 2)\r\n      log('Callback R ' + mid + ' action took ' + toNumericFixed((atime_ms \/ 1000), 3) +\r\n        ' sec., returned ' + result);\r\n\r\n    \/\/ ==================\r\n    \/\/ Calculate next time and mutate the control document for our our helper function\r\n    \/\/ which will create another mutation such that OnUpdate of this function will pick\r\n    \/\/ it up and generate the timer (avoids the MB-38554 issue).\r\n\r\n    var hour = curDoc.hour;\r\n    var min = curDoc.min;\r\n    var date_timer = getNextRecurringDate(hour, min);\r\n\r\n    curDoc.dynamic.prev_delay =\r\n      toNumericFixed(((fired_at.getTime() \/ 1000) - curDoc.dynamic.next_sched), 3);\r\n    curDoc.dynamic.prev_sched = curDoc.dynamic.next_sched;\r\n    curDoc.dynamic.prev_etime = Math.round(fired_at.getTime() \/ 1000);\r\n    curDoc.dynamic.prev_atime = toNumericFixed((atime_ms \/ 1000), 3);\r\n\r\n    curDoc.dynamic.state = \"pending\";\r\n    curDoc.dynamic.next_sched = Math.round(date_timer.getTime() \/ 1000);\r\n    \r\n    try {\r\n      cron_bkt[mid] = curDoc;\r\n    } catch (e) {\r\n      log('Callback help: F ' + mid + ' FATAL could not update KV cron cycle ' + curDoc.action);\r\n      return;\r\n    }\r\n\r\n    if (curDoc.verbose.scheduler &gt;= 1) {\r\n      log('Callback A ' + mid + ' gen mutation #1 to doc to force schedule rearm at ' +\r\n        toLocalISOTime(date_timer));\r\n    }\r\n    if (curDoc.verbose.scheduler &gt;= 2) {\r\n      log('Callback B ' + mid + ' sched ' + curDoc.dynamic.prev_sched +\r\n        ', actual ' + curDoc.dynamic.prev_etime +\r\n        ', delay ' + curDoc.dynamic.prev_delay +\r\n        ', took ' + curDoc.dynamic.prev_atime);\r\n    }\r\n    if (curDoc.verbose.scheduler &gt;= 3) {\r\n      log('Callback C ' + mid + ' curDoc', curDoc);\r\n    }\r\n  } catch (e) {\r\n    var mid = doc.type + '::' + doc.id; \/\/ make our KEY\r\n    log('Callback E ' + mid + ' Error exception:', e);\r\n  }\r\n}<\/pre>\n<h3>We need a helper function to trigger a new mutation<\/h3>\n<p>Because prior to 6.6 (which is not yet released) you cannot create a timer from within an executing timer&#8217;s callback we need a second Eventing Function (along with &#8220;allow_interbucket_recursion&#8221;:true) to trigger a mutation such that we can generate all our timers in the main Eventing Function&#8217;s OnUpdate(doc,meta) entry point.\u00a0 We do this as follows:<\/p>\n<ol>\n<li>cron_impl_2func_651 <strong>OnUpdate(doc,meta)<\/strong> receives a mutation, schedules a timer<\/li>\n<li>cron_impl_2func_651 After an amount of time when the timer matures the <strong>Callback(doc)<\/strong> routine is executed, first runs the desired user action and then it creates a mutation #1 on the control document (which is not seen by the creating Function to prevent recursion)<\/li>\n<li>cron_impl_2func_651_help <strong>OnUpdate(doc,meta)<\/strong> receives a mutation, makes another mutation #2 on the control document this triggers 1. above in an endless cycle.<\/li>\n<\/ol>\n<p>Note, in Couchbase release 6.6 we don&#8217;t need a helper function at all because you are allowed to you create a timer from within an executing timer.\u00a0 This greatly simplifies the needed logic to make a <em>cron<\/em> system<a id=\"footnote_2\"><\/a><sup>[2]<\/sup>.<\/p>\n<p>The sole JavaScript function in &#8220;cron_impl_2func_651_help&#8221; <strong>OnUpdate(doc,meta)<\/strong> is shown below.<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:default decode:true\">function OnUpdate(doc, meta) {\r\n  \/\/ fix for 6.5.X growing bucket ops\r\n  if (meta.id.startsWith(\"fix_timer_scan_issue:\")) upsertOneDocPerBucket(doc, meta);\r\n  \r\n  try {\r\n    \/\/ Check that doc has desired values\r\n    if (!doc.type || doc.type !== \"recurring_event\" || !doc.active || doc.active != true) return;\r\n    \/\/ doc must have 'action', 'dynamic {}', verbose {}, dynamic.state\r\n    if (!doc.action || !doc.dynamic || !doc.verbose || !doc.dynamic.state) return;\r\n    \/\/ Only process state pending this will only exist for a 'breif' time\r\n    if (doc.dynamic.state !== \"pending\") return;\r\n\r\n    var mid = doc.type + '::' + doc.id; \/\/ make our KEY\r\n    var newdoc = null;\r\n    try {\r\n      \/\/ read the current version of doc from KV, e.g. curDoc\r\n      newdoc = cron_bkt[mid];\r\n    } catch (e) {} \/\/ needed for pre 6.5, note pure 6.5+ deployment returns null sans exception\r\n    \r\n    var reason = null;\r\n    if (!newdoc || newdoc == null) {\r\n      reason = \"cron document is missing\";\r\n    } else\r\n    if (!newdoc.active) {\r\n      reason = \"cron document has active = false\";\r\n    } else\r\n    if (!newdoc.dynamic.state || newdoc.dynamic.state !== doc.dynamic.state) {\r\n      reason = \"cron document wrong dynamic.state expected \" + doc.dynamic.state;\r\n    } else\r\n    if (crc64(doc) !== crc64(newdoc)) {\r\n      reason = \"cron document changed\";\r\n    }\r\n    if (reason != null) {\r\n      if (!newdoc || newdoc == null || newdoc.verbose.scheduler &gt;= 1) {\r\n        log('OnUpdate help: X stopping schedule because ' + reason + ',', newdoc)\r\n        return;\r\n      }\r\n    }\r\n\r\n    newdoc.dynamic.state = \"rearm\";\r\n    \/\/ cron_bkt[mid] = newdoc;\r\n    if (!tryBucketKvWriteWithLog('OnUpdate help: F', mid, newdoc)) {\r\n      \/\/ Failed to write newdoc to cron_bkt[key] the error has been logged\r\n      \/\/ and there is nothing more we can do.\r\n      return;\r\n    }\r\n\r\n    if (newdoc.verbose.scheduler &gt;= 1) {\r\n      log('OnUpdate help: A ' + mid + ' mutation #2 to doc to force schedule rearm');\r\n    }\r\n    if (newdoc.verbose.scheduler &gt;= 3) {\r\n      log('OnUpdate help: B ' + mid + ',', newdoc);\r\n    }\r\n  } catch (e) {\r\n    log('OnUpdate help: E ' + meta.id + ', Error exception:', e);\r\n  }\r\n}\r\n\r\nfunction tryBucketKvWriteWithLog(tag, key, doc) {\r\n  var success = false;\r\n  var tries = 0;\r\n  while (tries &lt; 10) {\r\n    tries++;\r\n    try {\r\n      \/\/ critical that the below succeeds, because if it doesn't the cron cycle will break\r\n      cron_bkt[key] = doc;\r\n      success = true;\r\n      break;\r\n    } catch (e) {\r\n      log(tag + ' ' + key + ' WARN failed to update KV tries ' + tries, e);\r\n    }\r\n  }\r\n  if (!success) {\r\n    log(tag + ' ' + +key + ' FATAL could not update KV cron cycle, tried ' + tries + ', stoping ' + curDoc.action);\r\n  }\r\n  return success;\r\n}<\/pre>\n<h3>The helper function needs some utilities<\/h3>\n<p>These utilities provide a <em>fix for 6.5.X growing bucket ops<\/em> by ensuring an Eventing timer is fired on every vBucket in a timely fashion.<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction upsertOneDocPerBucket(doc, meta) {\r\n\r\n    var crcTable = makeCRC32Table();\r\n\r\n    \/\/ make one doc per bucket\r\n    var isVerbose = 0;\r\n    var isMacOS = false; \/\/ would be nice if this was an exposed constant in Eventing\r\n    var numvbs = 1024;   \/\/ default is linux\/PC\r\n    if (isMacOS) {\r\n        numvbs = 64;\r\n    }\r\n\r\n    var beg = (new Date).getTime();\r\n    var result = getKeysToCoverAllPartitions(crcTable, \"_tmp_vbs:\", numvbs);\r\n\r\n    for (var vb=0; vb&lt;numvbs; vb++) {\r\n        \/\/ brute force to fit a key prefix into a vBucket\r\n         var tst = result[vb];\r\n        if (isVerbose &gt; 1  || (isVerbose == 1) &amp;&amp; (vb &lt; 3 || vb &gt; numvbs -4)) {\r\n            log(\"KEY: \" + tst);\r\n        } else {\r\n            if (vb == 5) console.log(\"\\t*\\n\\t*\\n\\t*\");\r\n        }\r\n        \/\/ update the items to trigger a mutation for our PRIMARY fucntion\r\n        cron_bkt[tst] = { \"type\": \"_tmp_vbs\", \"vb\": vb, \"ts_millis\": beg, \"key\": tst };\r\n    }\r\n    var end = (new Date).getTime();\r\n    log(\"seeding one doc to each vBucket in primary_bucket alias (took \" + (end - beg) + \" mililis)\");\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction showHex(n) {\r\n    return n.toString(16);\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction makeCRC32Table() {\r\n    var crcTable = [];\r\n    var c;\r\n    for(var n =0; n &lt; 256; n++){\r\n        c = n;\r\n        for(var k =0; k &lt; 8; k++){\r\n            c = ((c&amp;1) ? (0xEDB88320 ^ (c &gt;&gt;&gt; 1)) : (c &gt;&gt;&gt; 1));\r\n        }\r\n        crcTable[n] = c;\r\n    }\r\n    return crcTable;\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction crc32(crcTable,str) {\r\n    var crc = 0 ^ (-1);\r\n    for (var i = 0; i &lt; str.length; i++ ) {\r\n        crc = (crc &gt;&gt;&gt; 8) ^ crcTable[(crc ^ str.charCodeAt(i)) &amp; 0xFF];\r\n    }\r\n    return (crc ^ (-1)) &gt;&gt;&gt; 0;\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction getKeysToCoverAllPartitions(crcTable,keyPrefix,partitionCount) {\r\n    var result = [];\r\n    var remaining = partitionCount;\r\n    for (var i = 0; remaining &gt; 0; i++) {\r\n      var key = keyPrefix + i;\r\n      var rv = (crc32(crcTable,key) &gt;&gt; 16) &amp; 0x7fff;\r\n      var actualPartition = rv &amp; partitionCount - 1;\r\n      if (!result[actualPartition] || result[actualPartition] === undefined) {\r\n        result[actualPartition] = key;\r\n        remaining--;\r\n      }\r\n    }\r\n    return result;\r\n}\r\n<\/pre>\n<h3>Now let\u2019s deploy \u00a0the two Eventing Functions<\/h3>\n<p>We reviewed a lot code and the design of the initial scheduler, now it\u2019s time to see everything working together.<\/p>\n<p>Remember for this example, three buckets <strong>travel-sample (<\/strong>a sample default data set), <strong>metadata<\/strong>, (the metadata bucket is a scratchpad for Eventing and can be shared with other Eventing functions), and finally the <strong>crondata<\/strong> (which holds our cron schedules). The <strong>travel-sample<\/strong> bucket has a size of 100MB and the other two buckets <strong>metadata<\/strong> and <strong>crondata<\/strong> should both have a size of 200MB and already exist as per the directions in \u201cPrerequisites\u201d.<\/p>\n<ul>\n<li>Verify your current bucket configuration by accessing the <strong>Couchbase Web Console &gt; Buckets<\/strong> page:<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8703 size-full\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_01_00_buckets.png\" alt=\"Eventing cron update\" width=\"2040\" height=\"650\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_00_buckets.png 2040w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_00_buckets-300x96.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_00_buckets-1024x326.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_00_buckets-768x245.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_00_buckets-1536x489.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_00_buckets-20x6.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_00_buckets-1320x421.png 1320w\" sizes=\"auto, (max-width: 2040px) 100vw, 2040px\" \/><\/li>\n<\/ul>\n<p>To deploy the Eventing Function \u201c<em>cron_impl_2func_651<\/em>\u201d you can follow one of two methods:<\/p>\n<ul>\n<li>Basic complexity, Method #1 Download\/Import<\/li>\n<li>Medium complexity, Method #2 Manually Add Function, Cut-n-Paste JavaScript<\/li>\n<\/ul>\n<h3>Method #1 Download\/Import<\/h3>\n<h4>Import the 1st Function &#8220;cron_impl_2func_651&#8221;<\/h4>\n<p>Download the first Eventing Function with all the required settings, Right-click on the following link and choose\u00a0<strong>Save Link As<\/strong>\u00a0to download the file <a href=\"https:\/\/raw.githubusercontent.com\/couchbaselabs\/blog-source-code\/master\/Strabala\/CronFiles\/cron_impl_2func_651.json\"><strong>cron_impl_2func_651.json<\/strong><\/a> onto your local file system.<\/p>\n<p>From the <strong>Couchbase Web Console &gt; Eventing<\/strong> page, click <strong>IMPORT<\/strong>, navigate to the file <em>cron_impl_2func_651.json<\/em>, select it and open it. The <strong>ADD FUNCTION<\/strong> dialog appears.<\/p>\n<p>In the <strong>ADD FUNCTION<\/strong> dialog, for individual Function elements provide the below information. Note the JSON file <em>cron_impl_2func_651.json<\/em> will pre-configure all settings correctly for this example:<\/p>\n<ul>\n<li>For the <strong>Source Bucket<\/strong> drop-down, verify it is set to <strong>crondata<\/strong>.<\/li>\n<li>For the <strong>Metadata Bucket<\/strong> drop-down, verify it is set to <strong>metadata<\/strong>.<\/li>\n<li>Verify that <strong>cron_impl_2func_651<\/strong>\u00a0is the name of the Function you are creating in the <strong>Function Name<\/strong> text-box.<\/li>\n<li>[Optional Step] Enter text <b>A cron like scheduler part 1<\/b>, in the <strong>Description<\/strong> text-box.<\/li>\n<li>For the <strong>Settings<\/strong> option, use the default values.<\/li>\n<li>For the <strong>Bindings<\/strong> option, verify that two bindings exist.<\/li>\n<li>For the binding, the &#8220;bucket alias&#8221;, specifies <strong>cron_bkt<\/strong> as the &#8220;alias name&#8221; of the bucket, and select<br \/>\n<strong>crondata<\/strong> as the associated bucket, and the mode should be &#8220;read and write&#8221;.<\/li>\n<li>For the binding, the &#8220;bucket alias&#8221;, specifies <strong>ts_bkt<\/strong> as the &#8220;alias name&#8221; of the bucket, and select<br \/>\n<strong>travel-sample<\/strong> as the associated bucket, and the mode should be &#8220;read and write&#8221;.<\/li>\n<li>Your settings in the dialog should look like the following:<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8704\" style=\"margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_01_add_function.png\" alt=\"Eventing cron update\" width=\"643\" height=\"640\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function.png 1286w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-300x299.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-1024x1019.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-150x150.png 150w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-768x764.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-65x65.png 65w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-50x50.png 50w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-20x20.png 20w\" sizes=\"auto, (max-width: 643px) 100vw, 643px\" \/><\/li>\n<li>After verifying all the required information in the ADD FUNCTION dialog, click Next: Add Code. The <strong>cron_impl_2func_651<\/strong> dialog appears (with the JavaScript code pre-loaded).<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8711 size-full\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_03_code_editor_done.png\" alt=\"Eventing cron update\" width=\"2040\" height=\"1542\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done.png 2040w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-300x227.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-1024x774.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-768x581.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-1536x1161.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-20x15.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-1320x998.png 1320w\" sizes=\"auto, (max-width: 2040px) 100vw, 2040px\" \/><\/li>\n<li>To return to the Eventing screen, click the &#8216;<strong>&lt; back to Eventing<\/strong>&#8216; link (below the editor) or click the <strong>Eventing<\/strong> tab.<\/li>\n<\/ul>\n<h4>Import the 2nd Function &#8220;cron_impl_2func_651_help&#8221;<\/h4>\n<p>Download the second Eventing Function with all the required settings, Right-click on the following link and choose <strong>Save Link As<\/strong>\u00a0to download the file <a href=\"https:\/\/raw.githubusercontent.com\/couchbaselabs\/blog-source-code\/master\/Strabala\/CronFiles\/cron_impl_2func_651_help.json\"><strong>cron_impl_2func_651_help.json<\/strong><\/a> onto your local file system.<\/p>\n<p>From the <strong>Couchbase Web Console &gt; Eventing<\/strong> page, click <strong>IMPORT<\/strong>, navigate to the file <em>cron_impl_2func_651_help.json<\/em>, select it and open it. The <strong>ADD FUNCTION<\/strong> dialog appears.<\/p>\n<p>In the <strong>ADD FUNCTION<\/strong> dialog, for individual Function elements provide the below information. Note the JSON file <em>cron_impl_2func_651_help.json<\/em> will pre-configure all settings correctly for this example:<\/p>\n<ul>\n<li>For the <strong>Source Bucket<\/strong> drop-down, verify it is set to <strong>crondata<\/strong>.<\/li>\n<li>For the <strong>Metadata Bucket<\/strong> drop-down, verify it is set to <strong>metadata<\/strong>.<\/li>\n<li>Verify that <strong>cron_impl_2func_651_help<\/strong>\u00a0is the name of the Function you are creating in the <strong>Function Name<\/strong> text-box.<\/li>\n<li>[Optional Step] Enter text <b>A cron like scheduler helper part 1<\/b>, in the <strong>Description<\/strong> text-box.<\/li>\n<li>For the <strong>Settings<\/strong> option, use the default values.<\/li>\n<li>For the <strong>Bindings<\/strong> option, verify that only one binding exists.<\/li>\n<li>For the binding, the &#8220;bucket alias&#8221;, specifies <strong>cron_bkt<\/strong> as the &#8220;alias name&#8221; of the bucket, and select<br \/>\n<strong>crondata<\/strong> as the associated bucket, and the mode should be &#8220;read and write&#8221;.<\/li>\n<li>Your settings in the dialog should look like the following:<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8706\" style=\"margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_01_add_function_help.png\" alt=\"Eventing cron update\" width=\"643\" height=\"640\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help.png 1286w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-300x299.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-1024x1019.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-150x150.png 150w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-768x764.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-65x65.png 65w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-50x50.png 50w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-20x20.png 20w\" sizes=\"auto, (max-width: 643px) 100vw, 643px\" \/><\/li>\n<li>After verifying all the required information in the ADD FUNCTION dialog, click Next: Add Code. The <strong>cron_impl_2func_651_help<\/strong> dialog appears (with the JavaScript code pre-loaded).<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8729\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/04\/ev_cr_1u_02_code_editor_done_help.png\" alt=\"Eventing cron update\" width=\"1020\" height=\"770\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help.png 2044w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-300x227.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-1024x774.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-768x580.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-1536x1160.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-20x15.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-1320x997.png 1320w\" sizes=\"auto, (max-width: 1020px) 100vw, 1020px\" \/><\/li>\n<li>To return to the Eventing screen, click the &#8216;<strong>&lt; back to Eventing<\/strong>&#8216; link (below the editor) or click the <strong>Eventing<\/strong> tab.<\/li>\n<\/ul>\n<h3>Method #2 Manually Add Function, Cut-n-Paste JavaScript<\/h3>\n<h4>Manually create &#8220;cron_impl_2func_651&#8221;<\/h4>\n<p>To add the first Eventing function from the <strong>Couchbase Web Console &gt; Eventing<\/strong> page, click <strong>ADD FUNCTION<\/strong>, to add a new Function. The <strong>ADD FUNCTION<\/strong> dialog appears.<\/p>\n<p>In the <strong>ADD FUNCTION<\/strong> dialog, for individual Function elements provide the below information:<\/p>\n<ul>\n<li>For the <strong>Source Bucket<\/strong> drop-down, set to <strong>crondata<\/strong>.<\/li>\n<li>For the <strong>Metadata Bucket<\/strong> drop-down, set to <strong>metadata<\/strong>.<\/li>\n<li>Make <strong>cron_impl_2func_651<\/strong>\u00a0is the name of the Function you are creating in the <strong>Function Name<\/strong> text-box.<\/li>\n<li>[Optional Step] Enter text <b>A cron like scheduler part 1<\/b>, in the <strong>Description<\/strong> text-box.<\/li>\n<li>For the <strong>Settings<\/strong> option, use the default values.<\/li>\n<li>For the <strong>Bindings<\/strong> option, create two bindings:<\/li>\n<li>For the binding, the &#8220;bucket alias&#8221;, specifies <strong>cron_bkt<\/strong> as the &#8220;alias name&#8221; of the bucket, and select<br \/>\n<strong>crondata<\/strong> as the associated bucket, and the mode should be &#8220;read and write&#8221;.<\/li>\n<li>For the binding, the &#8220;bucket alias&#8221;, specifies <strong>ts_bkt<\/strong> as the &#8220;alias name&#8221; of the bucket, and select<br \/>\n<strong>travel-sample<\/strong> as the associated bucket, and the mode should be &#8220;read and write&#8221;.<\/li>\n<li>After configuring your settings your dialog should look like this:<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8704\" style=\"margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_01_add_function.png\" alt=\"Eventing cron update\" width=\"643\" height=\"640\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function.png 1286w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-300x299.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-1024x1019.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-150x150.png 150w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-768x764.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-65x65.png 65w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-50x50.png 50w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function-20x20.png 20w\" sizes=\"auto, (max-width: 643px) 100vw, 643px\" \/><\/li>\n<li>After providing all the required information in the <strong>ADD FUNCTION<\/strong> dialog, click <strong>Next: Add Code<\/strong>. The <strong>cron_impl_2func_651<\/strong> dialog appears. The <strong>cron_impl_2func_651<\/strong>\u00a0dialog initially contains a placeholder code block. You will substitute your actual <strong>cron_impl_2func_651<\/strong>\u00a0code in this block.<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8707\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_02_code_editor.png\" alt=\"Eventing cron update\" width=\"800\" height=\"186\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor.png 2042w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor-300x70.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor-1024x239.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor-768x179.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor-1536x358.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor-20x5.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor-1320x308.png 1320w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/li>\n<li style=\"list-style-type: none\"><\/li>\n<li>Copy the following Eventing Function JavaScript source (618 lines) and paste it in the placeholder code block of <strong>cron_impl_2func_651<\/strong>\n<pre class=\"height-set:true height:800 toolbar-overlay:false nums:false lang:js decode:true\">\/* \r\nFunction \"cron_impl_2func_651\" also requires \"cron_impl_2func_651_help\"\r\n\r\nCreate a basic cron system using Eventing allows a recurring function to execute activity at a \r\nspecified time every day, hour, min, 30 sec., and 15 sec.  We use a bucket called 'crondata' \r\naliased to 'cron_bkt' which can hold one or more control documents of type = \"recurring_event\".\r\n\r\nThe following uses of timers do not work reliably in Couchbase versions 6.5 and 6.5.1\r\n  a) scheduling an Eventing timer within a timer's callback \r\n  b) overwriting an existing timer by id\r\n  \r\nIn addition the ability to cancel a timer does not exist in Couchbase versions 6.5 and 6.5.1\r\n  \r\nFor this example, we supply one real user function that builds a recurring 'static' cache document\r\nfrom bucket `travel-sample` via an N1QL query and save the result back to `travel-sample` via\r\nthe alais 'ts_bkt'.  This JavaScript function is doCronActionA(), we also provide two placeholders\r\ndoCronActionB() and doCronActionC() for additional experimentation.\r\n\r\nTest Doc:\r\n   {\r\n        \"type\":\"recurring_event\",   \/\/ The KEY will be &lt;&lt;type&gt;&gt;::&lt;&lt;id&gt;&gt;\r\n        \"id\":1,                     \/\/\r\n        \"hour\":14,                  \/\/ The hour of the day 0-23, *, *2X, *4X to trigger\r\n        \"min\":54,                   \/\/ The minute in the hour 0-59, *, *2X, *4X to trigger\r\n        \"action\":\"doCronActionA\",   \/\/ What function to run on the trigger\r\n        \"active\":false,             \/\/ Flag to arm or disable this schedule\r\n        \"verbose\" : {\r\n          \"user_func\":2,            \/\/ Logging level for the action logic : 0=none, etc. etc.\r\n          \"scheduler\":3             \/\/ Logging level for the cron logic   : 0=none, etc. etc.\r\n        },\r\n        \"dynamic\" : {\r\n          \"state\":\"arm\",            \/\/ States \"arm\"|\"rearm\"|\"pending\" if any value but \"pending\" start a schedule\r\n          \"next_sched\": 0,          \/\/ Number of seconds since epoch to next desired schedule\r\n          \"prev_sched\": 0,          \/\/ Number of seconds since epoch for previous schedule\r\n          \"prev_etime\": 0,          \/\/ Number of seconds since epoch for previous schedule actual exec time\r\n          \"prev_delay\": 0,          \/\/ Number of seconds that the timer was delayed from the schedule\r\n          \"prev_atime\": 0           \/\/ Number of seconds taken by the user 'action'\r\n        }\r\n    }\r\n    \r\n    INSERT INTO `crondata` (KEY,VALUE) VALUES (\"recurring_event::1\", \r\n    {\r\n        \"type\":\"recurring_event\",\r\n        \"id\":1,\r\n        \"hour\":14,\r\n        \"min\":54,\r\n        \"action\":\"doCronActionA\",\r\n    \t\"verbose\" : {\r\n          \"user_func\":2,\r\n          \"scheduler\":3\r\n        },\r\n        \"active\":false,\r\n\t    \"dynamic\" : {\r\n          \"state\": \"arm\",\r\n          \"next_sched\": 0,\r\n          \"prev_sched\": 0,\r\n          \"prev_etime\": 0,\r\n          \"prev_delay\": 0,\r\n          \"prev_atime\": 0\r\n\t    }\r\n    } \r\n    );\r\n\r\nNote, you can omit verbose{} and dynamic{} as they will be auto-created by this main Eventing \r\nFunction \"cron_impl_2func_651\". If verbose{} is missing the logging levels will default to \r\nverbose\" : {  \"user_func\":1, \"scheduler\":1 }\r\n\r\n    INSERT INTO `crondata` (KEY,VALUE) VALUES (\"recurring_event::1\", \r\n    {\r\n        \"type\":\"recurring_event\",\r\n        \"id\":1,\r\n        \"hour\":14,\r\n        \"min\":54,\r\n        \"action\":\"doCronActionA\",\r\n        \"active\":false\r\n    } \r\n    );\r\n\r\nN1QL : Make an index to query data without specifying keys\r\n    CREATE primary INDEX on `crondata` ;\r\n\r\nN1QL : Verify or inspect settings in schedule\t        \r\n    SELECT * FROM `crondata` WHERE type=\"recurring_event\";\r\n\r\nN1QL : Arm or set active\t    \r\n    UPDATE `crondata` SET active = true WHERE type=\"recurring_event\" AND id=1 ;\r\n    \r\nN1QL : Disarm or set inactive\t\r\n    UPDATE `crondata` SET active = false WHERE type=\"recurring_event\" AND id=1 ;\r\n    \r\nN1QL : Adjust time of trigger\t\r\n    UPDATE `crondata` SET hour = 11, min = 30 WHERE type=\"recurring_event\" AND id=1 ;\r\n\r\nN1QL : Adjust logging\t        \r\n    UPDATE `crondata` SET verbose.user_func = 1,  verbose.scheduler = 0 WHERE type=\"recurring_event\" AND id=1 ;\r\n\r\nN1QL : Delete the schedule\t    \r\n    DELETE FROM `crondata` WHERE type=\"recurring_event\" AND id=1 ;\r\n         \r\nThe action field is important it 'should' exist in this Eventing Function note it could be any \r\nJavaScript name e.g. MyFunc and you must implement like the example doCronActionA(doc) where\r\ndoc will be the currently active item of type = 'recurring_event' read from the alias bucket\r\n\u2018cron_bkt\u2019 when the timer is fired.  The action JavaScript function should return  either true\r\nor false used for logging purposes.  If the action does not exist it is an error and a warning\r\nis logged and the timer is disabled.\r\n\r\nIn Couchbase version 6.5+ to add a new cron like daily function just pause the active handler \r\ninsert your new function doCronActionB(doc) {...} then Resume the eventing handler.  The nice \r\nthing is if a timer was to be fired will the function was paused it will NOT be lost, when you \r\nresume the function it will be processed at the next available time slot.  \r\n\r\nAny change to a control structure will create a new recurring schedule or timer and cancel the \r\ncurrent previous schedule this includes changing the verbosity level. The previous timer will \r\ncontinue to run however when executed it will do a Checksum on the current control structure \r\nfrom KV against it\u2019s passed context and if different the Callback will ignore the old schedule.\r\nThis logic could be altered to process immediately if the schedule has expired search for the \r\nstring \"OnUpdate U\" in the code below.\r\n*\/\r\n\r\n\/\/ ==================\r\n\/* BEG USER FUNCTIONS TO RUN ONCE A DAY, HOUR, OR MINUTE - ANYTHING YOU WANT BELOW *\/\r\nfunction doCronActionA(doc) {\r\n  try {\r\n    \/\/ Check that doc has desired values\r\n    if (!doc.type || doc.type !== \"recurring_event\" || !doc.active || doc.active !== true) return;\r\n    if (doc.verbose.user_func &gt;= 1)\r\n      log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);\r\n\r\n    \/\/ this is a 6.5 N1QL query (feature not available in GA prior to 6.5)\r\n    \/\/ Create an embedded N1QL iterator by issuing a SELECT statement to get the\r\n    \/\/ counts of airlines by country.  Make a new document and write it out to KV \r\n\r\n    \/\/ We will use the iterator to create a KV document representing the results of a\r\n    \/\/ HARD lengthy embedded N1QL query and write it back to KV, the idea is to keep\r\n    \/\/ a calculation up to date once a day such that it that can be read 'quickly' \r\n    \/\/ by other Eventing Functions, other Couchbase services or SDKs.   \r\n\r\n    \/\/ Consider if we had 1 million docs in a minute do we really want to use N1QL\r\n    \/\/ to recalculate something that is almost static for all 1 million documents, of \r\n    \/\/ course not, so we make an intermediate value that can be read into Eventing\r\n    \/\/ and used via a single 'light weight' KV read.\r\n\r\n    var q_iter = SELECT country,\r\n      count( * ) cnt\r\n    FROM `travel-sample`\r\n    WHERE `type` = 'airline'\r\n    GROUP BY country;\r\n\r\n    \/\/ loop through the result set and update the map 'accumulate'\r\n    var accumulate = {};\r\n    var idx = 0;\r\n    for (var val of q_iter) {\r\n      if (doc.verbose.user_func &gt;= 2)\r\n        log(doc.action + ' N1QL idx ' + idx + ', country ' + val.country + \" cnt \" + val.cnt);\r\n      accumulate[val.country] = val.cnt;\r\n      idx++;\r\n    }\r\n    \/\/ close out embedded N1QL iterator\r\n    q_iter.close();\r\n\r\n    \/\/ Now let\u2019s make a cached KV document representing a HARD length embedded N1QL\r\n    \/\/ query and write it back to KV, we need a KEY and a type and id and then we \r\n    \/\/ upsert it into the `travel-sample` bucket.\r\n\r\n    var cachedoc = {};\r\n    cachedoc.type = \"cron_cache\";\r\n    cachedoc.id = \"airlines_by_country\";\r\n    cachedoc.date = new Date();\r\n    cachedoc.data = accumulate;\r\n    var ckey = cachedoc.type + '::' + cachedoc.id;\r\n    ts_bkt[ckey] = cachedoc;\r\n    if (doc.verbose.user_func &gt;= 2) {\r\n      log(doc.action + ' upsert to KV with KEY ' + ckey + ' cachedoc ', cachedoc);\r\n    }\r\n  } catch (e) {\r\n    log(doc.action + ' Error exception:', e);\r\n    return false;\r\n  }\r\n  return true;\r\n}\r\n\r\nfunction doCronActionB(doc) {\r\n  try {\r\n    \/\/ check that doc has desired values\r\n    if (doc.type !== \"recurring_event\" || doc.active !== true) return;\r\n    if (doc.verbose.user_func &gt;= 1)\r\n      log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);\r\n\r\n    \/\/ YOUR LOGIC HERE\r\n\r\n  } catch (e) {\r\n    log(doc.action + ' Error exception:', e);\r\n    return false;\r\n  }\r\n  return true;\r\n}\r\n\r\nfunction doCronActionC(doc) {\r\n  try {\r\n    \/\/ check that doc has desired values\r\n    if (doc.type !== \"recurring_event\" || doc.active !== true) return;\r\n    if (doc.verbose.user_func &gt;= 1)\r\n      log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);\r\n\r\n    \/\/ YOUR LOGIC HERE\r\n\r\n  } catch (e) {\r\n    log(doc.action + ' Error exception:', e);\r\n    return false;\r\n  }\r\n  return true;\r\n}\r\n\r\n\/* END USER FUNCTIONS TO RUN ONCE A DAY, HOUR, OR MINUTE - ANYTHING YOU WANT ABOVE *\/\r\n\/\/ ==================\r\n\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\nfunction noopTimer(context) {\r\n    \/\/ fix for 6.5.X growing bucket ops\r\n    try {\r\n        if (context.type === \"_tmp_vbs\" &amp;&amp; context.vb === 0) { \r\n            \/\/ log(\"noopTimer timers firing, printing only for vBucket 0\");\r\n        }\r\n    } catch (e) {\r\n        log(\"OnUpdate Exception in callback noopTimer:\", e);\r\n    }\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\nfunction rearmTimer(context) {\r\n    \/\/ fix for 6.5.X growing bucket ops\r\n    try {\r\n        if (context.type === \"_tmp_vbs\" &amp;&amp; context.vb === 0) { \r\n            \/\/ Update\/touch all docs in the helper_bucket the helper function will then\r\n            \/\/ mutate all 1024 of type == vbs_seed (64 on MacOS) to create a recuring cycle.\r\n            \/\/ log(\"noopTimer timer fired all 1024 vBuckets, logging only vb 0\", context);\r\n            \r\n            \/\/ generate a mutation to re-arm the HELPER function: fix_scan_issue\r\n            \/\/ which will in turn make new mutations for this Function\r\n            var cur = cron_bkt[context.key];\r\n            if (cur &amp;&amp; cur.ts_millis === context.ts_millis) {\r\n                \/\/ log(\"rearmTimer update fix_timer_scan_issue::1 in helper_bucket alias only for vBucket 0\");\r\n                var now = new Date();\r\n                cron_bkt[\"fix_timer_scan_issue::1\"] = { \"last_update\": now };\r\n            } else {\r\n                \/\/ NOOP we had multiple timer cycles, just let this one quietly stop.\r\n            }\r\n        }\r\n    } catch (e) {\r\n        log(\"OnUpdate Exception in callback rearmTimer:\", e);\r\n    }\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\nfunction genNoopTimers(doc, meta, seconds) {\r\n    \/\/ fix for 6.5.X growing bucket ops\r\n    try {\r\n        \/\/ redundant but play it safe\r\n        if (doc.type === \"_tmp_vbs\") {\r\n            \/\/ Since we are using an different function a timer on all our vBuckets do immeadiately (can take up to 15 seconds)\r\n            \/\/ If we used cross bucket recursion to rearm all the timers in a recurring fashion we would add a delay of at least 40 seconds.\r\n            createTimer(noopTimer, new Date(), null, doc);\r\n            if (doc.vb === 0) { \r\n                \/\/ Update\/touch all docs in the helper_bucket the helper function will then\r\n                \/\/ mutate all 1024 of type == vbs_seed (64 on MacOS) to create a recuring cycle.\r\n                \/\/ log(\"noopTimer timer fired all 1024 vBuckets, logging only vb 0\", context);\r\n            \r\n                \/\/ generate a mutation to re-arm the HELPER function: fix_scan_issue\r\n                \/\/ which will in turn make new mutations for this Function\r\n                \r\n                \/\/ log(\"genNoopTimers make timer to rearm fix_timer_scan_issue::1\");\r\n                createTimer(rearmTimer, new Date(new Date().getTime() + seconds * 1000), null, doc);\r\n            }\r\n        }\r\n    } catch (e) {\r\n        log(\"OnUpdate Exception in genNoopTimers:\", e);\r\n    }\r\n}\r\n\r\nfunction OnUpdate(doc, meta) {\r\n  \/\/ fix for 6.5.X growing bucket ops\r\n  if (doc.type === \"_tmp_vbs\") genNoopTimers(doc, meta, 30);\r\n  if (!cron_bkt[\"fix_timer_scan_issue::1\"]) {\r\n      cron_bkt[\"fix_timer_scan_issue::1\"] = {};\r\n  }\r\n    \r\n  try {\r\n    \/\/ Check if further analysis is needed we only trigger on an active recurring_event \r\n    if (doc.type !== \"recurring_event\" || doc.active !== true) return;\r\n\r\n    var update_doc = false;\r\n    if (!doc.dynamic) {\r\n      \/\/ Add if missing doc.dynamic with defaults\r\n      doc.dynamic = {\r\n        \"state\": \"arm\",\r\n        \"next_sched\": 0,\r\n        \"prev_sched\": 0,\r\n        \"prev_etime\": 0,\r\n        \"prev_delay\": 0,\r\n        \"prev_atime\": 0\r\n      };\r\n      \/\/ we need to update the document once we have the next schedule\r\n      update_doc = true;\r\n    }\r\n    if (!doc.verbose) {\r\n      \/\/ Add if missing doc.dynamic with defaults\r\n      doc.verbose = {\r\n        \"user_func\": 1,\r\n        \"scheduler\": 1\r\n      };\r\n      \/\/ we need to update the document once we have the next schedule\r\n      update_doc = true;\r\n    }\r\n    \/\/ Do not process dynamic.state pending\r\n    if (!doc.dynamic || !doc.dynamic.state || doc.dynamic.state === \"pending\") return;\r\n\r\n    var mid = doc.type + \"::\" + doc.id; \/\/ this is the same as meta.id or the KEY\r\n    var hour = doc.hour;\r\n    var min = doc.min;\r\n\r\n    \/\/ Do an eval check the JavaScript function exists. The eval occurs in a common \r\n    \/\/ utility function shared with RecurringCallback\r\n    if (!verifyFunctionExistsViaEval(doc, mid)) {\r\n      \/\/ doc.action did not exist, we have already logged the issue\r\n      return;\r\n    }\r\n\r\n    \/\/ Get the next valid execution time\r\n    var date_timer = getNextRecurringDate(hour, min);\r\n    var next_sched = Math.round(date_timer.getTime() \/ 1000);\r\n    if (!update_doc &amp;&amp; next_sched !== doc.dynamic.next_sched) {\r\n      \/\/ the next_sched should be the same as the setting from the helper application, however\r\n      \/\/ if we undeploy\/deploy or pause\/resume we might haver to reschedule to the next time slot\r\n      log('OnUpdate U ' + mid + ' calculated next_sched !== doc.dynamic.next_sched, delta ' +\r\n        (next_sched - doc.dynamic.next_sched) + ', reschedule');\r\n      update_doc = true;\r\n    }\r\n\r\n    if (update_doc) {\r\n      \/\/ this mutation is recursive and will be suppressed, we ensure we have a dynamic structure\r\n      doc.dynamic.next_sched = next_sched;\r\n\r\n      \/\/ rather then the call a function, to trap and retry if there is a resource issue\r\n      \/\/ cron_bkt[mid] = doc;\r\n      if (!tryBucketKvWriteWithLog('OnUpdate F', mid, doc)) {\r\n        \/\/ Failed to write doc to cron_bkt[key] the error has been logged\r\n        \/\/ and there is nothing more we can do.\r\n        return;\r\n      }\r\n    }\r\n\r\n    \/\/ Schedule an Eventing timer\r\n    var timer_id = createTimer(Callback, date_timer, null, doc);\r\n    if (doc.verbose.scheduler &gt;= 1) {\r\n      log('OnUpdate A ' + mid + ' rcv mutation (initial or rearm) schedule timer at ' +\r\n        toLocalISOTime(date_timer));\r\n    }\r\n    if (doc.verbose.scheduler &gt;= 2) {\r\n      log('OnUpdate B ' + mid + ' recurring timer was created, timer_id ' + timer_id);\r\n    }\r\n  } catch (e) {\r\n    log('OnUpdate E ' + meta.id + ', Error exception:', e);\r\n  }\r\n}\r\n\r\nfunction getNextRecurringDate(hour_str, min_str) {\r\n  \/\/ Note Javascript Dates are in milliseconds\r\n  var date_now = new Date();\r\n  var date_ret = new Date();\r\n  var hour;\r\n  var min;\r\n\r\n  try {\r\n    hour = parseInt(hour_str);\r\n  } catch (e) {}\r\n  try {\r\n    min = parseInt(min_str);\r\n  } catch (e) {}\r\n\r\n  \/\/ Note, this is only a simplistic partial 'crontab' syntax with some slight extensions\r\n  \/\/ it allows once a day, once an hour, once a minute.  It also contains some non-standard \r\n  \/\/ syntax to provide the ability to execute twice a minute or four times a minute.\r\n\r\n  if (hour_str === '*4X' &amp;&amp; min_str === '*4X') {\r\n    \/\/ once every 15 seconds or four times a minute\r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(15);\r\n    while (date_ret.getTime() &lt; date_now.getTime()) {\r\n      date_ret.setSeconds(date_ret.getSeconds() + 15);\r\n    }\r\n    return date_ret;\r\n  } else\r\n  if (hour_str === '*2X' &amp;&amp; min_str === '*2X') {\r\n    \/\/ once every 30 seconds or twice a minute\r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(30);\r\n    while (date_ret.getTime() &lt; date_now.getTime()) {\r\n      date_ret.setSeconds(date_ret.getSeconds() + 30);\r\n    }\r\n    return date_ret;\r\n  } else\r\n  if (hour_str === '*' &amp;&amp; min_str === '*') {\r\n    \/\/ once a minute \r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(0);\r\n    date_ret.setMinutes(date_ret.getMinutes() + 1);\r\n  } else\r\n  if (hour_str !== '*' &amp;&amp; isNaN(hour) === false &amp;&amp; min_str === '*') {\r\n    \/\/ once a minute only for a given hour\r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(0);\r\n    date_ret.setMinutes(date_ret.getMinutes() + 1);\r\n    if (date_ret.getTime() &lt; date_now.getTime()) {\r\n      date_ret.setHours(hour);\r\n    }\r\n    if (date_ret.getTime() &gt; date_now.getTime()) {\r\n      date_ret.setDate(date_ret.getDate() + 1);\r\n      date_ret.setSeconds(0);\r\n      date_ret.setMinutes(0);\r\n      date_ret.setHours(hour);\r\n    }\r\n  } else\r\n  if (hour_str === '*' &amp;&amp; min_str !== '*' &amp;&amp; isNaN(min) === false) {\r\n    \/\/ once a hour at a given minute\r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(0);\r\n    date_ret.setMinutes(min);\r\n    \/\/ schedule for next hour\r\n    date_ret.setHours(date_ret.getHours() + 1);\r\n  } else\r\n  if (isNaN(hour) === false &amp;&amp; isNaN(min) === false) {\r\n    \/\/ once a day for a given hour and a given minute \r\n    date_ret.setMilliseconds(0);\r\n    date_ret.setSeconds(0);\r\n    date_ret.setMinutes(min);\r\n    date_ret.setHours(hour);\r\n    if (date_ret.getTime() &lt; date_now.getTime()) {\r\n      \/\/ schedule for tomorrow\r\n      date_ret.setDate(date_ret.getDate() + 1);\r\n    }\r\n  } else {\r\n    log('getNextRecurringDate illegal input hour_str &lt;' +\r\n      hour_str + '&gt; min_str &lt;' + min_str + '&gt;');\r\n    throw new Error('getNextRecurringDate illegal input hour_str &lt;' +\r\n      hour_str + '&gt; min_str &lt;' + min_str + '&gt;');\r\n    return null;\r\n  }\r\n  return date_ret;\r\n}\r\n\r\nfunction verifyFunctionExistsViaEval(curDoc, id) {\r\n  var result = false;\r\n  try {\r\n    \/\/ check for function if missing this is invalid return result\r\n    result = eval(\"typeof \" + curDoc.action + \" === 'function';\");\r\n    if (result === false) {\r\n      if (curDoc.verbose.scheduler &gt;= 1)\r\n        log(\"Warn\/Disable (No Action and No Re-Arm), because required 'action' of \" +\r\n          curDoc.action + \"(doc) does not exist, id is\", id);\r\n      return result;\r\n    }\r\n  } catch (e) {\r\n    log('verifyFunctionExistsViaEval Error exception:', e);\r\n  }\r\n  return result;\r\n}\r\n\r\nfunction toNumericFixed(number, precision) {\r\n  var multi = Math.pow(10, precision);\r\n  return Math.round((number * multi).toFixed(precision + 1)) \/ multi;\r\n}\r\n\r\nfunction toLocalISOTime(d) {\r\n  var tzoffset = (new Date()).getTimezoneOffset() * 60000; \/\/offset in milliseconds\r\n  return (new Date(d.getTime() - tzoffset)).toISOString().slice(0, -1);\r\n}\r\n\r\nfunction tryBucketKvWriteWithLog(tag, key, doc) {\r\n  var success = false;\r\n  var tries = 0;\r\n  while (tries &lt; 10) {\r\n    tries++;\r\n    try {\r\n      \/\/ critical that the below succeeds, because if it doesn't the cron cycle will break\r\n      cron_bkt[key] = doc;\r\n      success = true;\r\n      break;\r\n    } catch (e) {\r\n      log(tag + ' ' + key + ' WARN failed to update KV tries ' + tries, e);\r\n    }\r\n  }\r\n  if (!success) {\r\n    log(tag + ' ' + +key + ' FATAL could not update KV cron cycle, tried ' + tries + ', stoping ' + curDoc.action);\r\n  }\r\n  return success;\r\n}\r\n\r\nfunction Callback(doc) {\r\n  try {\r\n    var fired_at = new Date();\r\n\r\n    \/\/ Check if further analysis is needed we only trigger on a recurring_event that is active\r\n    if (doc.type !== \"recurring_event\") return;\r\n    \/\/ doc must have 'action', 'dynamic {}', verbose {}, dynamic.state\r\n    if (!doc.action || !doc.dynamic || !doc.verbose || !doc.dynamic.state) return;\r\n    \/\/ process any doc.dynamic.state BUT pending \r\n    if (doc.dynamic.state === \"pending\") return;\r\n\r\n    \/\/ ==================\r\n    \/\/ Check if still active\r\n\r\n    \/\/ We make sure that in KV the 'doc' still exists and that it is still active if not just \r\n    \/\/ return thus skipping the action and not Re-arming the timer. Note `travel-sample` is \r\n    \/\/ aliased to the map 'cron_bkt\r\n\r\n    var mid = doc.type + '::' + doc.id; \/\/ make our KEY\r\n    var curDoc = null;\r\n    try {\r\n      \/\/ read the current version of doc from KV, e.g. curDoc\r\n      curDoc = cron_bkt[mid];\r\n    } catch (e) {} \/\/ needed for pre 6.5, note pure 6.5+ deployment returns null sans exception\r\n\r\n    var reason = null;\r\n    if (!curDoc || curDoc === null) {\r\n      reason = \"cron document is missing\";\r\n    } else\r\n    if (!curDoc.active) {\r\n      reason = \"cron document has active = false\";\r\n    } else\r\n    if (!curDoc.dynamic.state || curDoc.dynamic.state !== doc.dynamic.state) {\r\n      reason = \"cron document wrong dynamic.state expected \" + doc.dynamic.state;\r\n    } else\r\n    if (crc64(doc) !== crc64(curDoc)) {\r\n      reason = \"cron document changed\";\r\n    }\r\n\r\n    if (reason !== null) {\r\n      if (!curDoc || curDoc === null || curDoc.verbose.scheduler &gt;= 1) {\r\n        log('Callback X ' + mid + \" ignore\/stop this timer's schedule because \" + reason);\r\n      }\r\n      if (!curDoc || curDoc === null || curDoc.verbose.scheduler &gt;= 4) {\r\n        log('Callback Y ' + mid + ' timer doc', doc);\r\n        log('Callback Z ' + mid + ' KV curDoc', curDoc);\r\n      }\r\n      return;\r\n    }\r\n\r\n    \/\/ ==================\r\n    \/\/ Verify user routine exists and if so eval it \r\n\r\n    \/\/ Assume curDoc.action contains something like \"doCronActionA\" and we have a function in \r\n    \/\/ this handler like \"doCronActionA(doc)\". Below we use curDoc as the end user should be \r\n    \/\/ able to alter the eval'd JavaScript function.  We will execute two (2) evals.\r\n\r\n    \/\/ First eval check the JavaScript function exists.  The eval occurs in a common \r\n    \/\/ utility function shared with RecurringCallback\r\n    if (!verifyFunctionExistsViaEval(curDoc, mid)) {\r\n      \/\/ curDoc.action did not exist, we have already logged the issue\r\n      return;\r\n    }\r\n\r\n    \/\/ Second eval execute and process the user function we execute the defined function \r\n    \/\/ with an argument of curDoc\r\n    var beg_act = new Date();\r\n    var result = null;\r\n    eval(\"result = \" + curDoc.action + \"(curDoc);\");\r\n    var end_act = new Date();\r\n    var atime_ms = end_act.getTime() - beg_act.getTime();\r\n\r\n    if (curDoc.verbose.scheduler &gt;= 2)\r\n      log('Callback R ' + mid + ' action took ' + toNumericFixed((atime_ms \/ 1000), 3) +\r\n        ' sec., returned ' + result);\r\n\r\n    \/\/ ==================\r\n    \/\/ Calculate next time and mutate the control document for our our helper function\r\n    \/\/ which will create another mutation such that OnUpdate of this function will pick\r\n    \/\/ it up and generate the timer (avoids the MB-38554 issue).\r\n\r\n    var hour = curDoc.hour;\r\n    var min = curDoc.min;\r\n    var date_timer = getNextRecurringDate(hour, min);\r\n\r\n    curDoc.dynamic.prev_delay =\r\n      toNumericFixed(((fired_at.getTime() \/ 1000) - curDoc.dynamic.next_sched), 3);\r\n    curDoc.dynamic.prev_sched = curDoc.dynamic.next_sched;\r\n    curDoc.dynamic.prev_etime = Math.round(fired_at.getTime() \/ 1000);\r\n    curDoc.dynamic.prev_atime = toNumericFixed((atime_ms \/ 1000), 3);\r\n\r\n    curDoc.dynamic.state = \"pending\";\r\n    curDoc.dynamic.next_sched = Math.round(date_timer.getTime() \/ 1000);\r\n\r\n    \/\/ rather then the call a function, to trap and retry if there is a resource issue\r\n    \/\/ cron_bkt[mid] = curDoc;\r\n    if (!tryBucketKvWriteWithLog('Callback F', mid, curDoc)) {\r\n      \/\/ Failed to write curDoc to cron_bkt[key] the error has been logged\r\n      \/\/ and there is nothing more we can do.\r\n      return;\r\n    }\r\n\r\n    if (curDoc.verbose.scheduler &gt;= 1) {\r\n      log('Callback A ' + mid + ' gen mutation #1 to doc to force schedule rearm at ' +\r\n        toLocalISOTime(date_timer));\r\n    }\r\n    if (curDoc.verbose.scheduler &gt;= 2) {\r\n      log('Callback B ' + mid + ' sched ' + curDoc.dynamic.prev_sched +\r\n        ', actual ' + curDoc.dynamic.prev_etime +\r\n        ', delay ' + curDoc.dynamic.prev_delay +\r\n        ', took ' + curDoc.dynamic.prev_atime);\r\n    }\r\n    if (curDoc.verbose.scheduler &gt;= 3) {\r\n      log('Callback C ' + mid + ' curDoc', curDoc);\r\n    }\r\n  } catch (e) {\r\n    var mid = doc.type + '::' + doc.id; \/\/ make our KEY\r\n    log('Callback E ' + mid + ' Error exception:', e);\r\n  }\r\n}<\/pre>\n<\/li>\n<\/ul>\n<ul>\n<li>After pasting, the screen appears as displayed below:<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8711\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_03_code_editor_done.png\" alt=\"Eventing cron update\" width=\"1020\" height=\"771\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done.png 2040w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-300x227.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-1024x774.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-768x581.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-1536x1161.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-20x15.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_03_code_editor_done-1320x998.png 1320w\" sizes=\"auto, (max-width: 1020px) 100vw, 1020px\" \/><\/li>\n<li>Click <strong>Save<\/strong>.<\/li>\n<li>To return to the Eventing screen, click the &#8216;<strong>&lt; back to Eventing<\/strong>&#8216; link (below the editor) or click the <strong>Eventing<\/strong><\/li>\n<\/ul>\n<h4>Manually create &#8220;cron_impl_2func_651_help&#8221;<\/h4>\n<p>To add the second Eventing function from the <strong>Couchbase Web Console &gt; Eventing<\/strong> page, click <strong>ADD FUNCTION<\/strong>, to add a new Function. The <strong>ADD FUNCTION<\/strong> dialog appears.<\/p>\n<p>In the <strong>ADD FUNCTION<\/strong> dialog, for individual Function elements provide the below information:<\/p>\n<ul>\n<li>For the <strong>Source Bucket<\/strong> drop-down, set to <strong>crondata<\/strong>.<\/li>\n<li>For the <strong>Metadata Bucket<\/strong> drop-down, set to <strong>metadata<\/strong>.<\/li>\n<li>Make <strong>cron_impl_2func_651_help<\/strong>\u00a0is the name of the Function you are creating in the <strong>Function Name<\/strong> text-box.<\/li>\n<li>[Optional Step] Enter text <b>A cron like scheduler helper part 1<\/b>, in the <strong>Description<\/strong> text-box.<\/li>\n<li>For the <strong>Settings<\/strong> option, use the default values.<\/li>\n<li>For the <strong>Bindings<\/strong> option, create one binding:<\/li>\n<li>For the binding, the &#8220;bucket alias&#8221;, specifies <strong>cron_bkt<\/strong> as the &#8220;alias name&#8221; of the bucket, and select<br \/>\n<strong>crondata<\/strong> as the associated bucket, and the mode should be &#8220;read and write&#8221;.<\/li>\n<li>After configuring your settings your dialog should look like this:<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8706\" style=\"margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_01_add_function_help.png\" alt=\"Eventing cron update\" width=\"643\" height=\"640\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help.png 1286w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-300x299.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-1024x1019.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-150x150.png 150w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-768x764.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-65x65.png 65w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-50x50.png 50w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_01_add_function_help-20x20.png 20w\" sizes=\"auto, (max-width: 643px) 100vw, 643px\" \/><\/li>\n<li>After providing all the required information in the <strong>ADD FUNCTION<\/strong> dialog, click <strong>Next: Add Code<\/strong>. The <strong>cron_impl_2func_651_help<\/strong> dialog appears. The <strong>cron_impl_2func_651_help<\/strong>\u00a0dialog initially contains a placeholder code block. You will substitute your actual <strong>cron_impl_2func_651_help<\/strong>\u00a0code in this block.<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8710\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_02_code_editor_help.png\" alt=\"Eventing cron update\" width=\"800\" height=\"186\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor_help.png 2042w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor_help-300x70.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor_help-1024x239.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor_help-768x179.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor_help-1536x358.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor_help-20x5.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_02_code_editor_help-1320x308.png 1320w\" sizes=\"auto, (max-width: 800px) 100vw, 800px\" \/><\/li>\n<li>Copy the following Eventing Function JavaScript source (187 lines) and paste it in the placeholder code block of <strong>cron_impl_2func_651_help<\/strong>\n<pre class=\"height-set:true height:800 toolbar-overlay:false nums:false lang:js decode:true\">\/*\r\nFunction \"cron_impl_2func_651_help\" also requires \"cron_impl_2func_651\"\r\n\r\nTest Doc:\r\n   {\r\n        \"type\":\"recurring_event\",   \/\/ The KEY will be &lt;&lt;type&gt;&gt;::&lt;&lt;id&gt;&gt;\r\n        \"id\":1,                     \/\/\r\n        \"hour\":14,                  \/\/ The hour of the day 0-23, *, *2X, *4X to trigger\r\n        \"min\":54,                   \/\/ The minute in the hour 0-59, *, *2X, *4X to trigger\r\n        \"action\":\"doCronActionA\",   \/\/ What function to run on the trigger\r\n        \"active\":false,             \/\/ Flag to arm or disable this schedule\r\n        \"verbose\" : {\r\n          \"user_func\":2,            \/\/ Logging level for the action logic : 0=none, etc. etc.\r\n          \"scheduler\":3             \/\/ Logging level for the cron logic   : 0=none, etc. etc.\r\n        },\r\n        \"dynamic\" : {\r\n          \"state\":\"arm\",            \/\/ States \"arm\"|\"rearm\"|\"pending\" if any value but \"pending\" start a schedule\r\n          \"next_sched\": 0,          \/\/ Number of seconds since epoch to next desired schedule\r\n          \"prev_sched\": 0,          \/\/ Number of seconds since epoch for previous schedule\r\n          \"prev_etime\": 0,          \/\/ Number of seconds since epoch for previous schedule actual exec time\r\n          \"prev_delay\": 0,          \/\/ Number of seconds that the timer was delayed from the schedule\r\n          \"prev_atime\": 0           \/\/ Number of seconds taken by the user 'action'\r\n        }\r\n    }\r\n    \r\nNote, you can omit verbose{} and dynamic{} as they will be autocreated by the main Eventing \r\nFunction \"cron_impl_2func_651\". If verbose{} is missing the logging levels will default to \r\nverbose\" : {  \"user_func\":1, \"scheduler\":1 }\r\n*\/\r\n\r\nfunction tryBucketKvWriteWithLog(tag, key, doc) {\r\n  var success = false;\r\n  var tries = 0;\r\n  while (tries &lt; 10) {\r\n    tries++;\r\n    try {\r\n      \/\/ critical that the below succeeds, because if it doesn't the cron cycle will break\r\n      cron_bkt[key] = doc;\r\n      success = true;\r\n      break;\r\n    } catch (e) {\r\n      log(tag + ' ' + key + ' WARN failed to update KV tries ' + tries, e);\r\n    }\r\n  }\r\n  if (!success) {\r\n    log(tag + ' ' + +key + ' FATAL could not update KV cron cycle, tried ' + tries + ', stoping ' + curDoc.action);\r\n  }\r\n  return success;\r\n}\r\n\r\nfunction OnUpdate(doc, meta) {\r\n  \/\/ fix for 6.5.X growing bucket ops\r\n  if (meta.id.startsWith(\"fix_timer_scan_issue:\")) upsertOneDocPerBucket(doc, meta);\r\n  \r\n  try {\r\n    \/\/ Check that doc has desired values\r\n    if (!doc.type || doc.type !== \"recurring_event\" || !doc.active || doc.active != true) return;\r\n    \/\/ doc must have 'action', 'dynamic {}', verbose {}, dynamic.state\r\n    if (!doc.action || !doc.dynamic || !doc.verbose || !doc.dynamic.state) return;\r\n    \/\/ Only process state pending this will only exist for a 'breif' time\r\n    if (doc.dynamic.state !== \"pending\") return;\r\n\r\n    var mid = doc.type + '::' + doc.id; \/\/ make our KEY\r\n    var newdoc = null;\r\n    try {\r\n      \/\/ read the current version of doc from KV, e.g. curDoc\r\n      newdoc = cron_bkt[mid];\r\n    } catch (e) {} \/\/ needed for pre 6.5, note pure 6.5+ deployment returns null sans exception\r\n    \r\n    var reason = null;\r\n    if (!newdoc || newdoc == null) {\r\n      reason = \"cron document is missing\";\r\n    } else\r\n    if (!newdoc.active) {\r\n      reason = \"cron document has active = false\";\r\n    } else\r\n    if (!newdoc.dynamic.state || newdoc.dynamic.state !== doc.dynamic.state) {\r\n      reason = \"cron document wrong dynamic.state expected \" + doc.dynamic.state;\r\n    } else\r\n    if (crc64(doc) !== crc64(newdoc)) {\r\n      reason = \"cron document changed\";\r\n    }\r\n    if (reason != null) {\r\n      if (!newdoc || newdoc == null || newdoc.verbose.scheduler &gt;= 1) {\r\n        log('OnUpdate help: X stopping schedule because ' + reason + ',', newdoc)\r\n        return;\r\n      }\r\n    }\r\n\r\n    newdoc.dynamic.state = \"rearm\";\r\n    \/\/ cron_bkt[mid] = newdoc;\r\n    if (!tryBucketKvWriteWithLog('OnUpdate help: F', mid, newdoc)) {\r\n      \/\/ Failed to write newdoc to cron_bkt[key] the error has been logged\r\n      \/\/ and there is nothing more we can do.\r\n      return;\r\n    }\r\n\r\n    if (newdoc.verbose.scheduler &gt;= 1) {\r\n      log('OnUpdate help: A ' + mid + ' mutation #2 to doc to force schedule rearm');\r\n    }\r\n    if (newdoc.verbose.scheduler &gt;= 3) {\r\n      log('OnUpdate help: B ' + mid + ',', newdoc);\r\n    }\r\n  } catch (e) {\r\n    log('OnUpdate help: E ' + meta.id + ', Error exception:', e);\r\n  }\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction upsertOneDocPerBucket(doc, meta) {\r\n\r\n    var crcTable = makeCRC32Table();\r\n\r\n    \/\/ make one doc per bucket\r\n    var isVerbose = 0;\r\n    var isMacOS = false; \/\/ would be nice if this was an exposed constant in Eventing\r\n    var numvbs = 1024;   \/\/ default is linux\/PC\r\n    if (isMacOS) {\r\n        numvbs = 64;\r\n    }\r\n\r\n    var beg = (new Date).getTime();\r\n    var result = getKeysToCoverAllPartitions(crcTable, \"_tmp_vbs:\", numvbs);\r\n\r\n    for (var vb=0; vb&lt;numvbs; vb++) {\r\n        \/\/ brute force to fit a key prefix into a vBucket\r\n         var tst = result[vb];\r\n        if (isVerbose &gt; 1  || (isVerbose == 1) &amp;&amp; (vb &lt; 3 || vb &gt; numvbs -4)) {\r\n            log(\"KEY: \" + tst);\r\n        } else {\r\n            if (vb == 5) console.log(\"\\t*\\n\\t*\\n\\t*\");\r\n        }\r\n        \/\/ update the items to trigger a mutation for our PRIMARY fucntion\r\n        cron_bkt[tst] = { \"type\": \"_tmp_vbs\", \"vb\": vb, \"ts_millis\": beg, \"key\": tst };\r\n    }\r\n    var end = (new Date).getTime();\r\n    log(\"seeding one doc to each vBucket in primary_bucket alias (took \" + (end - beg) + \" mililis)\");\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction showHex(n) {\r\n    return n.toString(16);\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction makeCRC32Table() {\r\n    var crcTable = [];\r\n    var c;\r\n    for(var n =0; n &lt; 256; n++){\r\n        c = n;\r\n        for(var k =0; k &lt; 8; k++){\r\n            c = ((c&amp;1) ? (0xEDB88320 ^ (c &gt;&gt;&gt; 1)) : (c &gt;&gt;&gt; 1));\r\n        }\r\n        crcTable[n] = c;\r\n    }\r\n    return crcTable;\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction crc32(crcTable,str) {\r\n    var crc = 0 ^ (-1);\r\n    for (var i = 0; i &lt; str.length; i++ ) {\r\n        crc = (crc &gt;&gt;&gt; 8) ^ crcTable[(crc ^ str.charCodeAt(i)) &amp; 0xFF];\r\n    }\r\n    return (crc ^ (-1)) &gt;&gt;&gt; 0;\r\n}\r\n\r\n\/\/ FIXUP: ADDIN FUNCTON\r\n\/\/ fix for 6.5.X growing bucket ops\r\nfunction getKeysToCoverAllPartitions(crcTable,keyPrefix,partitionCount) {\r\n    var result = [];\r\n    var remaining = partitionCount;\r\n    for (var i = 0; remaining &gt; 0; i++) {\r\n      var key = keyPrefix + i;\r\n      var rv = (crc32(crcTable,key) &gt;&gt; 16) &amp; 0x7fff;\r\n      var actualPartition = rv &amp; partitionCount - 1;\r\n      if (!result[actualPartition] || result[actualPartition] === undefined) {\r\n        result[actualPartition] = key;\r\n        remaining--;\r\n      }\r\n    }\r\n    return result;\r\n}<\/pre>\n<\/li>\n<\/ul>\n<ul>\n<li>After pasting, the screen appears as displayed below:<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8729\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/04\/ev_cr_1u_02_code_editor_done_help.png\" alt=\"Eventing cron update\" width=\"1020\" height=\"770\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help.png 2044w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-300x227.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-1024x774.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-768x580.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-1536x1160.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-20x15.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1u_02_code_editor_done_help-1320x997.png 1320w\" sizes=\"auto, (max-width: 1020px) 100vw, 1020px\" \/><\/li>\n<\/ul>\n<ul>\n<li>Click <strong>Save<\/strong>.<\/li>\n<li>To return to the Eventing screen, click the &#8216;<strong>&lt; back to Eventing<\/strong>&#8216; link (below the editor) or click the <strong>Eventing<\/strong><\/li>\n<\/ul>\n<h3>Deploy the two functions<\/h3>\n<p>We are now ready to start the Eventing functions. From the <strong>Couchbase Web Console\u00a0&gt;\u00a0Eventing<\/strong>\u00a0screen:<\/p>\n<ul>\n<li>Click on the Function name <strong>cron_impl_2func_651_help<\/strong>\u00a0to expand and expose the Function controls.<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8713\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_04_functon_ctl_help.png\" alt=\"Eventing cron update\" width=\"1020\" height=\"280\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl_help.png 2042w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl_help-300x82.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl_help-1024x281.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl_help-768x211.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl_help-1536x421.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl_help-20x5.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl_help-1320x362.png 1320w\" sizes=\"auto, (max-width: 1020px) 100vw, 1020px\" \/><\/li>\n<\/ul>\n<ul>\n<li>Click <strong>Deploy<\/strong>.<\/li>\n<li>In the <strong>Confirm Deploy Function<\/strong> dialog, select \u201c<strong>From now\u201d<\/strong> from the Feed boundary option.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8535\" style=\"margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/04\/ev_cr_1_05_functon_deploy.png\" alt=\"\" width=\"382\" height=\"263\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1_05_functon_deploy.png 764w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1_05_functon_deploy-300x207.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1_05_functon_deploy-20x14.png 20w\" sizes=\"auto, (max-width: 382px) 100vw, 382px\" \/><\/li>\n<\/ul>\n<p>Let&#8217;s start the other Eventing Function. From the <strong>Couchbase Web Console\u00a0&gt;\u00a0Eventing<\/strong>\u00a0screen:<\/p>\n<ul>\n<li>Click on the Function name <strong>cron_impl_2func_651<\/strong>\u00a0to expand and expose the Function controls.<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8712\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_04_functon_ctl.png\" alt=\"Eventing cron update\" width=\"1020\" height=\"365\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl.png 2042w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl-300x107.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl-1024x366.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl-768x275.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl-1536x549.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl-20x7.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_04_functon_ctl-1320x472.png 1320w\" sizes=\"auto, (max-width: 1020px) 100vw, 1020px\" \/><\/li>\n<li>Click <strong>Deploy<\/strong>.<\/li>\n<li>In the <strong>Confirm Deploy Function<\/strong> dialog, select \u201c<strong>From now\u201d<\/strong> from the Feed boundary option.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8535\" style=\"margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/04\/ev_cr_1_05_functon_deploy.png\" alt=\"\" width=\"382\" height=\"263\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1_05_functon_deploy.png 764w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1_05_functon_deploy-300x207.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/ev_cr_1_05_functon_deploy-20x14.png 20w\" sizes=\"auto, (max-width: 382px) 100vw, 382px\" \/><\/li>\n<\/ul>\n<h3>Setup a <em>cron<\/em> task to run four (4) times per minute<\/h3>\n<p>At this point our Eventing function is waiting for a mutation specifically any document of type=\u201drecurring_event\u201d that has a field active=true.<\/p>\n<p>From the\u00a0<strong>Couchbase Web Console\u00a0&gt;\u00a0Query<\/strong>\u00a0page, we will use N1QL to create a new scheduled task in the \u2018travel-sample\u2019 bucket:<\/p>\n<ul>\n<li>Cut-n-paste the following N1QL statement into the <strong>Query Editor<\/strong>\n<pre class=\"toolbar-overlay:false nums:false lang:default decode:true\">INSERT INTO `crondata` (KEY,VALUE) VALUES (\r\n  \"recurring_event::1\", \r\n  {\r\n    \"type\": \"recurring_event\",\r\n    \"id\":1,\r\n    \"hour\":\"*\",\r\n    \"min\":\"0\",\r\n    \"action\": \"doCronActionA\",\r\n    \"verbose\": {\r\n      \"user_func\": 2,\r\n      \"scheduler\": 3\r\n    },\r\n    \"active\": false\r\n  }\r\n);<\/pre>\n<\/li>\n<\/ul>\n<ul>\n<li>Click <strong>Execute<br \/>\n<\/strong><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8715\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_06_n1ql_add_sched.png\" alt=\"Eventing cron update\" width=\"817\" height=\"582\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_06_n1ql_add_sched.png 1634w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_06_n1ql_add_sched-300x214.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_06_n1ql_add_sched-1024x729.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_06_n1ql_add_sched-768x547.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_06_n1ql_add_sched-1536x1094.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_06_n1ql_add_sched-20x14.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_06_n1ql_add_sched-1320x940.png 1320w\" sizes=\"auto, (max-width: 817px) 100vw, 817px\" \/><\/li>\n<\/ul>\n<h3>Activate our first <em>cron<\/em> task<\/h3>\n<p>The control document we previously made was not activated because we specified &#8220;active&#8221;:false, furthermore the schedule above will only run once an hour, but we want to test things and see them work in the near future.<\/p>\n<p>First we need an index to be able to manipulate our control documents in N1QL this only needs to be done once<\/p>\n<ul>\n<li>Cut-n-paste the following N1QL statement into the <strong>Query Editor<\/strong>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">CREATE primary INDEX on `crondata` ;<\/pre>\n<\/li>\n<\/ul>\n<p>Cut-n-paste the following N1QL statement into the <strong>Query Editor<\/strong>Now we will activate the task, but we will adjust the repeating schedule to every 15 seconds to see exactly how the system is behaving.\u00a0 We do this by modifying the control document with KEY recurring_event::1<\/p>\n<ul>\n<li>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">UPDATE `crondata`\r\nSET active=TRUE, hour=\"*4X\", min=\"*4X\"\r\nWHERE type=\"recurring_event\" AND id=1 ;<\/pre>\n<\/li>\n<\/ul>\n<ul>\n<li>Click <strong>Execute<br \/>\n<\/strong><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8716\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_07_n1ql_activate.png\" alt=\"Eventing cron update\" width=\"790\" height=\"420\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_07_n1ql_activate.png 1580w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_07_n1ql_activate-300x159.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_07_n1ql_activate-1024x544.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_07_n1ql_activate-768x408.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_07_n1ql_activate-1536x817.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_07_n1ql_activate-818x434.png 818w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_07_n1ql_activate-20x11.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_07_n1ql_activate-1320x702.png 1320w\" sizes=\"auto, (max-width: 790px) 100vw, 790px\" \/><\/li>\n<\/ul>\n<p>We used the non-standard syntax of =&#8221;*4X&#8221; to schedule a recurring item four time a minute we can see our work function <strong>doCronActionA<\/strong> executing and also the housekeeping logic to schedule the function via log statements\u00a0 because we set verbose=3.<\/p>\n<p>The scheduler is now running four times a minute. You can see the activity in the statistics and in the Application log files for the Eventing Functions <strong>cron_impl_2func_651 <\/strong>and <strong>cron_impl_2func_651_help<\/strong>.<\/p>\n<ul>\n<li>Access the\u00a0<strong>Couchbase Web Console\u00a0&gt;\u00a0Dashboard<\/strong> you will see activity burst every 15 seconds:<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8717\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_08_dashboard_activity.png\" alt=\"Eventing cron update\" width=\"1020\" height=\"532\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_08_dashboard_activity.png 2040w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_08_dashboard_activity-300x156.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_08_dashboard_activity-1024x534.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_08_dashboard_activity-768x401.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_08_dashboard_activity-1536x801.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_08_dashboard_activity-20x10.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_08_dashboard_activity-1320x688.png 1320w\" sizes=\"auto, (max-width: 1020px) 100vw, 1020px\" \/><\/li>\n<li>Access the <strong>Couchbase Web Console &gt; Eventing<\/strong> and click the <strong>Log<\/strong> link of the deployed <strong>cron_impl_2func_651<\/strong>\u00a0Eventing function. This Function Log dialog lists log statements in reverse order (newest items first). The initial output should be similar to the following:\n<div style=\"border: 1px solid black;max-width: 1000px;padding: 4px;font-size: 60%;margin-top: 12px;margin-bottom: 12px\">2020-05-20T18:34:33.340-07:00 [INFO] &#8220;OnUpdate B recurring_event::1 recurring timer was created, timer_id 570927555481258455388&#8221;<br \/>\n2020-05-20T18:34:33.340-07:00 [INFO] &#8220;OnUpdate A recurring_event::1 rcv mutation (initial or rearm) schedule timer at 2020-05-20T18:34:45.000&#8221;<br \/>\n2020-05-20T18:34:33.233-07:00 [INFO] &#8220;doCronActionA upsert to KV with KEY cron_cache::airlines_by_country cachedoc &#8221; {&#8220;type&#8221;:&#8221;cron_cache&#8221;,&#8221;id&#8221;:&#8221;airlines_by_country&#8221;,&#8221;date&#8221;:&#8221;2020-05-21T01:34:33.232Z&#8221;,&#8221;data&#8221;:{&#8220;United States&#8221;:127,&#8221;United Kingdom&#8221;:39,&#8221;France&#8221;:21}}<br \/>\n2020-05-20T18:34:33.233-07:00 [INFO] &#8220;Callback R recurring_event::1 action took 0.013 sec., returned true&#8221;<br \/>\n2020-05-20T18:34:33.233-07:00 [INFO] &#8220;Callback C recurring_event::1 curDoc&#8221; {&#8220;action&#8221;:&#8221;doCronActionA&#8221;,&#8221;active&#8221;:true,&#8221;hour&#8221;:&#8221;*4X&#8221;,&#8221;id&#8221;:1,&#8221;min&#8221;:&#8221;*4X&#8221;,&#8221;type&#8221;:&#8221;recurring_event&#8221;,&#8221;verbose&#8221;:{&#8220;scheduler&#8221;:3,&#8221;user_func&#8221;:2},&#8221;dynamic&#8221;:{&#8220;state&#8221;:&#8221;pending&#8221;,&#8221;next_sched&#8221;:1590024885,&#8221;prev_sched&#8221;:1590024870,&#8221;prev_etime&#8221;:1590024873,&#8221;prev_delay&#8221;:3.218,&#8221;prev_atime&#8221;:0.013}}<br \/>\n2020-05-20T18:34:33.233-07:00 [INFO] &#8220;Callback B recurring_event::1 sched 1590024870, actual 1590024873, delay 3.218, took 0.013&#8221;<br \/>\n2020-05-20T18:34:33.233-07:00 [INFO] &#8220;Callback A recurring_event::1 gen mutation #1 to doc to force schedule rearm at 2020-05-20T18:34:45.000&#8221;<br \/>\n2020-05-20T18:34:33.232-07:00 [INFO] &#8220;doCronActionA N1QL idx 2, country France cnt 21&#8221;<br \/>\n2020-05-20T18:34:33.232-07:00 [INFO] &#8220;doCronActionA N1QL idx 1, country United Kingdom cnt 39&#8221;<br \/>\n2020-05-20T18:34:33.232-07:00 [INFO] &#8220;doCronActionA N1QL idx 0, country United States cnt 127&#8221;<br \/>\n2020-05-20T18:34:33.220-07:00 [INFO] &#8220;doCronActionA user action controlled by recurring_event::1&#8221;<br \/>\n2020-05-20T18:34:19.340-07:00 [INFO] &#8220;OnUpdate B recurring_event::1 recurring timer was created, timer_id 381384185845112994486&#8221;<br \/>\n2020-05-20T18:34:19.340-07:00 [INFO] &#8220;OnUpdate A recurring_event::1 rcv mutation (initial or rearm) schedule timer at 2020-05-20T18:34:30.000&#8221;<\/div>\n<p>The oldest line at the bottom is the mutation that started the schedule (or re-armed the schedule) e.g. OnUpdate message, and we see the first two full executions of the business logic we coded up in <strong>doCronActionA<\/strong><\/li>\n<li>There will also be some messaging related to the &#8220;<em>fix for 6.5.X growing bucket ops<\/em>&#8221; but that will be logged to <strong>cron_impl_2func_651_help<\/strong> you will see messages like the following about every 30 seconds:\n<div style=\"border: 1px solid black;max-width: 1000px;padding: 4px;font-size: 60%;margin-top: 12px;margin-bottom: 12px\">2020-05-20T18:34::49.185-07:00 [INFO] &#8220;seeding one doc to each vBucket in primary_bucket alias (took 221 mililis)&#8221;<\/div>\n<\/li>\n<\/ul>\n<p>Let\u2019s adjust both the frequency and the verbosity of this particular task.\u00a0 We will use the standard <em>cron<\/em> syntax of \u2018*\u2019 for both <em>hour<\/em> and <em>min<\/em> to get a recurring schedule of once per minute 4X slower than the currently running frequency.\u00a0\u00a0 In addition, we will lower the verbosity level of the scheduler logic to zero and the user function to 1 so we only see one message per invocation.<\/p>\n<p>From the\u00a0<strong>Couchbase Web Console\u00a0&gt;\u00a0Query<\/strong>\u00a0page, we will use N1QL to create a new scheduled task in the \u2018travel-sample\u2019 bucket:<\/p>\n<ul>\n<li>Cut-n-paste the following N1QL statement into the <strong>Query Editor<\/strong>\n<pre class=\"toolbar-overlay:false nums:false lang:default decode:true\">UPDATE `crondata` \r\nSET verbose.scheduler = 0, verbose.user_func = 1, \r\n    active=true, hour=\"*\", min=\"*\" \r\nWHERE type=\"recurring_event\" AND id=1 ;<\/pre>\n<\/li>\n<\/ul>\n<ul>\n<li>Click <strong>Execute<\/strong><\/li>\n<\/ul>\n<p>After a 2- or 3-minutes access the <strong>Couchbase Web Console &gt; Eventing<\/strong> and click the <strong>Log<\/strong> link of the deployed <strong>cron_impl_2func_651<\/strong>\u00a0Eventing function.<\/p>\n<ul>\n<li>The recurring schedule is now one minute and much less verbose. Only one log message or line is emitted for each function execution (once again in reverse time order).\n<div style=\"border: 1px solid black;max-width: 1000px;padding: 4px;font-size: 60%;margin-top: 12px;margin-bottom: 12px\">2020-05-20T18:43:04.231-07:00 [INFO] &#8220;doCronActionA user action controlled by recurring_event::1&#8221;<br \/>\n2020-05-20T18:42:08.233-07:00 [INFO] &#8220;doCronActionA user action controlled by recurring_event::1&#8221;<\/div>\n<p>Only one log message or line is emitted for each scheduled user function execution, i.e. <strong>doCronActionA<\/strong> (once again in reverse time order).<\/li>\n<\/ul>\n<h3>Let\u2019s look at the work being performed<\/h3>\n<p>This code provides a practical framework for executing any JavaScript function on a recurring schedule and our function <strong>doCronActionA<\/strong> is upserting (insert or update) a calculated cache KV document once per minute.<\/p>\n<p>To check the results of the deployed Eventing Function, access the <strong>Couchbase Web Console &gt; Buckets<\/strong> page and click the Documents link of the <strong>travel-sample<\/strong> bucket.<\/p>\n<ul>\n<li>In the text box \u201c<strong>N1QL WHERE<\/strong>\u201d past the following text.\n<pre class=\"toolbar-overlay:false nums:false lang:default decode:true\">type=\"cron_cache\"<\/pre>\n<\/li>\n<\/ul>\n<ul>\n<li>Click <strong>Retrieve Docs<br \/>\n<\/strong>You should now see one document a cache document cron_cache::airlines_by_country which is being updated once per minute by the scheduled function <strong>doCronActionA<\/strong>.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8719\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_10_look_at_kv_cache_doc.png\" alt=\"Eventing cron update\" width=\"1020\" height=\"426\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_10_look_at_kv_cache_doc.png 2040w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_10_look_at_kv_cache_doc-300x125.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_10_look_at_kv_cache_doc-1024x428.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_10_look_at_kv_cache_doc-768x321.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_10_look_at_kv_cache_doc-1536x642.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_10_look_at_kv_cache_doc-20x8.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_10_look_at_kv_cache_doc-1320x551.png 1320w\" sizes=\"auto, (max-width: 1020px) 100vw, 1020px\" \/><\/li>\n<li>Click on the id \u201c<strong>cron_cache::airlines_by_country<\/strong>\u201d you will see your cached document that is being updated by the business logic of <strong>doCronActionA<\/strong>.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8720\" style=\"margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_11_look_at_kv_cache_doc.png\" alt=\"Eventing cron update\" width=\"644\" height=\"377\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_11_look_at_kv_cache_doc.png 1288w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_11_look_at_kv_cache_doc-300x176.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_11_look_at_kv_cache_doc-1024x599.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_11_look_at_kv_cache_doc-768x450.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_11_look_at_kv_cache_doc-20x12.png 20w\" sizes=\"auto, (max-width: 644px) 100vw, 644px\" \/><br \/>\nWhat a minute and Edit the document again and you will see the \u201cdate\u201d field updating, of course the source data is \u201cstatic\u201d right now so the counts will remain the same.<\/li>\n<\/ul>\n<h3>Let\u2019s look at the control document<\/h3>\n<p>This code provides a framework for keeping a bit of statistics on each running schedule.<\/p>\n<p>To check the statistics of the deployed Eventing Function, access the <strong>Couchbase Web Console &gt; Buckets<\/strong> page and click the Documents link of the <strong>crondata<\/strong> bucket.<\/p>\n<ul>\n<li>In the text box \u201c<strong>N1QL WHERE<\/strong>\u201d past the following text.\n<pre class=\"toolbar-overlay:false nums:false lang:default decode:true\">type=\"recuring_event\"<\/pre>\n<\/li>\n<\/ul>\n<ul>\n<li>Click <strong>Retrieve Docs<br \/>\n<\/strong>You should now see one document your control document recurring_event::1 that is driving the scheduled function <strong>doCronActionA<\/strong>.<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8721\" style=\"border: #000 1px solid;margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_12_look_at_control_doc.png\" alt=\"Eventing cron update\" width=\"1020\" height=\"426\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_12_look_at_control_doc.png 2040w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_12_look_at_control_doc-300x125.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_12_look_at_control_doc-1024x428.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_12_look_at_control_doc-768x321.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_12_look_at_control_doc-1536x642.png 1536w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_12_look_at_control_doc-20x8.png 20w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_12_look_at_control_doc-1320x551.png 1320w\" sizes=\"auto, (max-width: 1020px) 100vw, 1020px\" \/><\/li>\n<li>Click on the id \u201c<strong>recurring_event::1<\/strong>\u201d you will see your control document that is being updated by the scheduler logic with some statistics in the JSON object dynamic.<br \/>\n<img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8722\" style=\"margin-top: 12px\" src=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2020\/05\/ev_cr_1u_13_look_at_control_doc.png\" alt=\"Eventing cron update\" width=\"646\" height=\"473\" srcset=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_13_look_at_control_doc.png 1292w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_13_look_at_control_doc-300x220.png 300w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_13_look_at_control_doc-1024x750.png 1024w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_13_look_at_control_doc-768x562.png 768w, https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/05\/ev_cr_1u_13_look_at_control_doc-20x15.png 20w\" sizes=\"auto, (max-width: 646px) 100vw, 646px\" \/><br \/>\nThe &#8220;dynamic&#8221; portion of the document which is automatically added keeps some debug statistics:<\/li>\n<li>prev_sched: is the previous UNIX timestamp of the schedule that last ran<\/li>\n<li>prev_etime: is the actual UNIX timestamp when the schedule last ran<\/li>\n<li>prev_delay: is delay from the prev_sched to the prev_etime<\/li>\n<li>prev_atime: is the time it took to run this action, i.e. doCronActionA to execute.<\/li>\n<li>next_sched: is the next scheduled execution for this action<\/li>\n<\/ul>\n<p>These statistics kept in the JSON dynamic sub-object for each schedule are useful for determining that your scheduling system is healthy and your action being executed is finishing in a timely fashion.<\/p>\n<h3>Verify that the cache updates on data changes<\/h3>\n<p>The entire purpose of <strong>doCronActionA<\/strong> is to perform work at or near a scheduled time and update a cache document with the KEY \u201ccron_cache::airlines_by_country\u201d.<\/p>\n<p>Let\u2019s do some validations in the Query Monitor that our cache is being updated by 1) looking at the cache document, 2) delete some airlines from the travel-sample document set, and 3) \u00a0verifying the cache document gets updated by the function <strong>doCronActionA<\/strong>.<\/p>\n<p>From the\u00a0<strong>Couchbase Web Console\u00a0&gt;\u00a0Query<\/strong>\u00a0page, we will use N1QL to view and manipulate data in the \u2018travel-sample\u2019 bucket:<\/p>\n<ul>\n<li>Cut-n-paste the following N1QL statement into the<\/li>\n<li><strong>Query Editor<\/strong>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">SELECT data FROM `travel-sample`\r\nWHERE `type` = 'cron_cache' AND id== 'airlines_by_country';<\/pre>\n<\/li>\n<li>Click <strong>Execute<br \/>\n<\/strong>In the JSON view of the Query Workbench you should see:<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">[\r\n  {\r\n    \"data\": {\r\n      \"France\": 21,\r\n      \"United Kingdom\": 39,\r\n      \"United States\": 127\r\n    }\r\n  }\r\n]<\/pre>\n<\/li>\n<li>Cut-n-paste the following N1QL statement into the <strong>Query Editor<\/strong>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">DELETE FROM `travel-sample`\r\nWHERE `type` = 'airline' AND callsign LIKE 'U%'<\/pre>\n<\/li>\n<li>Click <strong>Execute<br \/>\n<\/strong>In the JSON view of the Query Workbench you should see (we just deleted some data)<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">{\r\n  \"results\": []\r\n}<\/pre>\n<\/li>\n<li>Wait a bit over a minute<\/li>\n<li>Cut-n-paste the following N1QL statement into the <strong>Query Editor<\/strong>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">SELECT data FROM `travel-sample`\r\nWHERE `type` = 'cron_cache' AND id== 'airlines_by_country';<\/pre>\n<\/li>\n<li><strong><span style=\"font-weight: 400\">Click <\/span>Execute<br \/>\n<\/strong>In the JSON view of the Query Workbench you should see that four (4) airlines are no longer present<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">[\r\n  {\r\n    \"data\": {\r\n      \"France\": 21,\r\n      \"United Kingdom\": 39,\r\n      \"United States\": 123\r\n    }\r\n  }\r\n]<\/pre>\n<\/li>\n<\/ul>\n<h3>Startup a second scheduled task<\/h3>\n<p>This code provides a practical framework for executing 1 to N JavaScript functions on recurring schedules.<\/p>\n<p>We will let the function <strong>doCronActionA <\/strong>continue to run on a one-minute schedule but now we will enable <strong>doCronActionB <\/strong>on a 30 second schedule (two times per minute).\u00a0\u00a0 This function is an empty shell and it will only log that it was invoked.<\/p>\n<p>From the\u00a0<strong>Couchbase Web Console\u00a0&gt;\u00a0Query<\/strong> page, we will use N1QL to view and manipulate data in the \u2018<strong>crondata<\/strong>\u2019 bucket:<\/p>\n<ul>\n<li>Cut-n-paste the following N1QL statement into the <strong>Query Editor<br \/>\n<\/strong><\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">INSERT INTO `crondata` (KEY,VALUE) VALUES (\r\n  \"recurring_event::2\", \r\n  {\r\n    \"type\":\"recurring_event\",\r\n    \"id\":2,\r\n    \"hour\":\"*2X\",\r\n    \"min\":\"*2X\",\r\n    \"action\":\"doCronActionB\",\r\n    \"verbose\": {\r\n      \"user_func\": 1,\r\n      \"scheduler\": 0\r\n    },\r\n    \"active\": true\r\n  }\r\n);<\/pre>\n<\/li>\n<li>Click <strong>Execute<\/strong><\/li>\n<\/ul>\n<p>At this point you are executing two (2) different tasks each running on two (2) different schedules to verify wait two to three minutes and inspect the log files again<\/p>\n<ul>\n<li>Access the <strong>Couchbase Web Console &gt; Eventing<\/strong> and click the <strong>Log<\/strong> link of the deployed <strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651<\/span><\/strong>\u00a0Eventing function.<br \/>\nOnly one log message or line is emitted for each function execution (once again in reverse time order).\u00a0 We see that <strong>doCronActionA<\/strong> fires once a minute while<strong> doCronActionB <\/strong>fires twice as much e.g. once every 30 seconds.<\/p>\n<div style=\"border: 1px solid black;max-width: 1000px;padding: 4px;font-size: 60%;margin-top: 12px;margin-bottom: 12px\">2020-05-20T19:16:05.259-07:00 [INFO] &#8220;doCronActionA user action controlled by recurring_event::1&#8221;<br \/>\n2020-05-20T19:16:05.255-07:00 [INFO] &#8220;doCronActionB user action controlled by recurring_event::2&#8221;<br \/>\n2020-05-20T19:15:37.253-07:00 [INFO] &#8220;doCronActionB user action controlled by recurring_event::2&#8221;<br \/>\n2020-05-20T19:15:09.250-07:00 [INFO] &#8220;doCronActionA user action controlled by recurring_event::1&#8221;<br \/>\n2020-05-20T19:15:09.249-07:00 [INFO] &#8220;doCronActionB user action controlled by recurring_event::2&#8221;<br \/>\n2020-05-20T19:14:34.255-07:00 [INFO] &#8220;doCronActionB user action controlled by recurring_event::2&#8221;<\/div>\n<\/li>\n<\/ul>\n<h3>[OPTIONAL] Pause \/ Edit JavaScript \/ Resume<\/h3>\n<p>We are essentially done with this installment, feel free to experiment and modifying things and experiment for example:<\/p>\n<p>From the\u00a0<strong>Couchbase Web Console\u00a0&gt;\u00a0Eventing<\/strong>\u00a0screen:<\/p>\n<ul>\n<li>Click on the Function name <strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651<\/span><\/strong>\u00a0to expand and expose the Function controls.<\/li>\n<li>Click <strong>Pause<\/strong>.<\/li>\n<li>In the <strong>Confirm Pause Function<\/strong> dialog, select \u201c<strong>Pause Function\u201d<\/strong>.<\/li>\n<li>Click \u201c<strong>Edit JavaScript<\/strong>\u201d<\/li>\n<li>If you&#8217;re feeling confident, modify the <strong>doCronActionB <\/strong>function to perform some KV operations or integrate with cURL.<br \/>\nIf you just want to see a change add something simple to the function try something like the below:<\/p>\n<pre class=\"toolbar-overlay:false nums:false lang:js decode:true\">function doCronActionB(doc) {\r\n  try {\r\n    \/\/ check that doc has desired values\r\n    if (doc.type !== \"recurring_event\" || doc.active !== true) return;\r\n    if (doc.verbose.user_func &gt;= 1)\r\n      log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);\r\n\r\n    \/\/ YOUR LOGIC HERE\r\n    var a = 1 + 7; \r\n    log('this is my logic, a = 1 +7 = ' + a);\r\n    \r\n  } catch (e) {\r\n    log(doc.action + ' Error exception:', e);\r\n    return false;\r\n  }\r\n  return true;\r\n}<\/pre>\n<\/li>\n<li>Click <strong>Save<\/strong>.<\/li>\n<li>To return to the Eventing screen, click the &#8216;<strong>&lt; back to Eventing<\/strong>&#8216; link (below the editor) or click the <strong>Eventing<\/strong><\/li>\n<li>Click <strong>Resume<\/strong>.<\/li>\n<li>In the <strong>Confirm Resume Function<\/strong> dialog, select \u201c<strong>Resume Function\u201d<\/strong>.<\/li>\n<li>Wait for about a minute and for the function <strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651<\/span><\/strong>\u00a0to deploy<\/li>\n<li>Click the <strong>Log<\/strong> link of the deployed <strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651<\/span><\/strong>\u00a0Eventing function.\n<div style=\"border: 1px solid black;max-width: 1000px;padding: 4px;font-size: 60%;margin-top: 12px;margin-bottom: 12px\">2020-05-20T19:20:41.343-07:00 [INFO] &#8220;this is my logic, a = 1 +7 = 8&#8221;<br \/>\n2020-05-20T19:20:41.343-07:00 [INFO] &#8220;doCronActionB user action controlled by recurring_event::2&#8221;<\/div>\n<\/li>\n<\/ul>\n<h3>Cleanup<\/h3>\n<p>Cleanup involves undeploying and deleting the function and then removing the two buckets you created. This concludes the Example.<\/p>\n<h4>Remove Functions<\/h4>\n<p>From the <strong>Couchbase Web Console\u00a0&gt;\u00a0Eventing<\/strong>\u00a0screen:<\/p>\n<ul>\n<li>Click on the Function name <strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651<\/span><\/strong>\u00a0to expand and expose the Function controls.<\/li>\n<li>Click <strong>Undeploy<\/strong>.<\/li>\n<li>In the <strong>Confirm Undeploy Function<\/strong> dialog, select \u201c<strong>Undeploy Function\u201d<\/strong>.<\/li>\n<li>Wait for the function <strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651<\/span><\/strong><strong>\u00a0<\/strong>to undeploy.<\/li>\n<li>Click <strong>Delete<\/strong>.<\/li>\n<li>In the <strong>Confirm Delete Function<\/strong> dialog, select \u201c<strong>Delete Function\u201d<\/strong>.<\/li>\n<\/ul>\n<p>From the\u00a0<strong>Couchbase Web Console\u00a0&gt;\u00a0Eventing<\/strong>\u00a0screen:<\/p>\n<ul>\n<li>Click on the Function name <strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651_help<\/span><\/strong>\u00a0to expand and expose the Function controls.<\/li>\n<li>Click <strong>Undeploy<\/strong>.<\/li>\n<li>In the <strong>Confirm Undeploy Function<\/strong> dialog, select \u201c<strong>Undeploy Function\u201d<\/strong>.<\/li>\n<li>Wait for the function <strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651_help<\/span><\/strong><strong>\u00a0<\/strong>to undeploy.<\/li>\n<li>Click <strong>Delete<\/strong>.<\/li>\n<li>In the <strong>Confirm Delete Function<\/strong> dialog, select \u201c<strong>Delete Function\u201d<\/strong>.<\/li>\n<\/ul>\n<h4>Remove Buckets<\/h4>\n<p>Next Drop the buckets \u2018<strong>metadata<\/strong>\u2019 &#8216;<strong>crondata<\/strong>&#8216;, and \u2018<strong>travel-sample<\/strong>\u2019 (they can always be recreated).<\/p>\n<p>From the <strong>Couchbase Web Console &gt; Buckets<\/strong> page and click the Documents link of the <strong>travel-sample<\/strong> bucket.<\/p>\n<ul>\n<li><strong>Click<\/strong> on the bucket name \u201c<strong>metadata<\/strong>\u201d to expand and expose the controls<\/li>\n<li>Click <strong>Delete <\/strong><\/li>\n<li>In the <strong>Confirm Delete Bucket<\/strong> dialog, select \u201c<strong>Delete Bucket\u201d<\/strong>.<\/li>\n<li><strong>Click<\/strong> on the bucket name \u201c<strong>crondata<\/strong>\u201d to expand and expose the controls<\/li>\n<li>Click <strong>Delete <\/strong><\/li>\n<li>In the <strong>Confirm Delete Bucket<\/strong> dialog, select \u201c<strong>Delete Bucket\u201d<\/strong>.<\/li>\n<li><strong>Click<\/strong> on the bucket name \u201c<strong>travel-sample<\/strong>\u201d to expand and expose the controls<\/li>\n<li>Click <strong>Delete <\/strong><\/li>\n<li>In the <strong>Confirm Delete Bucket<\/strong> dialog, select \u201c<strong>Delete Bucket\u201d<\/strong>.<\/li>\n<\/ul>\n<h3>Final Thoughts<\/h3>\n<p>I hope you found this walkthrough educational and have developed more appreciation for the Couchbase Eventing Service as a whole.<\/p>\n<p>Earlier I noted that Eventing is designed to process high velocity mutations (in millions per second) from the DCP stream of the function\u2019s associated source bucket.\u00a0 This Eventing Function or scheduler needs only to react to minimal changes in scheduler documents.<\/p>\n<p>I have started 5,000 schedules with this code unchanged just by adding control documents. I have even run 120,000 schedules every minute just to test this implementation (yes 120K is a fairly ludicrous amount of independent <em>cron<\/em> schedules to run).\u00a0 In addition I put 120,000 sc3hedules 2 days in the future to ensure we had no degenerate resource issues.<\/p>\n<p>Being forced to use two Evening Functions to create the scheduling system, the main function <strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651<\/span><\/strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\"> and a simple helper <\/span><strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\">cron_impl_2func_651_help<\/span><\/strong><span class=\"cbui-table-cell cbui-tablerow-title ng-binding\"> was not as elegant as I had hoped.\u00a0 In addition having to work around a bucket op leak via firing a timer on every vBucket was disappointing to say the least. Fortunately, due to this effort changes were implemented and in the upcoming 6.6.0 release I can make a cleaner scheduler using a single Evening Function.\u00a0 \u00a0The key enhancements in 6.6 are 1) the ability to generate a new timer from within a timer callback and 2) the ability to cancel or overwrite an existing timer by reference, and 3) the elimination of growing resource usage on idle systems with timers scheduled in the future.<\/span><\/p>\n<p>I utilized a standalone bucket \u2018<strong>crondata<\/strong>\u2019 to hold the schedule or control documents (I do not count the use of \u2018<strong>metadata<\/strong>\u2019 bucket as it is a required system scratchpad for Eventing and reserved for Eventing itself) to give the most flexibility in manipulating data in other buckets.\u00a0 \u00a0 If I had collocated the control documents in another bucket I would not be able N1QL operations on that bucket (since it is a source bucket to the Eventing Function) and would have been limited to only KV operations to manipulate data in the collocated bucket.<\/p>\n<p>I challenge you to try your hand at other cron use cases and also to think of other ways to leverage a scheduling service:<\/p>\n<ul>\n<li>Checking an item count in a large dataset during &#8220;off peak hours&#8221; and performing incremental purging<\/li>\n<li>Performing scheduled document enrichment via N1QL.<\/li>\n<li>Recalculating stock portfolios on a regular schedule.<\/li>\n<li>Managing TTL or Expiry times via N1QL on a recurring schedule, refer to <a href=\"https:\/\/www.couchbase.com\/blog\/how-to-manage-ttl-with-couchbase-n1ql\/\">How To Manage Time-To-Live (TTL) Documents with Couchbase N1QL.<\/a><\/li>\n<li>Integrating with external REST endpoints on a repeating schedule, refer to <a href=\"https:\/\/www.couchbase.com\/blog\/using-curl-eventing-service-update\/\">Using cURL with the Eventing Service: Update<\/a>.<\/li>\n<li>Update the JavaScript of <strong>cron_impl_2func_651<\/strong> to add a new field &#8220;prev_astatus&#8221; to the JSON dynamic object to save the true\/false result flag returned from the previous executed user action.<\/li>\n<\/ul>\n<h3>Updates<\/h3>\n<p>This blog was updated on July 24, 2020 to add a work around for the 6.5.x releases having growing number of metadata bucket operations which can eventually block mutations for a given Eventing function when creating timers in the future (as in one hour+) in an otherwise idle system.<\/p>\n<h3>Next Steps<\/h3>\n<p>In a few weeks \u201c<em>Implementing a robust portable cron like scheduler via Couchbase Eventing (Part 2)<\/em>\u201d will be released, where we will explore executing a sequence of database driven dynamic N1QL statements without the requirement to edit the Eventing Function or to define a hardcoded \u201caction\u201d script inside the Eventing function.<\/p>\n<h3>Resources<\/h3>\n<ul>\n<li><em>Download: <\/em><a href=\"https:\/\/couchbase.com\/downloads?family=server&amp;product=couchbase-server-developer\">Download Couchbase Server 6.5.1<\/a><\/li>\n<li><em>Eventing Function: <\/em><a href=\"https:\/\/raw.githubusercontent.com\/couchbaselabs\/blog-source-code\/master\/Strabala\/CronFiles\/cron_impl_2func_651.json\">cron_impl_2func_651.json<\/a><\/li>\n<li><em>Eventing Helper Function:<\/em> <a href=\"https:\/\/raw.githubusercontent.com\/couchbaselabs\/blog-source-code\/master\/Strabala\/CronFiles\/cron_impl_2func_651_help.json\">cron_impl_2func_651_help.json<\/a><\/li>\n<\/ul>\n<h3>References<\/h3>\n<ul>\n<li>Couchbase Eventing documentation:<br \/>\n<a href=\"https:\/\/docs.couchbase.com\/server\/current\/eventing\/eventing-overview.html\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/docs.couchbase.com\/server\/current\/eventing\/eventing-overview.html<\/a><\/li>\n<li>Couchbase Server 6.5 What&#8217;s New:<br \/>\n<a href=\"https:\/\/docs.couchbase.com\/server\/6.5\/introduction\/whats-new.html\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/docs.couchbase.com\/server\/6.5\/introduction\/whats-new.html<\/a><\/li>\n<li>Couchbase blogs on Eventing:<br \/>\n<a href=\"https:\/\/www.couchbase.com\/blog\/tag\/eventing\/\" target=\"_blank\" rel=\"noopener noreferrer\">https:\/\/www.couchbase.com\/blog\/tag\/eventing\/<\/a><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400\">We would love to hear from you on how you liked the 6.5 features and how it\u2019ll benefit your business going forward. Please share your feedback via the comments or in the Couchbase <\/span><a href=\"https:\/\/www.couchbase.com\/forums\/\"><span style=\"font-weight: 400\">forum<\/span><\/a><span style=\"font-weight: 400\">.\u00a0<\/span><\/p>\n<h3>Footnotes<\/h3>\n<div style=\"font-size: 75%\">\n<p><a href=\"#footnote_1\"><sup>[1]<\/sup><\/a> The timer implementation in the Eventing Service is designed to handle large numbers of distributed timers in the millions at high velocity. A single Eventing node can handle over 100K Timers per second and the only promise is to run timers as soon as possible, e.g. no timers lost. Consider that the current scan interval is seven (7) seconds to harvest timers that are ready to fire as such you should expect some delays. For more details on Timer scheduling refer to <a href=\"https:\/\/docs.couchbase.com\/server\/6.5\/eventing\/eventing-timers.html#wall-clock-accuracy\">Timers: Wall-clock Accuracy<\/a> in the Couchbase documentation.<\/p>\n<p><a href=\"#footnote_2\"><sup>[2]<\/sup><\/a> By adjusting <em>allow_interbucket_recursion<\/em> to <em>true<\/em> you are removing guards that were put in place in the Couchbase server to protect against accidentally Eventing logic which can initiate infinite recursive loops.\u00a0 There is nothing wrong with this but it is easy to make a mistake when leveraging recursion.\u00a0 In Couchbase versions 6.6 adjusting the Eventing logic can be collapsed from two (2) Eventing Functions into one (1) simplified Eventing Function with no need to adjust the <em>allow_interbucket_recursion<\/em> setting.<\/p>\n<p><a href=\"#footnote_3\"><sup>[3]<\/sup><\/a> Two major limitations exist. First, if a document is modified several times in a short duration, the calls may be coalesced into a single event due to deduplication. Second, it is not possible to discern between Create and Update operations.\u00a0 For the proposes of a <em>cron<\/em> function neither limitation presents an issue.<\/p>\n<p><a href=\"#footnote_4\"><sup>[4]<\/sup><\/a> Why I didn\u2019t implement exact crontab semantics, I could have but amount of code is excessive \u2013 I refer you to explore the <a href=\"https:\/\/github.com\/kelektiv\/node-cron\">https:\/\/github.com\/kelektiv\/node-cron<\/a> package along with its dependencies of moment and moment-timezone (all very large packages).\u00a0 The getNextRecurringDate(hour_str, min_str) may not be as flexible but it is simple and covers our use case.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>This is the first of a multi-part series to leverage the Couchbase Eventing Service to run multiple scheduled tasks at specific recurring intervals in a cron like fashion completely inside the database without requiring additional infrastructure via a single general-purpose [&hellip;]<\/p>\n","protected":false},"author":42711,"featured_media":8494,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1814,1815,1821,2225,1816,1819,2273,2389,1812],"tags":[],"ppma_author":[9113],"class_list":["post-8489","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-application-design","category-best-practices-and-tutorials","category-couchbase-architecture","category-cloud","category-couchbase-server","category-data-modeling","category-eventing","category-solutions","category-n1ql-query"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.7.1 (Yoast SEO v25.7) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Implementing a Scheduler Via Couchbase Eventing (Part 1)<\/title>\n<meta name=\"description\" content=\"In this tutorial, you&#039;ll learn about running fixed user routines and JavaScript functions defined inside an Eventing Function.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Implementing a Scheduler Via Couchbase Eventing (Part 1)\" \/>\n<meta property=\"og:description\" content=\"In this tutorial, you&#039;ll learn about running fixed user routines and JavaScript functions defined inside an Eventing Function.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2020-05-22T17:50:02+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-14T05:40:23+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1920\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Jon Strabala, Principal Product Manager, Couchbase\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jon Strabala, Principal Product Manager, Couchbase\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"73 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/\"},\"author\":{\"name\":\"Jon Strabala, Principal Product Manager, Couchbase\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c991579f88217edee79ffedb6fc914cc\"},\"headline\":\"Implementing a Scheduler Via Couchbase Eventing (Part 1)\",\"datePublished\":\"2020-05-22T17:50:02+00:00\",\"dateModified\":\"2025-06-14T05:40:23+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/\"},\"wordCount\":8614,\"commentCount\":3,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg\",\"articleSection\":[\"Application Design\",\"Best Practices and Tutorials\",\"Couchbase Architecture\",\"Couchbase Capella\",\"Couchbase Server\",\"Data Modeling\",\"Eventing\",\"Solutions\",\"SQL++ \/ N1QL Query\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/\",\"name\":\"Implementing a Scheduler Via Couchbase Eventing (Part 1)\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg\",\"datePublished\":\"2020-05-22T17:50:02+00:00\",\"dateModified\":\"2025-06-14T05:40:23+00:00\",\"description\":\"In this tutorial, you'll learn about running fixed user routines and JavaScript functions defined inside an Eventing Function.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#primaryimage\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg\",\"width\":2560,\"height\":1920,\"caption\":\"Prague Astronomical Clock\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Implementing a Scheduler Via Couchbase Eventing (Part 1)\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c991579f88217edee79ffedb6fc914cc\",\"name\":\"Jon Strabala, Principal Product Manager, Couchbase\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/9c6045b0c2f7b07b0ee10f94ad748a25\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/db52a9f6d84faba430dd38106cdbc16ff02c2066b103b5f6b4cfcde40e83c683?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/db52a9f6d84faba430dd38106cdbc16ff02c2066b103b5f6b4cfcde40e83c683?s=96&d=mm&r=g\",\"caption\":\"Jon Strabala, Principal Product Manager, Couchbase\"},\"description\":\"Jon Strabala is a Principal Product Manager, responsible for the Couchbase Eventing Service. Before joining Couchbase, he spent more than 20 years building software products across various domains, starting with EDA in aerospace then transitioning to building enterprise software focused on what today is coined \u201cIoT\u201d and \u201cat-scale data.\u201d Jon worked for several small software consultancies until eventually starting and managing his own firm. He has extensive experience in NoSQL\/NewSQL, both in contributing and commercializing new technologies such as compressed bitmaps and column stores. Jon holds a bachelor\u2019s degree in electrical engineering and a master's in computer engineering, both from the University of Southern California, and an MBA from the University of California at Irvine.\",\"url\":\"https:\/\/www.couchbase.com\/blog\/author\/jon-strabala\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Implementing a Scheduler Via Couchbase Eventing (Part 1)","description":"In this tutorial, you'll learn about running fixed user routines and JavaScript functions defined inside an Eventing Function.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/","og_locale":"en_US","og_type":"article","og_title":"Implementing a Scheduler Via Couchbase Eventing (Part 1)","og_description":"In this tutorial, you'll learn about running fixed user routines and JavaScript functions defined inside an Eventing Function.","og_url":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/","og_site_name":"The Couchbase Blog","article_published_time":"2020-05-22T17:50:02+00:00","article_modified_time":"2025-06-14T05:40:23+00:00","og_image":[{"width":2560,"height":1920,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg","type":"image\/jpeg"}],"author":"Jon Strabala, Principal Product Manager, Couchbase","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Jon Strabala, Principal Product Manager, Couchbase","Est. reading time":"73 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/"},"author":{"name":"Jon Strabala, Principal Product Manager, Couchbase","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c991579f88217edee79ffedb6fc914cc"},"headline":"Implementing a Scheduler Via Couchbase Eventing (Part 1)","datePublished":"2020-05-22T17:50:02+00:00","dateModified":"2025-06-14T05:40:23+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/"},"wordCount":8614,"commentCount":3,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg","articleSection":["Application Design","Best Practices and Tutorials","Couchbase Architecture","Couchbase Capella","Couchbase Server","Data Modeling","Eventing","Solutions","SQL++ \/ N1QL Query"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/","url":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/","name":"Implementing a Scheduler Via Couchbase Eventing (Part 1)","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg","datePublished":"2020-05-22T17:50:02+00:00","dateModified":"2025-06-14T05:40:23+00:00","description":"In this tutorial, you'll learn about running fixed user routines and JavaScript functions defined inside an Eventing Function.","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2020\/04\/Schema_Orloj_pragueorlojhzenilc-scaled.jpg","width":2560,"height":1920,"caption":"Prague Astronomical Clock"},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/implementing-a-robust-portable-cron-like-scheduler-via-couchbase-eventing-part-1\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Implementing a Scheduler Via Couchbase Eventing (Part 1)"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"The Couchbase Blog","description":"Couchbase, the NoSQL Database","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"The Couchbase Blog","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/c991579f88217edee79ffedb6fc914cc","name":"Jon Strabala, Principal Product Manager, Couchbase","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/9c6045b0c2f7b07b0ee10f94ad748a25","url":"https:\/\/secure.gravatar.com\/avatar\/db52a9f6d84faba430dd38106cdbc16ff02c2066b103b5f6b4cfcde40e83c683?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/db52a9f6d84faba430dd38106cdbc16ff02c2066b103b5f6b4cfcde40e83c683?s=96&d=mm&r=g","caption":"Jon Strabala, Principal Product Manager, Couchbase"},"description":"Jon Strabala is a Principal Product Manager, responsible for the Couchbase Eventing Service. Before joining Couchbase, he spent more than 20 years building software products across various domains, starting with EDA in aerospace then transitioning to building enterprise software focused on what today is coined \u201cIoT\u201d and \u201cat-scale data.\u201d Jon worked for several small software consultancies until eventually starting and managing his own firm. He has extensive experience in NoSQL\/NewSQL, both in contributing and commercializing new technologies such as compressed bitmaps and column stores. Jon holds a bachelor\u2019s degree in electrical engineering and a master's in computer engineering, both from the University of Southern California, and an MBA from the University of California at Irvine.","url":"https:\/\/www.couchbase.com\/blog\/author\/jon-strabala\/"}]}},"authors":[{"term_id":9113,"user_id":42711,"is_guest":0,"slug":"jon-strabala","display_name":"Jon Strabala, Principal Product Manager, Couchbase","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/db52a9f6d84faba430dd38106cdbc16ff02c2066b103b5f6b4cfcde40e83c683?s=96&d=mm&r=g","author_category":"","last_name":"Strabala, Principal Product Manager, Couchbase","first_name":"Jon","job_title":"","user_url":"","description":"Jon Strabala is a Principal Product Manager, responsible for the Couchbase Eventing Service. Before joining Couchbase, he spent more than 20 years building software products across various domains, starting with EDA in aerospace then transitioning to building enterprise software focused on what today is coined \u201cIoT\u201d and \u201cat-scale data.\u201d Jon worked for several small software consultancies until eventually starting and managing his own firm. He has extensive experience in NoSQL\/NewSQL, both in contributing and commercializing new technologies such as compressed bitmaps and column stores. Jon holds a bachelor\u2019s degree in electrical engineering and a master's in computer engineering, both from the University of Southern California, and an MBA from the University of California at Irvine."}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/8489","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/users\/42711"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/comments?post=8489"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/posts\/8489\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media\/8494"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/media?parent=8489"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/categories?post=8489"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/tags?post=8489"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=8489"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}