Esta es la primera de una serie de varias partes para aprovechar la Servicio de eventos Couchbase para ejecutar múltiples tareas programadas a intervalos recurrentes específicos en un cron completamente dentro de la base de datos sin necesidad de infraestructura adicional a través de una única función de eventos de uso general.
En esta entrega, nos centraremos en la ejecución de rutinas fijas de usuario, funciones JavaScript definidas dentro de una función de eventos.
En artículos posteriores ampliaremos la cron como Eventing Function para programar y ejecutar sentencias N1QL dinámicas manejadas por la base de datos y finalmente exploraremos la programación de funciones JavaScript dinámicas manejadas por la base de datos.
Fondo
El servicio de eventos de Couchbase proporciona un marco para escribir tus propias rutinas, simples funciones JavaScript, para procesar los cambios en los documentos. Este servicio proporciona toda la infraestructura necesaria para crear funciones escalables y robustas basadas en la nube, permitiéndote centrarte en desarrollar pura lógica de negocio para interactuar casi en tiempo real con los cambios en tus datos. Tus funciones pueden acceder al servicio de datos de Couchbase (KV), al servicio de consultas de Couchbase (N1QL) y a puntos finales REST externos al sistema Couchbase.

En JSON modelo de datos de Couchbase procede de JavaScriptPor lo tanto, es natural que el servicio de eventos ofrezca la posibilidad de escribir código JavaScript para analizar y manipular documentos JSON en cualquier tipo de evento de cambio, incluyendo inserciones, actualizaciones, fusiones y eliminaciones (denominadas conjuntamente mutaciones).
Las funciones de eventos normalmente le permiten desplegar y ejecutar fragmentos de código personalizados para que reaccionen a miles e incluso millones de mutaciones por segundo en sus documentos. Algunos casos de uso típicos son documentado para desarrollar Funciones Eventing de alta velocidad a escala que respondan a mutaciones de documentos Couchbase.

Este artículo se centrará más bien en un caso de uso de muy baja velocidad del Servicio de Eventos construyendo un crontab distribuido fiable "en base de datos", permitiéndote ejecutar funciones JavaScript que interactúan con servicios Couchbase en un horario periódico regular.
Programación de la lógica empresarial para que se ejecute en una fecha u hora determinadas
Cronque lleva el nombre de "Chronos," la palabra griega para el tiempo es una de las utilidades más útiles en un sistema Linux. En Linux el cron está controlada por un archivo crontab (tabla cron), un archivo de configuración que especifica los comandos del shell que se ejecutarán periódicamente en un horario determinado.
Uno de los inconvenientes cron es que no está diseñado para ser un servicio distribuido; se ejecuta en una sola caja, por lo que presenta un único punto de fallo. Si el sistema está fuera de línea durante varias horas, se pierden todas las tareas programadas.
Sí, hay algunos distribuidos cron como Google's Cloud Service, AWS' Scheduled Tasks, y Azure Functions / Time Triggers. Pero las ofertas de cada proveedor de nube tienen sus propios modismos que no son directamente portables.
Además, es necesario asegurar la metodología de configuración y control, por ejemplo si se controla una cron a través de una API REST sobre HTTP/S, deberá tenerlo en cuenta en su plan de seguridad.
Utilizar el propio Couchbase para ejecutar comandos periódicos
Con una pequeña cantidad de código y planificación, puede aprovechar el servicio Eventing de Couchbase para proporcionar cron para las operaciones o el mantenimiento programados de la base de datos. La integración del programador en la base de datos permite obtener las siguientes ventajas:
- Portabilidad a través de proveedores Cloud, si reubicas tu cluster de Couchbase tu planificador no se ve afectado.
- Soporte, si utiliza Couchbase tiene un único proveedor para proporcionarle soporte y otros servicios.
- Distribuido, no hay un único punto de fallo y todos los servicios de Couchbase admiten réplicas distribuidas.
- Ejecución garantizada, su tarea se ejecuta incluso después de recuperarse de un fallo de nodo.
Programación de Couchbase, los temporizadores son la salsa secreta
Los temporizadores son construcciones del servicio de eventos de Couchbase mediante las cuales los desarrolladores pueden especificar una rutina (lógica de negocio) que se activará en un momento futuro. Utilizaremos esta funcionalidad para implementar un Couchbase configurable puro. crontab que le permite activar tareas repetitivas como parte de sus flujos de trabajo, tanto si necesita ejecutar una simple consulta N1QL como construir un complejo motor de reglas.
En todos los diseños posteriores limitaremos nuestra cron a una resolución de 15 segundos o más. Tenemos esta limitación porque, aunque los temporizadores se escalan a millones y se garantiza que se disparan y ejecutan, no son precisos como un reloj de pared y actualmente tienen un retardo de estado estacionario limitado a menos de 14 segundos. [1].
Por supuesto, si necesita una programación más ajustada, es decir, menos de 15 segundos, entonces simplemente debe procesar la mutación en sí en la lógica de eventos sin el uso de una construcción de temporizador para programar una llamada de vuelta en el futuro.
En el momento de escribir estas líneas, la versión actual de Couchbase es la 6.5.1 que dos limitaciones con las que debemos trabajar al hacer un robusto cron sistema.
- En las versiones 5.5.x, 6.0.x y 6.5.x, una función invocada por una llamada de retorno de un temporizador no puede crear un nuevo temporizador de forma fiable (se puede solucionar mediante una segunda función cooperativa).
- En las versiones 6.5.x, la creación de temporizadores en el futuro (como en una hora o más) en un sistema por lo demás inactivo puede dar lugar a un número creciente de operaciones de cubo de metadatos que eventualmente pueden bloquear las mutaciones para una función de Eventing dada (en 6.5.X se puede lograr una solución en el espacio de usuario a través de una segunda función cooperativa). La gravedad se rige por:
- El número de vBuckets que mantienen un temporizador activo. Por lo tanto, si solo hay unos pocos temporizadores en el futuro, es posible que el problema no se note o no se materialice. Este es el caso con sólo unos pocos cron horarios, pero para completar en caso de añadir la funcionalidad de la fecha puse en una solución para este problema para el código suministrado en este artículo.
- Si un temporizador de Eventing se ha disparado recientemente en un vBucket (lo que soluciona el problema para el vBucket en cuestión por función). Por lo tanto, los sistemas con mucha actividad de temporizadores a corto plazo no experimentarán este problema aunque los temporizadores estén programados para un futuro lejano.
Afortunadamente, en la versión 6.6.0 ambos problemas o restricciones se han eliminado y se puede crear un programador en una única y sencilla Función de Eventos unificada.

Requisitos previos
En este artículo utilizaremos la última versión de GA, es decir, la versión 6.5.1 de Couchbase (puede que tengas que hacer algunos cambios en las funciones de eventos descritas para versiones anteriores de Couchbase). El ejemplo de este artículo se ejecutará contra el conjunto de datos travel-sample que se entrega con el servidor Couchbase.
CONSEJO PROFESIONAL: Sólo para usuarios avanzados, si estás familiarizado con Couchbase Eventing y también con nuestras herramientas CLI / REST puedes saltarte la mayor parte de este blog y descargar un archivo ZIP para configurar y ejecutar rápidamente el sistema programador que se presenta a continuación. Haz clic con el botón derecho en el siguiente enlace y selecciona Guardar enlace como para descargar el archivo cron_impl_2func_CLI.zip, muévalo a un nodo Eventing, extraiga el archivo ZIP y consulte el archivo README.txt extraído.
Sin embargo, si usted no está familiarizado con Couchbase o el servicio Eventing por favor camine a través de GET STARTED y un ejemplo Eventing específicamente se refieren a los siguientes:
- Configure un servidor Couchbase 6.5.1 que funcione según las instrucciones de Comience aquí
- Asegúrese de que puede ejecutar una consulta N1QL en la base de datos viaje-muestra según las instrucciones de Ejecute su primera consulta N1QL.
- Comprender cómo desplegar una función básica de Eventing según las instrucciones de la sección Archivo de documentos que también utiliza viaje-muestra conjunto de datos.
- Asegúrese de tener la viaje-muestra en la vista de cubos de la interfaz de usuario.
- Asegúrese de que un cubo llamado metadatos en la vista Buckets de la interfaz de usuario debe tener un tamaño mínimo de 200 MB.
- En la vista Buckets de la interfaz de usuario, cree un bucket llamado crondata con un tamaño mínimo de 200 MB. Para obtener información detallada sobre cómo crear cubos, consulte Crear un cubo.
- Establecer allow_interbucket_recursion a verdadero para permitir que dos (2) funciones de Eventing modifiquen el mismo documento KV [2].
1curl -X POST -u "$CB_USERNAME:$CB_PASSWORD" 'https://localhost:8091/_p/event/api/v1/config' -d '{ "allow_interbucket_recursion":true }'
Implementación #1, programación tipo "cron" codificada.
Para nuestra primera implementación, por ejemplo la Parte 1 de la serie, diseñaremos una estructura de control simple que es simplemente un documento KV JSON y también dos (2) Funciones Eventing que responderán y actuarán sobre la información en la estructura de control.
A continuación se muestra un diseño de un documento JSON, o estructura de control, que nos permitirá tener múltiples "eventos" programados. Cada evento programado tendrá su propio documento de control con una CLAVE única como recurring_event::1, recurring_event::1, ... recurring_event::N. La propia estructura JSON contiene información para "reconstituir la clave", ya que nuestro sistema de programación responderá a los cambios o actualizaciones (mutaciones) de los documentos de control, como alternar el estado "activo" para activar o desactivar la acción o cambiar el campo "verbose" que controla la cantidad y el estilo del registro.
A continuación se muestra un ejemplo de documento de control con CLAVE evento_recurrente::1 que ejecutará la función JavaScript doCronActionA a las 14:54 (14:30) todos los días.
| Registro de control JSON | Descripción |
|---|---|
| { | |
| "type": "recurring_event", | La CLAVE será <>::<>. |
| "id":1, | |
| "hora":14, | La hora del día 0-23, *, *2X, *4X para activar |
| "min":54, | El minuto en la hora 0-59, *, *2X, *4X para activar |
| "acción": "doCronActionA", | Función JavaScript que se ejecutará cuando se active el temporizador |
| "activo":true, | Bandera para activar o desactivar este horario |
| "verbose": { | [OPCIONAL] control de registro |
| "user_func":2, | Nivel de registro para la lógica de acción : 0=ninguno, etc. etc. |
| "programador":3 | Nivel de registro para la lógica cron : 0=ninguno, etc. etc. |
| }, | |
| "dinámico": { | control y estadísticas del sistema [DINÁMICO |
| "estado": "brazo", | "armar"|"rearmar"|"pendiente" cualquier valor != "pendiente" iniciar una programación |
| "next_sched": 0, | Número de segundos transcurridos desde la época hasta el siguiente horario deseado |
| "prev_sched": 0, | Número de segundos transcurridos desde la fecha de la programación anterior |
| "prev_etime": 0, | Número de segundos transcurridos desde la época para el tiempo de ejecución real del programa anterior |
| "prev_delay": 0, | Número de segundos que el temporizador se ha retrasado con respecto a la programación |
| "prev_atime": 0 | Número de segundos que ha tardado el usuario 'acción' |
| } | |
| } |
Como Linux tradicional crontab puede establecer la hora y el minuto en números enteros legales, y también puede establecer hora a "*" para procesar todas las horas o ajuste min a "*" para procesar todos los minutos.
Aunque no apoyaremos la crontab sintaxis admitimos dos configuraciones no estándar, a saber, si fijar ambos hora y min a "*4X" ejecutaremos y rearmaremos cuatro (4) veces por minuto y si los configuras ambos a "*2X" ejecutaremos y rearmaremos dos (2) veces por minuto. A continuación se muestra una tabla de horarios soportados con su descripción:
| hora | min | Los valores pueden ser números o cadenas |
|---|---|---|
| 13 | 32 | Corre a las 13:32 (o 13:32) |
| * | 15 | Se ejecuta cada hora a los 15 minutos |
| 8 | 12 | Corre una vez al día a las 8:32 (o 8:32 am) |
| * | * | Ejecutar una vez por minuto |
| *2X | *2X | Se ejecuta dos veces por minuto - requiere que tanto la hora como el minuto estén ajustados a "*2X". |
| *4X | *4X | Funciona cuatro veces por minuto - requiere que la hora y el minuto estén ajustados a "*2X". |
Eventualmente utilizaremos el Query Workbench para insertar el cron documentos de control, todos los cuales deben tener una CLAVE única de evento_recurrente::# a una hora de ejecución programada de 14:54 ( 2:54 pm), para la acción doCronActionA, podríamos utilizar la siguiente sentencia N1QL.
No te preocupes por ejecutar ninguna sentencia N1QL en este momento, realizaremos las sentencias N1QL más tarde después de haber construido y desplegado nuestra Función de Eventos.
Puede crear un registro (o registros) de control en el cubo viaje-muestray luego listarlo, armarlo, desarmarlo, ajustar el horario que sigue, cambiar el nivel de verbosidad para el registro, o borrarlo de la siguiente manera:
| Acción | Declaración N1QL |
|---|---|
| Crear un calendario | INSERTAR EN viaje-muestra (CLAVE,VALOR) VALORES ("recurring_event::1", { "type": "recurring_event", "id":1, "hora": "14″, "min": "54″, "acción": "doCronActionA", "activo":true } ); |
| Crear un índice para consultar datos sin especificar claves | CREAR un índice primario sobre crondata ; |
| Mostrar todos los horarios ordenados por id | SELECT * FROM crondata WHERE type="recurring_event" order by id ; |
| Mostrar horario específico | SELECT * FROM crondata WHERE type="recurring_event" AND id=1 ; |
| Armar o activar | ACTUALIZACIÓN crondata SET active = true WHERE type="recurring_event" AND id=1 ; |
| Desarmar o desactivar | ACTUALIZACIÓN crondata SET active = false WHERE type="recurring_event" AND id=1 ; |
| Ajustar el tiempo de activación | ACTUALIZACIÓN crondata SET hora = 11, min = 30 WHERE type="recurring_event" AND id=1 ; |
| Ajustar el registro de la "acción | ACTUALIZACIÓN crondata SET verbose.user_data = 0 WHERE type="recurring_event" AND id=1 ; |
| Ajustar el registro de la lógica del programador | ACTUALIZACIÓN crondata SET verbose.scheduler = 0 WHERE type="recurring_event" AND id=1 ; |
| Borrar el horario | DELETE FROM crondata WHERE type="recurring_event" AND id=1 ; |
Supongamos que tenemos cuatro (4) horarios activos, la ejecución de la primera sentencia N1QL, por encima de la lista de todos ellos, por ejemplo.
|
1 2 |
SELECT active, action, hour, min, type, id, verbose.user_func, verbose.scheduler FROM `crondata` where type="recurring_event" order by id ; |
Devolvería algo como la siguiente salida (vista de tabla en el Query Workbench):
| activo | acción | hora | id | min | programador | tipo | usuario_func |
|---|---|---|---|---|---|---|---|
| verdadero | "doCronActionA" | 14 | 1 | 54 | 1 | "evento_recurrente" | 2 |
| verdadero | "doCronActionB" | * | 2 | * | 1 | "evento_recurrente" | 1 |
| verdadero | "doCronActionC" | *2X | 3 | *2X | 4 | "evento_recurrente" | 4 |
| verdadero | "doCronActionD" | * | 4 | 0 | 0 | "evento_recurrente" | 1 |
En la tabla anterior tenemos cuatro acciones: la primera se ejecuta una vez al día, la segunda cada minuto, la tercera cada 30 segundos y la cuarta cada hora. En una próxima entrega de esta serie añadiremos la función "día de la semana".
El objeto anidado "verbose"si no se suministra será por defecto { "user_func":1, "scheduler":1 } indicando un nivel de registro bajo o escueto para la función de acción y también para la lógica de programación. Un valor de 0 suprimirá todos los mensajes de registro, es decir, doCronActionD, mientras que valores mayores serán más verbosos, es decir, como se define en doCronActionC.
El objeto anidado "dinámico" si normalmente nunca se suministra y será por defecto { "state": "arm", "next_sched": 0, "prev_sched": 0, "prev_etime": 0, "prev_delay": 0, "prev_atime": 0 } este es un bloc de notas para la programación lógica de eventos en ejecución y también proporciona estadísticas útiles sobre los tiempos de ejecución, por lo que debe ser tratado como de sólo lectura.
En este punto tenemos un diseño de control de alto nivel, pero necesitamos lógica para procesar nuestras estructuras de control, aquí es donde el Servicio de Eventos de Couchbase, específicamente una Función de Eventos entra en juego.
Funciones del concurso
Este diseño requiere dos (2) funciones de Eventing: una función JavaScript principal "cron_impl_2func_651" y una pequeña función JavaScript de ayuda "cron_impl_2func_651_help". Discutiremos cada sección de las funciones JavaScript que comprende la implementación inicial combinada de código JavaScript de casi 610 líneas (con aproximadamente 44% de las líneas son comentarios y espacios en blanco)
No te preocupes por hacer un cortar y pegar en este momentoComo más tarde voy a proporcionar un enlace para descargar (para la importación) las dos funciones de eventos necesarios y todos los ajustes necesarios en dos archivos llamados "cron_impl_2func_651.json" "cron_impl_2func_651_help.json" y también si lo prefiere las dos funciones unificadas completo que se puede cortar y pegar directamente.
Nuestra función principal de eventos "cron_impl_2func_651" estará compuesta por nueve (9) funciones JavaScript
- Tres (3) funciones de lógica empresarial (dos de las cuales están vacías).
- doCronActionA(doc) - una acción de usuario de ejemplo N1QL a ejecutar
- doCronActionB(doc) - un intérprete de comandos de acción de usuario vacío para experimentos
- doCronActionC(doc) - un intérprete de comandos de acción de usuario vacío para experimentos
- Un (1) punto de entrada para concursos completos.
- OnUpdate(doc, meta) - el punto de entrada estándar de Eventing para Inserciones o Actualizaciones
- Uno (1) cron analizador sintáctico para generar la siguiente programación.
- getNextRecurringDate(hour_str, min_str) - lógica cron para encontrar la siguiente Fecha programada
- Tres (3) funciones de apoyo para comprobar que la lógica de negocio existe o formatear resultados.
- verifyFunctionExistsViaEval(curDoc, id) - asegurarse de que tenemos una función que ejecutar
- toNumericFixed(number, precision) - formatea un flotante a un estilo compacto
- toLocalISOTime(d) - formatea una fecha a un estilo compacto
- Una (1) función callback cuando se ejecutan los temporizadores.
- Callback(doc) - una función de devolución de llamada para temporizadores programados
Nuestra función de ayuda para eventos "cron_impl_2func_651_help" estará compuesta por una (1) función JavaScript
- Un (1) punto de entrada para concursos completos.
- OnUpdate(doc, meta) - el punto de entrada estándar de Eventing para Inserciones o Actualizaciones
En las secciones siguientes recorreremos cada una de las funciones JavaScript anteriores.
Necesitamos una función JavaScript, por ejemplo, la lógica de negocio para ejecutar en un horario periódico.
Lo primero que queremos es una rutina o función que contenga nuestra lógica de negocio que ejecutaremos basándonos en nuestras reglas crontab. Llamaremos al método JavaScript doCronActionA(doc)sin embargo, puede llamarse como se quiera, por ejemplo doPeriodicLedgerBalance(doc), los únicos requisitos para nuestras funciones de "acción" que implementan nuestra lógica de negocio programada son los siguientes:
- Tiene un parámetro: doc, un documento de control como el descrito anteriormente de type="recurring_event".
- El nombre real de JavaScript coincide con el campo "acción" del documento de control.
- Devuelve verdadero sobre el éxito y falso sobre el fracaso
- Utiliza doc.verbose.user_func para controlar el registro si 0 es silencioso, si 1 emite una sola línea, si 2 emite cualquier información de registro que se necesite para depurar la función, etc. etc..
Escribiremos nuestra función doCronActionA(doc)para ejecutar una consulta N1QL incrustada ) para combinar los recuentos de líneas aéreas por país y, a continuación, crear un único documento KV con los datos calculados.
|
1 |
SELECT country, count( * ) AS cnt FROM `travel-sample` WHERE `type` = 'airline' GROUP BY country; |
En mi sistema de prueba un pequeño nodo único noMDS (ejecutando todos los servicios de Couchbase) el N1QL anterior tarda unos 20 ms. (para mayor claridad fingir que es supercomplejo tarda 10 segundos en completarse).
La idea aquí es que el documento KV final calculado y resumido pueda ser cargado rápidamente por 100K (o un millón) de mutaciones Eventing por segundo sin la sobrecarga adicional de comunicación con los nodos del servicio de Consulta y de procesar sentencias N1QL en cada mutación.
Debería ser obvio que el objetivo de esta lógica de negocio en particular, doCronActionA(doc)es crear una caché semiestática que se actualice periódicamente según un calendario.
Todo lo que estamos haciendo realmente (y es bastante rápido) es obtener un recuento de aerolíneas por país a partir del conjunto de documentos de la muestra de viajes. A medida que usamos N1QL construimos un documento y eventualmente lo escribimos en KV como un documento resumido. El punto clave a destacar aquí es que no queremos repetir el mismo trabajo para millones de mutaciones cada uno, sobre todo porque algunos cálculos podrían tomar 10 segundos de tiempo de cálculo del servicio de consulta cada vez que iniciamos una consulta N1QL incrustada desde una función Eventing.
A continuación mostramos la función JavaScript que queremos que se ejecute una vez al día (o quizás una vez cada hora, etc.). Observa que el nombre de la función coincide con el nombre del campo de acción de la estructura de control. Para más detalles sobre la terminología de Eventing y las construcciones del lenguaje, consulta los documentos y ejemplos de Couchbase en Servicio de Eventing: Fundamentos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
function doCronActionA(doc) { try { // Check that doc has desired values if (!doc.type || doc.type !== "recurring_event" || !doc.active || doc.active !== true) return; if (doc.verbose.user_func >= 1) log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id); // this is a 6.5 N1QL query (feature not available in GA prior to 6.5) // Create an embedded N1QL iterator by issuing a SELECT statement to get the // counts of airlines by country. Make a new document and write it out to KV // We will use the iterator to create a KV document representing the results of a // HARD lengthy embedded N1QL query and write it back to KV, the idea is to keep // a calculation up to date once a day such that it that can be read 'quickly' // by other Eventing Functions, other Couchbase services or SDKs. // Consider if we had 1 million docs in a minute do we really want to use N1QL // to recalculate something that is almost static for all 1 million documents, of // course not, so we make an intermediate value that can be read into Eventing // and used via a single 'light weight' KV read. var q_iter = SELECT country, count( * ) cnt FROM `travel-sample` WHERE `type` = 'airline' GROUP BY country; // loop through the result set and update the map 'accumulate' var accumulate = {}; var idx = 0; for (var val of q_iter) { if (doc.verbose.user_func >= 2) log(doc.action + ' N1QL idx ' + idx + ', country ' + val.country + " cnt " + val.cnt); accumulate[val.country] = val.cnt; idx++; } // close out embedded N1QL iterator q_iter.close(); // Now let’s make a cached KV document representing a HARD length embedded N1QL // query and write it back to KV, we need a KEY and a type and id and then we // upsert it into the `travel-sample` bucket. var cachedoc = {}; cachedoc.type = "cron_cache"; cachedoc.id = "airlines_by_country"; cachedoc.date = new Date(); cachedoc.data = accumulate; var ckey = cachedoc.type + '::' + cachedoc.id; ts_bkt[ckey] = cachedoc; if (doc.verbose.user_func >= 2) { log(doc.action + ' upsert to KV with KEY ' + ckey + ' cachedoc ', cachedoc); } } catch (e) { log(doc.action + ' Error exception:', e); return false; } return true; } |
La función anterior simplemente 1) consulta el cubo de muestras de viajes para extraer datos, en este caso el recuento de aerolíneas para cada país, 2) crea un nuevo documento KV y una nueva clave y lo escribe en el cubo de muestras de viajes para su uso posterior.
Además, como parte de este ejemplo hemos construido un registro que responde a un ajuste numérico de verbosidad que a) registra una sola línea si el documento de control tiene un valor para doc.verbose.user_func == 1 o b) emite más información si el valor de doc.verbose.user_func >= 2.
Se trata de un marco genérico que puede ejecutar una (1) cron acción o incluso mil (1000) de cron acciones. Como tal, he proporcionado dos funciones adicionales "vacías" - como se señaló antes, podrían haber sido nombradas de cualquier manera.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function doCronActionB(doc) { try { // check that doc has desired values if (doc.type !== "recurring_event" || doc.active !== true) return; if (doc.verbose.user_func >= 1) log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id); // YOUR LOGIC HERE } catch (e) { log(doc.action + ' Error exception:', e); return false; } return true; } |
y
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function doCronActionC(doc) { try { // check that doc has desired values if (doc.type !== "recurring_event" || doc.active !== true) return; if (doc.verbose.user_func >= 1) log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id); // YOUR LOGIC HERE } catch (e) { log(doc.action + ' Error exception:', e); return false; } return true; } |
Estas funciones doCronActionB y doCronActionC son triviales, ya que simplemente registran información en el registro de la aplicación de eventos de la función de eventos. Consulte Funciones de registro para más detalles. Por supuesto, necesita un documento de control de type="recurring_event" con active=true y una acción como action = "doCronActionB" para habilitarlos y ejecutarlos realmente.
Necesitamos un punto de entrada o manejador de eventos
A partir de la versión 6.5, el servicio de eventos admite dos puntos de entrada o controladores OnUpdate(doc, meta) y OnDelete(meta) sólo nos interesa el OnUpdate(doc,meta) para este ejemplo.
En OnUpdate(doc,meta) es llamado cuando se crea o modifica (muta) cualquier documento del bucket de origen y filtra inmediatamente los documentos que no interesan. [3]
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
function OnUpdate(doc, meta) { // fix for 6.5.X growing bucket ops if (doc.type === "_tmp_vbs") genNoopTimers(doc, meta, 30); if (!cron_bkt["fix_timer_scan_issue::1"]) { cron_bkt["fix_timer_scan_issue::1"] = {}; } try { // Check if further analysis is needed we only trigger on an active recurring_event if (doc.type !== "recurring_event" || doc.active !== true) return; var update_doc = false; if (!doc.dynamic) { // Add if missing doc.dynamic with defaults doc.dynamic = { "state": "arm", "next_sched": 0, "prev_sched": 0, "prev_etime": 0, "prev_delay": 0, "prev_atime": 0 }; // we need to update the document once we have the next schedule update_doc = true; } if (!doc.verbose) { // Add if missing doc.dynamic with defaults doc.verbose = { "user_func": 1, "scheduler": 1 }; // we need to update the document once we have the next schedule update_doc = true; } // Do not process dynamic.state pending if (!doc.dynamic || !doc.dynamic.state || doc.dynamic.state === "pending") return; var mid = doc.type + "::" + doc.id; // this is the same as meta.id or the KEY var hour = doc.hour; var min = doc.min; // Do an eval check the JavaScript function exists. The eval occurs in a common // utility function shared with RecurringCallback if (!verifyFunctionExistsViaEval(doc, mid)) { // doc.action did not exist, we have already logged the issue return; } // Get the next valid execution time var date_timer = getNextRecurringDate(hour, min); var next_sched = Math.round(date_timer.getTime() / 1000); if (!update_doc && next_sched !== doc.dynamic.next_sched) { // the next_sched should be the same as the setting from the helper application, however // if we undeploy/deploy or pause/resume we might haver to reschedule to the next time slot log('OnUpdate U ' + mid + ' calculated next_sched !== doc.dynamic.next_sched, delta ' + (next_sched - doc.dynamic.next_sched) + ', reschedule'); update_doc = true; } if (update_doc) { // this mutation is recursive and will be suppressed, we ensure we have a dynamic structure doc.dynamic.next_sched = next_sched; // rather then the call a function, to trap and retry if there is a resource issue // cron_bkt[mid] = doc; if (!tryBucketKvWriteWithLog('OnUpdate F', mid, doc)) { // Failed to write doc to cron_bkt[key] the error has been logged // and there is nothing more we can do. return; } } // Schedule an Eventing timer var timer_id = createTimer(Callback, date_timer, null, doc); if (doc.verbose.scheduler >= 1) { log('OnUpdate A ' + mid + ' rcv mutation (initial or rearm) schedule timer at ' + toLocalISOTime(date_timer)); } if (doc.verbose.scheduler >= 2) { log('OnUpdate B ' + mid + ' recurring timer was created, timer_id ' + timer_id); } } catch (e) { log('OnUpdate E ' + meta.id + ', Error exception:', e); } } |
La clave aquí es que el cron en nuestro manejador sólo se preocupa por los documentos que tienen doc.type de "recurring_event y también un doc.active de true. Además, en este ejemplo hemos creado un rastreo para el método cron lógica de mantenimiento que sólo se registra en el registro de la aplicación si el documento de control tiene un valor para doc.verbose >= 3.
Si sólo ejecuta unas pocas programaciones, puede desactivar el trabajo en el espacio de usuario o "arreglo para 6.5.X cubo creciente ops" comentando cuatro líneas de código en el bloque OnUpdate anterior para "cron_impl_2func_651" como sigue:
|
1 2 3 4 5 6 |
function OnUpdate(doc, meta) { // fix for 6.5.X growing bucket ops // if (doc.type === "_tmp_vbs") genNoopTimers(doc, meta, 30); // if (!cron_bkt["fix_timer_scan_issue::1"]) { // cron_bkt["fix_timer_scan_issue::1"] = {}; // } |
Necesitamos código para solucionar posibles operaciones de crecimiento de cubos para 6.5.X
A partir de la versión 6.5.X necesitamos un "arreglo para 6.5.X cubo creciente ops"que ocurre en sistemas inactivos con muchos temporizadores programados en el futuro. Este código garantiza que un temporizador de Eventing se ha disparado recientemente en un vBucket (lo que soluciona el problema para el vBucket dado en función de cada función).
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
// FIXUP: ADDIN FUNCTON function noopTimer(context) { // fix for 6.5.X growing bucket ops try { if (context.type === "_tmp_vbs" && context.vb === 0) { // log("noopTimer timers firing, printing only for vBucket 0"); } } catch (e) { log("OnUpdate Exception in callback noopTimer:", e); } } // FIXUP: ADDIN FUNCTON function rearmTimer(context) { // fix for 6.5.X growing bucket ops try { if (context.type === "_tmp_vbs" && context.vb === 0) { // Update/touch all docs in the helper_bucket the helper function will then // mutate all 1024 of type == vbs_seed (64 on MacOS) to create a recuring cycle. // log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context); // generate a mutation to re-arm the HELPER function: fix_scan_issue // which will in turn make new mutations for this Function var cur = cron_bkt[context.key]; if (cur && cur.ts_millis === context.ts_millis) { // log("rearmTimer update fix_timer_scan_issue::1 in helper_bucket alias only for vBucket 0"); var now = new Date(); cron_bkt["fix_timer_scan_issue::1"] = { "last_update": now }; } else { // NOOP we had multiple timer cycles, just let this one quietly stop. } } } catch (e) { log("OnUpdate Exception in callback rearmTimer:", e); } } // FIXUP: ADDIN FUNCTON function genNoopTimers(doc, meta, seconds) { // fix for 6.5.X growing bucket ops try { // redundant but play it safe if (doc.type === "_tmp_vbs") { // Since we are using an different function a timer on all our vBuckets do immeadiately (can take up to 15 seconds) // 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. createTimer(noopTimer, new Date(), null, doc); if (doc.vb === 0) { // Update/touch all docs in the helper_bucket the helper function will then // mutate all 1024 of type == vbs_seed (64 on MacOS) to create a recuring cycle. // log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context); // generate a mutation to re-arm the HELPER function: fix_scan_issue // which will in turn make new mutations for this Function // log("genNoopTimers make timer to rearm fix_timer_scan_issue::1"); createTimer(rearmTimer, new Date(new Date().getTime() + seconds * 1000), null, doc); } } } catch (e) { log("OnUpdate Exception in genNoopTimers:", e); } } |
Necesitamos una utilidad para calcular la siguiente hora en el horario
La siguiente función getNextRecurringDate(hour, min) determinará una hora para ejecutar la acción definida como parte de nuestra programación. Esto no es una implementación completa de cronEn lugar de ello, contiene las características estándar clave para ejecutarse una vez al día, una vez por hora y una vez por minuto. También contiene alguna sintaxis no estándar para proporcionar la capacidad de ejecutar dos veces por minuto o cuatro veces por minuto.
Como se ha descrito anteriormente, la función getNextRecurringDate(hora, min) permite lo siguiente (la tabla se duplica a continuación), siendo las dos últimas no estándar.[4]
| hora | min | Los valores pueden ser números o cadenas |
|---|---|---|
| 13 | 32 | Corre a las 13:32 (o 13:32) |
| * | 15 | Se ejecuta cada hora a los 15 minutos |
| 8 | 12 | Corre una vez al día a las 8:32 (o 8:32 am) |
| * | * | Ejecutar una vez por minuto |
| *2X | *2X | Se ejecuta dos veces por minuto - requiere que tanto la hora como el minuto estén ajustados a "*2X". |
| *4X | *4X | Funciona cuatro veces por minuto - requiere que la hora y el minuto estén ajustados a "*2X". |
A continuación se muestra una implementación de la lógica necesaria para determinar la próxima vez que debe activarse un temporizador de Eventing en nuestro programa, en el caso de que la lógica de usuario de nuestro primer ejemplo doCronActionA(doc) no se completa a tiempo, por ejemplo, si se sobrepasa el tiempo real, se seleccionará el siguiente cuanto de la programación. Tenga en cuenta tanto los Temporizadores como sus Funciones Padre. Así, si una Función de Eventos tiene un tiempo de espera de ejecución por defecto de 60 segundos, si es necesario esta configuración puede ser ajustada o aumentada.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
function getNextRecurringDate(hour_str, min_str) { // Note Javascript Dates are in milliseconds var date_now = new Date(); var date_ret = new Date(); var hour; var min; try { hour = parseInt(hour_str); } catch (e) {} try { min = parseInt(min_str); } catch (e) {} // Note, this is only a simplistic partial 'crontab' syntax with some slight extensions // it allows once a day, once an hour, once a minute. It also contains some non-standard // syntax to provide the ability to execute twice a minute or four times a minute. if (hour_str === '*4X' && min_str === '*4X') { // once every 15 seconds or four times a minute date_ret.setMilliseconds(0); date_ret.setSeconds(15); while (date_ret.getTime() < date_now.getTime()) { date_ret.setSeconds(date_ret.getSeconds() + 15); } return date_ret; } else if (hour_str === '*2X' && min_str === '*2X') { // once every 30 seconds or twice a minute date_ret.setMilliseconds(0); date_ret.setSeconds(30); while (date_ret.getTime() < date_now.getTime()) { date_ret.setSeconds(date_ret.getSeconds() + 30); } return date_ret; } else if (hour_str === '*' && min_str === '*') { // once a minute date_ret.setMilliseconds(0); date_ret.setSeconds(0); date_ret.setMinutes(date_ret.getMinutes() + 1); } else if (hour_str !== '*' && isNaN(hour) === false && min_str === '*') { // once a minute only for a given hour date_ret.setMilliseconds(0); date_ret.setSeconds(0); date_ret.setMinutes(date_ret.getMinutes() + 1); if (date_ret.getTime() < date_now.getTime()) { date_ret.setHours(hour); } if (date_ret.getTime() > date_now.getTime()) { date_ret.setDate(date_ret.getDate() + 1); date_ret.setSeconds(0); date_ret.setMinutes(0); date_ret.setHours(hour); } } else if (hour_str === '*' && min_str !== '*' && isNaN(min) === false) { // once a hour at a given minute date_ret.setMilliseconds(0); date_ret.setSeconds(0); date_ret.setMinutes(min); // schedule for next hour date_ret.setHours(date_ret.getHours() + 1); } else if (isNaN(hour) === false && isNaN(min) === false) { // once a day for a given hour and a given minute date_ret.setMilliseconds(0); date_ret.setSeconds(0); date_ret.setMinutes(min); date_ret.setHours(hour); if (date_ret.getTime() < date_now.getTime()) { // schedule for tomorrow date_ret.setDate(date_ret.getDate() + 1); } } else { log('getNextRecurringDate illegal input hour_str <' + hour_str + '> min_str <' + min_str + '>'); throw new Error('getNextRecurringDate illegal input hour_str <' + hour_str + '> min_str <' + min_str + '>'); return null; } return date_ret; } |
Necesitamos algunas pequeñas utilidades
La función de utilidad común que simplemente comprueba si nuestro JavaScript existe utilizado por ambos OnUpdate(doc,meta), mostrado arriba, y el temporizador Devolución de llamada(doc), que se muestra más adelante. A continuación verifyFunctionExistsViaEval(curDoc, id) que recibe dos argumentos: un documento de control JSON y la CLAVE de dicho documento.
Esto nos permite saber inmediatamente, en el despliegue, si hubo un problema con una falta de coincidencia de nombres entre el documento de registro de control JSON y el nombre real de la función de lógica de negocio en el código JavaScript.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function verifyFunctionExistsViaEval(curDoc, id) { var result = false; try { // check for function if missing this is invalid return result result = eval("typeof " + curDoc.action + " === 'function';"); if (result === false) { if (curDoc.verbose.scheduler >= 1) log("Warn/Disable (No Action and No Re-Arm), because required 'action' of " + curDoc.action + "(doc) does not exist, id is", id); return result; } } catch (e) { log('verifyFunctionExistsViaEval Error exception:', e); } return result; } |
Tenga en cuenta que si se intenta ejecutar una función inexistente, el usuario final recibirá una advertencia en el registro de la aplicación. cron_impl_2func_651.log para corregir el problema.
2020-04-22T16:20:38.725-07:00 [INFO] "Advertir/Desactivar (Sin Acción y Sin Rearmar), porque la 'acción' requerida de doCronMyNewFunction(doc) no existe, id es" "recurring_event::1"
Esta corrección se puede hacer a través de una Pausa/Reanudar añadiendo la función y luego ajustando el documento de control con el id especificado o CLAVE (a través de un toggle activo a falso y luego verdadero) -o- ajustando el documento de control para apuntar a una función existente en su manejador.
A continuación, la utilidad toNumericFixed(número, precisión) sólo permite un bonito formato compacto de flotantes para nuestros mensajes de registro.
|
1 2 3 4 |
function toNumericFixed(number, precision) { var multi = Math.pow(10, precision); return Math.round((number * multi).toFixed(precision + 1)) / multi; } |
Por último, la utilidad toLocalISOTime(d) sólo permite un formato compacto de fechas para nuestros mensajes de registro.
|
1 2 3 4 |
function toLocalISOTime(d) { var tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in milliseconds return (new Date(d.getTime() - tzoffset)).toISOString().slice(0, -1); } |
Necesitamos una llamada de retorno del temporizador para ejecutar la lógica de usuario y volver a activar el temporizador
La última función JavaScript de "cron_impl_2func_651" es la llamada de retorno del temporizador, a la que se llama cuando se activa el temporizador programado. La función de devolución de llamada debe ser una función de nivel superior que reciba un único argumento, el contexto.
En este caso en nuestro manejador OnUpdate referenciamos una función JavaScript de Devolución de llamada(doc) con un contexto de doc (nuestro documento de control del planificador activo de type="recurring_event")
En la versión 6.6 podemos crear otro temporizador dentro de un temporizador pero para todas las versiones anteriores necesitaremos lanzar una mutación a una función "helper" (evitamos cuidadosamente la recursión infinita). En la 6.6 la función helper no es necesaria y la lógica se ha simplificado sustancialmente.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
function Callback(doc) { try { var fired_at = new Date(); // Check if further analysis is needed we only trigger on a recurring_event that is active if (doc.type !== "recurring_event") return; // doc must have 'action', 'dynamic {}', verbose {}, dynamic.state if (!doc.action || !doc.dynamic || !doc.verbose || !doc.dynamic.state) return; // process any doc.dynamic.state BUT pending if (doc.dynamic.state === "pending") return; // ================== // Check if still active // We make sure that in KV the 'doc' still exists and that it is still active if not just // return thus skipping the action and not Re-arming the timer. Note `travel-sample` is // aliased to the map 'cron_bkt var mid = doc.type + '::' + doc.id; // make our KEY var curDoc = null; try { // read the current version of doc from KV, e.g. curDoc curDoc = cron_bkt[mid]; } catch (e) {} // needed for pre 6.5, note pure 6.5+ deployment returns null sans exception var reason = null; if (!curDoc || curDoc === null) { reason = "cron document is missing"; } else if (!curDoc.active) { reason = "cron document has active = false"; } else if (!curDoc.dynamic.state || curDoc.dynamic.state !== doc.dynamic.state) { reason = "cron document wrong dynamic.state expected " + doc.dynamic.state; } else if (crc64(doc) !== crc64(curDoc)) { reason = "cron document changed"; } if (reason !== null) { if (!curDoc || curDoc === null || curDoc.verbose.scheduler >= 1) { log('Callback X ' + mid + " ignore/stop this timer's schedule because " + reason); } if (!curDoc || curDoc === null || curDoc.verbose.scheduler >= 4) { log('Callback Y ' + mid + ' timer doc', doc); log('Callback Z ' + mid + ' KV curDoc', curDoc); } return; } // ================== // Verify user routine exists and if so eval it // Assume curDoc.action contains something like "doCronActionA" and we have a function in // this handler like "doCronActionA(doc)". Below we use curDoc as the end user should be // able to alter the eval'd JavaScript function. We will execute two (2) evals. // First eval check the JavaScript function exists. The eval occurs in a common // utility function shared with Callback if (!verifyFunctionExistsViaEval(curDoc, mid)) { // curDoc.action did not exist, we have already logged the issue return; } // Second eval execute and process the user function we execute the defined function // with an argument of curDoc var beg_act = new Date(); var result = null; eval("result = " + curDoc.action + "(curDoc);"); var end_act = new Date(); var atime_ms = end_act.getTime() - beg_act.getTime(); if (curDoc.verbose.scheduler >= 2) log('Callback R ' + mid + ' action took ' + toNumericFixed((atime_ms / 1000), 3) + ' sec., returned ' + result); // ================== // Calculate next time and mutate the control document for our our helper function // which will create another mutation such that OnUpdate of this function will pick // it up and generate the timer (avoids the MB-38554 issue). var hour = curDoc.hour; var min = curDoc.min; var date_timer = getNextRecurringDate(hour, min); curDoc.dynamic.prev_delay = toNumericFixed(((fired_at.getTime() / 1000) - curDoc.dynamic.next_sched), 3); curDoc.dynamic.prev_sched = curDoc.dynamic.next_sched; curDoc.dynamic.prev_etime = Math.round(fired_at.getTime() / 1000); curDoc.dynamic.prev_atime = toNumericFixed((atime_ms / 1000), 3); curDoc.dynamic.state = "pending"; curDoc.dynamic.next_sched = Math.round(date_timer.getTime() / 1000); try { cron_bkt[mid] = curDoc; } catch (e) { log('Callback help: F ' + mid + ' FATAL could not update KV cron cycle ' + curDoc.action); return; } if (curDoc.verbose.scheduler >= 1) { log('Callback A ' + mid + ' gen mutation #1 to doc to force schedule rearm at ' + toLocalISOTime(date_timer)); } if (curDoc.verbose.scheduler >= 2) { log('Callback B ' + mid + ' sched ' + curDoc.dynamic.prev_sched + ', actual ' + curDoc.dynamic.prev_etime + ', delay ' + curDoc.dynamic.prev_delay + ', took ' + curDoc.dynamic.prev_atime); } if (curDoc.verbose.scheduler >= 3) { log('Callback C ' + mid + ' curDoc', curDoc); } } catch (e) { var mid = doc.type + '::' + doc.id; // make our KEY log('Callback E ' + mid + ' Error exception:', e); } } |
Necesitamos una función de ayuda para desencadenar una nueva mutación
Dado que antes de la versión 6.6 (que aún no se ha publicado) no se puede crear un temporizador desde la llamada de retorno de un temporizador en ejecución, necesitamos una segunda función de eventos (junto con "allow_interbucket_recursion":true) para activar una mutación de forma que podamos generar todos nuestros temporizadores en el punto de entrada OnUpdate(doc,meta) de la función de eventos principal. Hacemos esto de la siguiente manera:
- cron_impl_2func_651 OnUpdate(doc,meta) recibe una mutación, programa un temporizador
- cron_impl_2func_651 Después de una cantidad de tiempo cuando el temporizador madura el Devolución de llamada(doc) se ejecuta la rutina, primero ejecuta la acción de usuario deseada y luego crea una mutación #1 en el documento de control (que no es vista por la Función creadora para evitar la recursividad)
- cron_impl_2func_651_help OnUpdate(doc,meta) recibe una mutación, hace otra mutación #2 en el documento de control esto desencadena 1. anterior en un ciclo sin fin.
Nota, en la versión 6.6 de Couchbase no necesitamos una función de ayuda en absoluto porque se te permite crear un temporizador desde dentro de un temporizador en ejecución. Esto simplifica enormemente la lógica necesaria para hacer un cron sistema[2].
La única función JavaScript en "cron_impl_2func_651_help" OnUpdate(doc,meta) se muestra a continuación.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
function OnUpdate(doc, meta) { // fix for 6.5.X growing bucket ops if (meta.id.startsWith("fix_timer_scan_issue:")) upsertOneDocPerBucket(doc, meta); try { // Check that doc has desired values if (!doc.type || doc.type !== "recurring_event" || !doc.active || doc.active != true) return; // doc must have 'action', 'dynamic {}', verbose {}, dynamic.state if (!doc.action || !doc.dynamic || !doc.verbose || !doc.dynamic.state) return; // Only process state pending this will only exist for a 'breif' time if (doc.dynamic.state !== "pending") return; var mid = doc.type + '::' + doc.id; // make our KEY var newdoc = null; try { // read the current version of doc from KV, e.g. curDoc newdoc = cron_bkt[mid]; } catch (e) {} // needed for pre 6.5, note pure 6.5+ deployment returns null sans exception var reason = null; if (!newdoc || newdoc == null) { reason = "cron document is missing"; } else if (!newdoc.active) { reason = "cron document has active = false"; } else if (!newdoc.dynamic.state || newdoc.dynamic.state !== doc.dynamic.state) { reason = "cron document wrong dynamic.state expected " + doc.dynamic.state; } else if (crc64(doc) !== crc64(newdoc)) { reason = "cron document changed"; } if (reason != null) { if (!newdoc || newdoc == null || newdoc.verbose.scheduler >= 1) { log('OnUpdate help: X stopping schedule because ' + reason + ',', newdoc) return; } } newdoc.dynamic.state = "rearm"; // cron_bkt[mid] = newdoc; if (!tryBucketKvWriteWithLog('OnUpdate help: F', mid, newdoc)) { // Failed to write newdoc to cron_bkt[key] the error has been logged // and there is nothing more we can do. return; } if (newdoc.verbose.scheduler >= 1) { log('OnUpdate help: A ' + mid + ' mutation #2 to doc to force schedule rearm'); } if (newdoc.verbose.scheduler >= 3) { log('OnUpdate help: B ' + mid + ',', newdoc); } } catch (e) { log('OnUpdate help: E ' + meta.id + ', Error exception:', e); } } function tryBucketKvWriteWithLog(tag, key, doc) { var success = false; var tries = 0; while (tries < 10) { tries++; try { // critical that the below succeeds, because if it doesn't the cron cycle will break cron_bkt[key] = doc; success = true; break; } catch (e) { log(tag + ' ' + key + ' WARN failed to update KV tries ' + tries, e); } } if (!success) { log(tag + ' ' + +key + ' FATAL could not update KV cron cycle, tried ' + tries + ', stoping ' + curDoc.action); } return success; } |
La función helper necesita algunas utilidades
Estas utilidades proporcionan un arreglo para 6.5.X cubo creciente ops asegurándose de que se dispara un temporizador de eventos en cada vBucket en el momento oportuno.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
// FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops function upsertOneDocPerBucket(doc, meta) { var crcTable = makeCRC32Table(); // make one doc per bucket var isVerbose = 0; var isMacOS = false; // would be nice if this was an exposed constant in Eventing var numvbs = 1024; // default is linux/PC if (isMacOS) { numvbs = 64; } var beg = (new Date).getTime(); var result = getKeysToCoverAllPartitions(crcTable, "_tmp_vbs:", numvbs); for (var vb=0; vb<numvbs; vb++) { // brute force to fit a key prefix into a vBucket var tst = result[vb]; if (isVerbose > 1 || (isVerbose == 1) && (vb < 3 || vb > numvbs -4)) { log("KEY: " + tst); } else { if (vb == 5) console.log("\t*\n\t*\n\t*"); } // update the items to trigger a mutation for our PRIMARY fucntion cron_bkt[tst] = { "type": "_tmp_vbs", "vb": vb, "ts_millis": beg, "key": tst }; } var end = (new Date).getTime(); log("seeding one doc to each vBucket in primary_bucket alias (took " + (end - beg) + " mililis)"); } // FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops function showHex(n) { return n.toString(16); } // FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops function makeCRC32Table() { var crcTable = []; var c; for(var n =0; n < 256; n++){ c = n; for(var k =0; k < 8; k++){ c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } crcTable[n] = c; } return crcTable; } // FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops function crc32(crcTable,str) { var crc = 0 ^ (-1); for (var i = 0; i < str.length; i++ ) { crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF]; } return (crc ^ (-1)) >>> 0; } // FIXUP: ADDIN FUNCTON // fix for 6.5.X growing bucket ops function getKeysToCoverAllPartitions(crcTable,keyPrefix,partitionCount) { var result = []; var remaining = partitionCount; for (var i = 0; remaining > 0; i++) { var key = keyPrefix + i; var rv = (crc32(crcTable,key) >> 16) & 0x7fff; var actualPartition = rv & partitionCount - 1; if (!result[actualPartition] || result[actualPartition] === undefined) { result[actualPartition] = key; remaining--; } } return result; } |
Ahora vamos a desplegar las dos Funciones Eventing
Hemos revisado mucho código y el diseño del planificador inicial, ahora es el momento de ver cómo funciona todo junto.
Recuerde que en este ejemplo hay tres cubos muestra-viaje (un ejemplo de conjunto de datos por defecto), metadatos(el cubo de metadatos es un bloc de notas para Eventing y puede compartirse con otras funciones de Eventing) y, por último, la función crondata (que contiene nuestros cronogramas). La dirección viaje-muestra tiene un tamaño de 100 MB y los otros dos cubos metadatos y crondata deben tener un tamaño de 200 MB y existir ya según las instrucciones de "Requisitos previos".
- Verifique la configuración actual de su cubo accediendo a la página Consola Web de Couchbase > Cubos página:

Para desplegar la función Eventing "cron_impl_2func_651"puede seguir uno de estos dos métodos:
- Complejidad básica, Método #1 Descargar/Importar
- Complejidad media, Método #2 Añadir función manualmente, Cortar y pegar JavaScript
Método #1 Descargar/Importar
Importar la 1ª función "cron_impl_2func_651"
Descargue la primera función Eventing con todos los ajustes necesarios, haga clic con el botón derecho del ratón en el siguiente enlace y seleccione Guardar enlace como para descargar el archivo cron_impl_2func_651.json en tu sistema de archivos local.
Desde el Consola Web de Couchbase > Eventos haga clic en IMPORTARnavega hasta el archivo cron_impl_2func_651.jsonselecciónelo y ábralo. La página AÑADIR FUNCIÓN aparece el cuadro de diálogo.
En el AÑADIR FUNCIÓN para cada uno de los elementos de la función, facilite la siguiente información. Tenga en cuenta el archivo JSON cron_impl_2func_651.json preconfigurará todos los ajustes correctamente para este ejemplo:
- Para el Cubo de origen y compruebe que está en crondata.
- Para el Cubo de metadatos y compruebe que está en metadatos.
- Compruebe que cron_impl_2func_651 es el nombre de la función que está creando en el campo Nombre de la función cuadro de texto.
- [Paso opcional] Introduzca el texto Un programador cron parte 1en el Descripción cuadro de texto.
- Para el Ajustes utilice los valores por defecto.
- Para el Fijaciones verifique que existen dos enlaces.
- Para la vinculación, el "alias de cubo", especifica cron_bkt como "nombre alias" del cubo, y seleccione
crondata como cubo asociado, y el modo debe ser "lectura y escritura". - Para la vinculación, el "alias de cubo", especifica ts_bkt como "nombre alias" del cubo, y seleccione
viaje-muestra como cubo asociado, y el modo debe ser "lectura y escritura". - La configuración en el cuadro de diálogo debe ser como la siguiente:

- Después de verificar toda la información requerida en el cuadro de diálogo AÑADIR FUNCIÓN, haga clic en Siguiente: Añadir Código. Aparecerá la página cron_impl_2func_651 (con el código JavaScript precargado).

- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos ficha.
Importar la 2ª Función "cron_impl_2func_651_help"
Descargue la segunda función Eventing con todos los ajustes necesarios, haga clic con el botón derecho del ratón en el siguiente enlace y seleccione Guardar enlace como para descargar el archivo cron_impl_2func_651_help.json en tu sistema de archivos local.
Desde el Consola Web de Couchbase > Eventos haga clic en IMPORTARnavega hasta el archivo cron_impl_2func_651_help.jsonselecciónelo y ábralo. La página AÑADIR FUNCIÓN aparece el cuadro de diálogo.
En el AÑADIR FUNCIÓN para cada uno de los elementos de la función, facilite la siguiente información. Tenga en cuenta el archivo JSON cron_impl_2func_651_help.json preconfigurará todos los ajustes correctamente para este ejemplo:
- Para el Cubo de origen y compruebe que está en crondata.
- Para el Cubo de metadatos y compruebe que está en metadatos.
- Compruebe que cron_impl_2func_651_help es el nombre de la función que está creando en el campo Nombre de la función cuadro de texto.
- [Paso opcional] Introduzca el texto Un ayudante cron como programador parte 1en el Descripción cuadro de texto.
- Para el Ajustes utilice los valores por defecto.
- Para el Fijaciones verifique que sólo existe un enlace.
- Para la vinculación, el "alias de cubo", especifica cron_bkt como "nombre alias" del cubo, y seleccione
crondata como cubo asociado, y el modo debe ser "lectura y escritura". - La configuración en el cuadro de diálogo debe ser como la siguiente:

- Después de verificar toda la información requerida en el cuadro de diálogo AÑADIR FUNCIÓN, haga clic en Siguiente: Añadir Código. Aparecerá la página cron_impl_2func_651_help (con el código JavaScript precargado).

- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos ficha.
Método #2 Añadir función manualmente, cortar y pegar JavaScript
Crear manualmente "cron_impl_2func_651"
Para añadir la primera función de Eventing desde Consola Web de Couchbase > Eventos haga clic en AÑADIR FUNCIÓNpara añadir una nueva función. La dirección AÑADIR FUNCIÓN aparece el cuadro de diálogo.
En el AÑADIR FUNCIÓN para cada elemento de la función, facilite la siguiente información:
- Para el Cubo de origen seleccione crondata.
- Para el Cubo de metadatos seleccione metadatos.
- Visite cron_impl_2func_651 es el nombre de la función que está creando en el campo Nombre de la función cuadro de texto.
- [Paso opcional] Introduzca el texto Un programador cron parte 1en el Descripción cuadro de texto.
- Para el Ajustes utilice los valores por defecto.
- Para el Fijaciones crear dos enlaces:
- Para la vinculación, el "alias de cubo", especifica cron_bkt como "nombre alias" del cubo, y seleccione
crondata como cubo asociado, y el modo debe ser "lectura y escritura". - Para la vinculación, el "alias de cubo", especifica ts_bkt como "nombre alias" del cubo, y seleccione
viaje-muestra como cubo asociado, y el modo debe ser "lectura y escritura". - Una vez configurados los ajustes, el cuadro de diálogo debería tener este aspecto:

- Tras facilitar toda la información requerida en el AÑADIR FUNCIÓN pulse Siguiente Añadir código. En cron_impl_2func_651 aparece el cuadro de diálogo. La página cron_impl_2func_651 contiene inicialmente un bloque de código. Usted sustituirá su cron_impl_2func_651 en este bloque.

- Copie el siguiente código fuente JavaScript de Eventing Function (618 líneas) y péguelo en el bloque de código del marcador de posición de cron_impl_2func_651
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618/*Function "cron_impl_2func_651" also requires "cron_impl_2func_651_help"Create a basic cron system using Eventing allows a recurring function to execute activity at aspecified time every day, hour, min, 30 sec., and 15 sec. We use a bucket called 'crondata'aliased to 'cron_bkt' which can hold one or more control documents of type = "recurring_event".The following uses of timers do not work reliably in Couchbase versions 6.5 and 6.5.1a) scheduling an Eventing timer within a timer's callbackb) overwriting an existing timer by idIn addition the ability to cancel a timer does not exist in Couchbase versions 6.5 and 6.5.1For this example, we supply one real user function that builds a recurring 'static' cache documentfrom bucket `travel-sample` via an N1QL query and save the result back to `travel-sample` viathe alais 'ts_bkt'. This JavaScript function is doCronActionA(), we also provide two placeholdersdoCronActionB() and doCronActionC() for additional experimentation.Test Doc:{"type":"recurring_event", // The KEY will be <<type>>::<<id>>"id":1, //"hour":14, // The hour of the day 0-23, *, *2X, *4X to trigger"min":54, // The minute in the hour 0-59, *, *2X, *4X to trigger"action":"doCronActionA", // What function to run on the trigger"active":false, // Flag to arm or disable this schedule"verbose" : {"user_func":2, // Logging level for the action logic : 0=none, etc. etc."scheduler":3 // Logging level for the cron logic : 0=none, etc. etc.},"dynamic" : {"state":"arm", // States "arm"|"rearm"|"pending" if any value but "pending" start a schedule"next_sched": 0, // Number of seconds since epoch to next desired schedule"prev_sched": 0, // Number of seconds since epoch for previous schedule"prev_etime": 0, // Number of seconds since epoch for previous schedule actual exec time"prev_delay": 0, // Number of seconds that the timer was delayed from the schedule"prev_atime": 0 // Number of seconds taken by the user 'action'}}INSERT INTO `crondata` (KEY,VALUE) VALUES ("recurring_event::1",{"type":"recurring_event","id":1,"hour":14,"min":54,"action":"doCronActionA","verbose" : {"user_func":2,"scheduler":3},"active":false,"dynamic" : {"state": "arm","next_sched": 0,"prev_sched": 0,"prev_etime": 0,"prev_delay": 0,"prev_atime": 0}});Note, you can omit verbose{} and dynamic{} as they will be auto-created by this main EventingFunction "cron_impl_2func_651". If verbose{} is missing the logging levels will default toverbose" : { "user_func":1, "scheduler":1 }INSERT INTO `crondata` (KEY,VALUE) VALUES ("recurring_event::1",{"type":"recurring_event","id":1,"hour":14,"min":54,"action":"doCronActionA","active":false});N1QL : Make an index to query data without specifying keysCREATE primary INDEX on `crondata` ;N1QL : Verify or inspect settings in scheduleSELECT * FROM `crondata` WHERE type="recurring_event";N1QL : Arm or set activeUPDATE `crondata` SET active = true WHERE type="recurring_event" AND id=1 ;N1QL : Disarm or set inactiveUPDATE `crondata` SET active = false WHERE type="recurring_event" AND id=1 ;N1QL : Adjust time of triggerUPDATE `crondata` SET hour = 11, min = 30 WHERE type="recurring_event" AND id=1 ;N1QL : Adjust loggingUPDATE `crondata` SET verbose.user_func = 1, verbose.scheduler = 0 WHERE type="recurring_event" AND id=1 ;N1QL : Delete the scheduleDELETE FROM `crondata` WHERE type="recurring_event" AND id=1 ;The action field is important it 'should' exist in this Eventing Function note it could be anyJavaScript name e.g. MyFunc and you must implement like the example doCronActionA(doc) wheredoc will be the currently active item of type = 'recurring_event' read from the alias bucket‘cron_bkt’ when the timer is fired. The action JavaScript function should return either trueor false used for logging purposes. If the action does not exist it is an error and a warningis logged and the timer is disabled.In Couchbase version 6.5+ to add a new cron like daily function just pause the active handlerinsert your new function doCronActionB(doc) {...} then Resume the eventing handler. The nicething is if a timer was to be fired will the function was paused it will NOT be lost, when youresume the function it will be processed at the next available time slot.Any change to a control structure will create a new recurring schedule or timer and cancel thecurrent previous schedule this includes changing the verbosity level. The previous timer willcontinue to run however when executed it will do a Checksum on the current control structurefrom KV against it’s passed context and if different the Callback will ignore the old schedule.This logic could be altered to process immediately if the schedule has expired search for thestring "OnUpdate U" in the code below.*/// ==================/* BEG USER FUNCTIONS TO RUN ONCE A DAY, HOUR, OR MINUTE - ANYTHING YOU WANT BELOW */function doCronActionA(doc) {try {// Check that doc has desired valuesif (!doc.type || doc.type !== "recurring_event" || !doc.active || doc.active !== true) return;if (doc.verbose.user_func >= 1)log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);// this is a 6.5 N1QL query (feature not available in GA prior to 6.5)// Create an embedded N1QL iterator by issuing a SELECT statement to get the// counts of airlines by country. Make a new document and write it out to KV// We will use the iterator to create a KV document representing the results of a// HARD lengthy embedded N1QL query and write it back to KV, the idea is to keep// a calculation up to date once a day such that it that can be read 'quickly'// by other Eventing Functions, other Couchbase services or SDKs.// Consider if we had 1 million docs in a minute do we really want to use N1QL// to recalculate something that is almost static for all 1 million documents, of// course not, so we make an intermediate value that can be read into Eventing// and used via a single 'light weight' KV read.var q_iter = SELECT country,count( * ) cntFROM `travel-sample`WHERE `type` = 'airline'GROUP BY country;// loop through the result set and update the map 'accumulate'var accumulate = {};var idx = 0;for (var val of q_iter) {if (doc.verbose.user_func >= 2)log(doc.action + ' N1QL idx ' + idx + ', country ' + val.country + " cnt " + val.cnt);accumulate[val.country] = val.cnt;idx++;}// close out embedded N1QL iteratorq_iter.close();// Now let’s make a cached KV document representing a HARD length embedded N1QL// query and write it back to KV, we need a KEY and a type and id and then we// upsert it into the `travel-sample` bucket.var cachedoc = {};cachedoc.type = "cron_cache";cachedoc.id = "airlines_by_country";cachedoc.date = new Date();cachedoc.data = accumulate;var ckey = cachedoc.type + '::' + cachedoc.id;ts_bkt[ckey] = cachedoc;if (doc.verbose.user_func >= 2) {log(doc.action + ' upsert to KV with KEY ' + ckey + ' cachedoc ', cachedoc);}} catch (e) {log(doc.action + ' Error exception:', e);return false;}return true;}function doCronActionB(doc) {try {// check that doc has desired valuesif (doc.type !== "recurring_event" || doc.active !== true) return;if (doc.verbose.user_func >= 1)log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);// YOUR LOGIC HERE} catch (e) {log(doc.action + ' Error exception:', e);return false;}return true;}function doCronActionC(doc) {try {// check that doc has desired valuesif (doc.type !== "recurring_event" || doc.active !== true) return;if (doc.verbose.user_func >= 1)log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);// YOUR LOGIC HERE} catch (e) {log(doc.action + ' Error exception:', e);return false;}return true;}/* END USER FUNCTIONS TO RUN ONCE A DAY, HOUR, OR MINUTE - ANYTHING YOU WANT ABOVE */// ==================// FIXUP: ADDIN FUNCTONfunction noopTimer(context) {// fix for 6.5.X growing bucket opstry {if (context.type === "_tmp_vbs" && context.vb === 0) {// log("noopTimer timers firing, printing only for vBucket 0");}} catch (e) {log("OnUpdate Exception in callback noopTimer:", e);}}// FIXUP: ADDIN FUNCTONfunction rearmTimer(context) {// fix for 6.5.X growing bucket opstry {if (context.type === "_tmp_vbs" && context.vb === 0) {// Update/touch all docs in the helper_bucket the helper function will then// mutate all 1024 of type == vbs_seed (64 on MacOS) to create a recuring cycle.// log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context);// generate a mutation to re-arm the HELPER function: fix_scan_issue// which will in turn make new mutations for this Functionvar cur = cron_bkt[context.key];if (cur && cur.ts_millis === context.ts_millis) {// log("rearmTimer update fix_timer_scan_issue::1 in helper_bucket alias only for vBucket 0");var now = new Date();cron_bkt["fix_timer_scan_issue::1"] = { "last_update": now };} else {// NOOP we had multiple timer cycles, just let this one quietly stop.}}} catch (e) {log("OnUpdate Exception in callback rearmTimer:", e);}}// FIXUP: ADDIN FUNCTONfunction genNoopTimers(doc, meta, seconds) {// fix for 6.5.X growing bucket opstry {// redundant but play it safeif (doc.type === "_tmp_vbs") {// Since we are using an different function a timer on all our vBuckets do immeadiately (can take up to 15 seconds)// 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.createTimer(noopTimer, new Date(), null, doc);if (doc.vb === 0) {// Update/touch all docs in the helper_bucket the helper function will then// mutate all 1024 of type == vbs_seed (64 on MacOS) to create a recuring cycle.// log("noopTimer timer fired all 1024 vBuckets, logging only vb 0", context);// generate a mutation to re-arm the HELPER function: fix_scan_issue// which will in turn make new mutations for this Function// log("genNoopTimers make timer to rearm fix_timer_scan_issue::1");createTimer(rearmTimer, new Date(new Date().getTime() + seconds * 1000), null, doc);}}} catch (e) {log("OnUpdate Exception in genNoopTimers:", e);}}function OnUpdate(doc, meta) {// fix for 6.5.X growing bucket opsif (doc.type === "_tmp_vbs") genNoopTimers(doc, meta, 30);if (!cron_bkt["fix_timer_scan_issue::1"]) {cron_bkt["fix_timer_scan_issue::1"] = {};}try {// Check if further analysis is needed we only trigger on an active recurring_eventif (doc.type !== "recurring_event" || doc.active !== true) return;var update_doc = false;if (!doc.dynamic) {// Add if missing doc.dynamic with defaultsdoc.dynamic = {"state": "arm","next_sched": 0,"prev_sched": 0,"prev_etime": 0,"prev_delay": 0,"prev_atime": 0};// we need to update the document once we have the next scheduleupdate_doc = true;}if (!doc.verbose) {// Add if missing doc.dynamic with defaultsdoc.verbose = {"user_func": 1,"scheduler": 1};// we need to update the document once we have the next scheduleupdate_doc = true;}// Do not process dynamic.state pendingif (!doc.dynamic || !doc.dynamic.state || doc.dynamic.state === "pending") return;var mid = doc.type + "::" + doc.id; // this is the same as meta.id or the KEYvar hour = doc.hour;var min = doc.min;// Do an eval check the JavaScript function exists. The eval occurs in a common// utility function shared with RecurringCallbackif (!verifyFunctionExistsViaEval(doc, mid)) {// doc.action did not exist, we have already logged the issuereturn;}// Get the next valid execution timevar date_timer = getNextRecurringDate(hour, min);var next_sched = Math.round(date_timer.getTime() / 1000);if (!update_doc && next_sched !== doc.dynamic.next_sched) {// the next_sched should be the same as the setting from the helper application, however// if we undeploy/deploy or pause/resume we might haver to reschedule to the next time slotlog('OnUpdate U ' + mid + ' calculated next_sched !== doc.dynamic.next_sched, delta ' +(next_sched - doc.dynamic.next_sched) + ', reschedule');update_doc = true;}if (update_doc) {// this mutation is recursive and will be suppressed, we ensure we have a dynamic structuredoc.dynamic.next_sched = next_sched;// rather then the call a function, to trap and retry if there is a resource issue// cron_bkt[mid] = doc;if (!tryBucketKvWriteWithLog('OnUpdate F', mid, doc)) {// Failed to write doc to cron_bkt[key] the error has been logged// and there is nothing more we can do.return;}}// Schedule an Eventing timervar timer_id = createTimer(Callback, date_timer, null, doc);if (doc.verbose.scheduler >= 1) {log('OnUpdate A ' + mid + ' rcv mutation (initial or rearm) schedule timer at ' +toLocalISOTime(date_timer));}if (doc.verbose.scheduler >= 2) {log('OnUpdate B ' + mid + ' recurring timer was created, timer_id ' + timer_id);}} catch (e) {log('OnUpdate E ' + meta.id + ', Error exception:', e);}}function getNextRecurringDate(hour_str, min_str) {// Note Javascript Dates are in millisecondsvar date_now = new Date();var date_ret = new Date();var hour;var min;try {hour = parseInt(hour_str);} catch (e) {}try {min = parseInt(min_str);} catch (e) {}// Note, this is only a simplistic partial 'crontab' syntax with some slight extensions// it allows once a day, once an hour, once a minute. It also contains some non-standard// syntax to provide the ability to execute twice a minute or four times a minute.if (hour_str === '*4X' && min_str === '*4X') {// once every 15 seconds or four times a minutedate_ret.setMilliseconds(0);date_ret.setSeconds(15);while (date_ret.getTime() < date_now.getTime()) {date_ret.setSeconds(date_ret.getSeconds() + 15);}return date_ret;} elseif (hour_str === '*2X' && min_str === '*2X') {// once every 30 seconds or twice a minutedate_ret.setMilliseconds(0);date_ret.setSeconds(30);while (date_ret.getTime() < date_now.getTime()) {date_ret.setSeconds(date_ret.getSeconds() + 30);}return date_ret;} elseif (hour_str === '*' && min_str === '*') {// once a minutedate_ret.setMilliseconds(0);date_ret.setSeconds(0);date_ret.setMinutes(date_ret.getMinutes() + 1);} elseif (hour_str !== '*' && isNaN(hour) === false && min_str === '*') {// once a minute only for a given hourdate_ret.setMilliseconds(0);date_ret.setSeconds(0);date_ret.setMinutes(date_ret.getMinutes() + 1);if (date_ret.getTime() < date_now.getTime()) {date_ret.setHours(hour);}if (date_ret.getTime() > date_now.getTime()) {date_ret.setDate(date_ret.getDate() + 1);date_ret.setSeconds(0);date_ret.setMinutes(0);date_ret.setHours(hour);}} elseif (hour_str === '*' && min_str !== '*' && isNaN(min) === false) {// once a hour at a given minutedate_ret.setMilliseconds(0);date_ret.setSeconds(0);date_ret.setMinutes(min);// schedule for next hourdate_ret.setHours(date_ret.getHours() + 1);} elseif (isNaN(hour) === false && isNaN(min) === false) {// once a day for a given hour and a given minutedate_ret.setMilliseconds(0);date_ret.setSeconds(0);date_ret.setMinutes(min);date_ret.setHours(hour);if (date_ret.getTime() < date_now.getTime()) {// schedule for tomorrowdate_ret.setDate(date_ret.getDate() + 1);}} else {log('getNextRecurringDate illegal input hour_str <' +hour_str + '> min_str <' + min_str + '>');throw new Error('getNextRecurringDate illegal input hour_str <' +hour_str + '> min_str <' + min_str + '>');return null;}return date_ret;}function verifyFunctionExistsViaEval(curDoc, id) {var result = false;try {// check for function if missing this is invalid return resultresult = eval("typeof " + curDoc.action + " === 'function';");if (result === false) {if (curDoc.verbose.scheduler >= 1)log("Warn/Disable (No Action and No Re-Arm), because required 'action' of " +curDoc.action + "(doc) does not exist, id is", id);return result;}} catch (e) {log('verifyFunctionExistsViaEval Error exception:', e);}return result;}function toNumericFixed(number, precision) {var multi = Math.pow(10, precision);return Math.round((number * multi).toFixed(precision + 1)) / multi;}function toLocalISOTime(d) {var tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in millisecondsreturn (new Date(d.getTime() - tzoffset)).toISOString().slice(0, -1);}function tryBucketKvWriteWithLog(tag, key, doc) {var success = false;var tries = 0;while (tries < 10) {tries++;try {// critical that the below succeeds, because if it doesn't the cron cycle will breakcron_bkt[key] = doc;success = true;break;} catch (e) {log(tag + ' ' + key + ' WARN failed to update KV tries ' + tries, e);}}if (!success) {log(tag + ' ' + +key + ' FATAL could not update KV cron cycle, tried ' + tries + ', stoping ' + curDoc.action);}return success;}function Callback(doc) {try {var fired_at = new Date();// Check if further analysis is needed we only trigger on a recurring_event that is activeif (doc.type !== "recurring_event") return;// doc must have 'action', 'dynamic {}', verbose {}, dynamic.stateif (!doc.action || !doc.dynamic || !doc.verbose || !doc.dynamic.state) return;// process any doc.dynamic.state BUT pendingif (doc.dynamic.state === "pending") return;// ==================// Check if still active// We make sure that in KV the 'doc' still exists and that it is still active if not just// return thus skipping the action and not Re-arming the timer. Note `travel-sample` is// aliased to the map 'cron_bktvar mid = doc.type + '::' + doc.id; // make our KEYvar curDoc = null;try {// read the current version of doc from KV, e.g. curDoccurDoc = cron_bkt[mid];} catch (e) {} // needed for pre 6.5, note pure 6.5+ deployment returns null sans exceptionvar reason = null;if (!curDoc || curDoc === null) {reason = "cron document is missing";} elseif (!curDoc.active) {reason = "cron document has active = false";} elseif (!curDoc.dynamic.state || curDoc.dynamic.state !== doc.dynamic.state) {reason = "cron document wrong dynamic.state expected " + doc.dynamic.state;} elseif (crc64(doc) !== crc64(curDoc)) {reason = "cron document changed";}if (reason !== null) {if (!curDoc || curDoc === null || curDoc.verbose.scheduler >= 1) {log('Callback X ' + mid + " ignore/stop this timer's schedule because " + reason);}if (!curDoc || curDoc === null || curDoc.verbose.scheduler >= 4) {log('Callback Y ' + mid + ' timer doc', doc);log('Callback Z ' + mid + ' KV curDoc', curDoc);}return;}// ==================// Verify user routine exists and if so eval it// Assume curDoc.action contains something like "doCronActionA" and we have a function in// this handler like "doCronActionA(doc)". Below we use curDoc as the end user should be// able to alter the eval'd JavaScript function. We will execute two (2) evals.// First eval check the JavaScript function exists. The eval occurs in a common// utility function shared with RecurringCallbackif (!verifyFunctionExistsViaEval(curDoc, mid)) {// curDoc.action did not exist, we have already logged the issuereturn;}// Second eval execute and process the user function we execute the defined function// with an argument of curDocvar beg_act = new Date();var result = null;eval("result = " + curDoc.action + "(curDoc);");var end_act = new Date();var atime_ms = end_act.getTime() - beg_act.getTime();if (curDoc.verbose.scheduler >= 2)log('Callback R ' + mid + ' action took ' + toNumericFixed((atime_ms / 1000), 3) +' sec., returned ' + result);// ==================// Calculate next time and mutate the control document for our our helper function// which will create another mutation such that OnUpdate of this function will pick// it up and generate the timer (avoids the MB-38554 issue).var hour = curDoc.hour;var min = curDoc.min;var date_timer = getNextRecurringDate(hour, min);curDoc.dynamic.prev_delay =toNumericFixed(((fired_at.getTime() / 1000) - curDoc.dynamic.next_sched), 3);curDoc.dynamic.prev_sched = curDoc.dynamic.next_sched;curDoc.dynamic.prev_etime = Math.round(fired_at.getTime() / 1000);curDoc.dynamic.prev_atime = toNumericFixed((atime_ms / 1000), 3);curDoc.dynamic.state = "pending";curDoc.dynamic.next_sched = Math.round(date_timer.getTime() / 1000);// rather then the call a function, to trap and retry if there is a resource issue// cron_bkt[mid] = curDoc;if (!tryBucketKvWriteWithLog('Callback F', mid, curDoc)) {// Failed to write curDoc to cron_bkt[key] the error has been logged// and there is nothing more we can do.return;}if (curDoc.verbose.scheduler >= 1) {log('Callback A ' + mid + ' gen mutation #1 to doc to force schedule rearm at ' +toLocalISOTime(date_timer));}if (curDoc.verbose.scheduler >= 2) {log('Callback B ' + mid + ' sched ' + curDoc.dynamic.prev_sched +', actual ' + curDoc.dynamic.prev_etime +', delay ' + curDoc.dynamic.prev_delay +', took ' + curDoc.dynamic.prev_atime);}if (curDoc.verbose.scheduler >= 3) {log('Callback C ' + mid + ' curDoc', curDoc);}} catch (e) {var mid = doc.type + '::' + doc.id; // make our KEYlog('Callback E ' + mid + ' Error exception:', e);}}
- Después de pegar, aparece la pantalla que se muestra a continuación:

- Haga clic en Guardar.
- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos
Crear manualmente "cron_impl_2func_651_help"
Para añadir la segunda función de Eventing desde Consola Web de Couchbase > Eventos haga clic en AÑADIR FUNCIÓNpara añadir una nueva función. La dirección AÑADIR FUNCIÓN aparece el cuadro de diálogo.
En el AÑADIR FUNCIÓN para cada elemento de la función, facilite la siguiente información:
- Para el Cubo de origen seleccione crondata.
- Para el Cubo de metadatos seleccione metadatos.
- Visite cron_impl_2func_651_help es el nombre de la función que está creando en el campo Nombre de la función cuadro de texto.
- [Paso opcional] Introduzca el texto Un ayudante cron como programador parte 1en el Descripción cuadro de texto.
- Para el Ajustes utilice los valores por defecto.
- Para el Fijaciones crear un enlace:
- Para la vinculación, el "alias de cubo", especifica cron_bkt como "nombre alias" del cubo, y seleccione
crondata como cubo asociado, y el modo debe ser "lectura y escritura". - Una vez configurados los ajustes, el cuadro de diálogo debería tener este aspecto:

- Tras facilitar toda la información requerida en el AÑADIR FUNCIÓN pulse Siguiente Añadir código. En cron_impl_2func_651_help aparece el cuadro de diálogo. La página cron_impl_2func_651_help contiene inicialmente un bloque de código. Usted sustituirá su cron_impl_2func_651_help en este bloque.

- Copie el siguiente código fuente JavaScript de Eventing Function (187 líneas) y péguelo en el bloque de código del marcador de posición de cron_impl_2func_651_help
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187/*Function "cron_impl_2func_651_help" also requires "cron_impl_2func_651"Test Doc:{"type":"recurring_event", // The KEY will be <<type>>::<<id>>"id":1, //"hour":14, // The hour of the day 0-23, *, *2X, *4X to trigger"min":54, // The minute in the hour 0-59, *, *2X, *4X to trigger"action":"doCronActionA", // What function to run on the trigger"active":false, // Flag to arm or disable this schedule"verbose" : {"user_func":2, // Logging level for the action logic : 0=none, etc. etc."scheduler":3 // Logging level for the cron logic : 0=none, etc. etc.},"dynamic" : {"state":"arm", // States "arm"|"rearm"|"pending" if any value but "pending" start a schedule"next_sched": 0, // Number of seconds since epoch to next desired schedule"prev_sched": 0, // Number of seconds since epoch for previous schedule"prev_etime": 0, // Number of seconds since epoch for previous schedule actual exec time"prev_delay": 0, // Number of seconds that the timer was delayed from the schedule"prev_atime": 0 // Number of seconds taken by the user 'action'}}Note, you can omit verbose{} and dynamic{} as they will be autocreated by the main EventingFunction "cron_impl_2func_651". If verbose{} is missing the logging levels will default toverbose" : { "user_func":1, "scheduler":1 }*/function tryBucketKvWriteWithLog(tag, key, doc) {var success = false;var tries = 0;while (tries < 10) {tries++;try {// critical that the below succeeds, because if it doesn't the cron cycle will breakcron_bkt[key] = doc;success = true;break;} catch (e) {log(tag + ' ' + key + ' WARN failed to update KV tries ' + tries, e);}}if (!success) {log(tag + ' ' + +key + ' FATAL could not update KV cron cycle, tried ' + tries + ', stoping ' + curDoc.action);}return success;}function OnUpdate(doc, meta) {// fix for 6.5.X growing bucket opsif (meta.id.startsWith("fix_timer_scan_issue:")) upsertOneDocPerBucket(doc, meta);try {// Check that doc has desired valuesif (!doc.type || doc.type !== "recurring_event" || !doc.active || doc.active != true) return;// doc must have 'action', 'dynamic {}', verbose {}, dynamic.stateif (!doc.action || !doc.dynamic || !doc.verbose || !doc.dynamic.state) return;// Only process state pending this will only exist for a 'breif' timeif (doc.dynamic.state !== "pending") return;var mid = doc.type + '::' + doc.id; // make our KEYvar newdoc = null;try {// read the current version of doc from KV, e.g. curDocnewdoc = cron_bkt[mid];} catch (e) {} // needed for pre 6.5, note pure 6.5+ deployment returns null sans exceptionvar reason = null;if (!newdoc || newdoc == null) {reason = "cron document is missing";} elseif (!newdoc.active) {reason = "cron document has active = false";} elseif (!newdoc.dynamic.state || newdoc.dynamic.state !== doc.dynamic.state) {reason = "cron document wrong dynamic.state expected " + doc.dynamic.state;} elseif (crc64(doc) !== crc64(newdoc)) {reason = "cron document changed";}if (reason != null) {if (!newdoc || newdoc == null || newdoc.verbose.scheduler >= 1) {log('OnUpdate help: X stopping schedule because ' + reason + ',', newdoc)return;}}newdoc.dynamic.state = "rearm";// cron_bkt[mid] = newdoc;if (!tryBucketKvWriteWithLog('OnUpdate help: F', mid, newdoc)) {// Failed to write newdoc to cron_bkt[key] the error has been logged// and there is nothing more we can do.return;}if (newdoc.verbose.scheduler >= 1) {log('OnUpdate help: A ' + mid + ' mutation #2 to doc to force schedule rearm');}if (newdoc.verbose.scheduler >= 3) {log('OnUpdate help: B ' + mid + ',', newdoc);}} catch (e) {log('OnUpdate help: E ' + meta.id + ', Error exception:', e);}}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunction upsertOneDocPerBucket(doc, meta) {var crcTable = makeCRC32Table();// make one doc per bucketvar isVerbose = 0;var isMacOS = false; // would be nice if this was an exposed constant in Eventingvar numvbs = 1024; // default is linux/PCif (isMacOS) {numvbs = 64;}var beg = (new Date).getTime();var result = getKeysToCoverAllPartitions(crcTable, "_tmp_vbs:", numvbs);for (var vb=0; vb<numvbs; vb++) {// brute force to fit a key prefix into a vBucketvar tst = result[vb];if (isVerbose > 1 || (isVerbose == 1) && (vb < 3 || vb > numvbs -4)) {log("KEY: " + tst);} else {if (vb == 5) console.log("\t*\n\t*\n\t*");}// update the items to trigger a mutation for our PRIMARY fucntioncron_bkt[tst] = { "type": "_tmp_vbs", "vb": vb, "ts_millis": beg, "key": tst };}var end = (new Date).getTime();log("seeding one doc to each vBucket in primary_bucket alias (took " + (end - beg) + " mililis)");}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunction showHex(n) {return n.toString(16);}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunction makeCRC32Table() {var crcTable = [];var c;for(var n =0; n < 256; n++){c = n;for(var k =0; k < 8; k++){c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));}crcTable[n] = c;}return crcTable;}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunction crc32(crcTable,str) {var crc = 0 ^ (-1);for (var i = 0; i < str.length; i++ ) {crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];}return (crc ^ (-1)) >>> 0;}// FIXUP: ADDIN FUNCTON// fix for 6.5.X growing bucket opsfunction getKeysToCoverAllPartitions(crcTable,keyPrefix,partitionCount) {var result = [];var remaining = partitionCount;for (var i = 0; remaining > 0; i++) {var key = keyPrefix + i;var rv = (crc32(crcTable,key) >> 16) & 0x7fff;var actualPartition = rv & partitionCount - 1;if (!result[actualPartition] || result[actualPartition] === undefined) {result[actualPartition] = key;remaining--;}}return result;}
- Después de pegar, aparece la pantalla que se muestra a continuación:

- Haga clic en Guardar.
- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos
Despliegue de las dos funciones
Ahora estamos listos para iniciar las funciones de Eventing. Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651_help para expandir y exponer los controles de Función.

- Haga clic en Despliegue.
- En el Confirmar función de despliegue seleccione "A partir de ahora" de la opción Límite de alimentación.

Empecemos con la otra función de Eventing. Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651 para expandir y exponer los controles de Función.

- Haga clic en Despliegue.
- En el Confirmar función de despliegue seleccione "A partir de ahora" de la opción Límite de alimentación.

Configurar un cron tarea a ejecutar cuatro (4) veces por minuto
En este punto nuestra función Eventing está esperando una mutación específicamente cualquier documento de type="recurring_event" que tenga un campo active=true.
Desde el Consola Web Couchbase > Consulta utilizaremos N1QL para crear una nueva tarea programada en el cubo "muestra-viaje":
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
123456789101112131415INSERT INTO `crondata` (KEY,VALUE) VALUES ("recurring_event::1",{"type": "recurring_event","id":1,"hour":"*","min":"0","action": "doCronActionA","verbose": {"user_func": 2,"scheduler": 3},"active": false});
- Haga clic en Ejecute

Activar nuestro primer cron tarea
El documento de control que hicimos anteriormente no se activó porque especificamos "active":false, además la programación anterior sólo se ejecutará una vez por hora, pero queremos probar las cosas y ver que funcionen en un futuro próximo.
En primer lugar necesitamos un índice para poder manipular nuestros documentos de control en N1QL esto sólo tiene que hacerse una vez
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
1CREATE primary INDEX on `crondata` ;
Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultasAhora activaremos la tarea, pero ajustaremos el horario de repetición a cada 15 segundos para ver exactamente cómo se comporta el sistema. Para ello modificaremos el documento de control con la CLAVE recurring_event::1
-
123UPDATE `crondata`SET active=TRUE, hour="*4X", min="*4X"WHERE type="recurring_event" AND id=1 ;
- Haga clic en Ejecute

Utilizamos la sintaxis no estándar de ="*4X" para programar un elemento recurrente cuatro veces por minuto podemos ver nuestra función de trabajo doCronActionA ejecutándose y también la lógica de mantenimiento para programar la función a través de sentencias de registro porque hemos establecido verbose=3.
El programador se ejecuta ahora cuatro veces por minuto. Puede ver la actividad en las estadísticas y en los archivos de registro de la aplicación para las funciones de eventos. cron_impl_2func_651 y cron_impl_2func_651_help.
- Acceder a la Consola Web de Couchbase > Panel de control verás una ráfaga de actividad cada 15 segundos:

- Acceder a la Consola Web de Couchbase > Eventos y haga clic en el botón Registro enlace del desplegado cron_impl_2func_651 Función de registro de eventos. Este cuadro de diálogo Registro de funciones enumera las sentencias de registro en orden inverso (los elementos más nuevos primero). La salida inicial debería ser similar a la siguiente:
2020-05-20T18:34:33.340-07:00 [INFO] "OnUpdate B recurring_event::1 temporizador recurrente fue creado, timer_id 570927555481258455388"
2020-05-20T18:34:33.340-07:00 [INFO] "OnUpdate A recurring_event::1 rcv mutation (initial or rearm) schedule timer at 2020-05-20T18:34:45.000"
2020-05-20T18:34:33.233-07:00 [INFO]"doCronActionA upsert to KV with KEY cron_cache::airlines_by_country cachedoc " {"type": "cron_cache", "id": "airlines_by_country", "date": "2020-05-21T01:34:33.232Z", "data":{"Estados Unidos":127, "Reino Unido":39, "Francia":21}}
2020-05-20T18:34:33.233-07:00 [INFO] "Callback R recurring_event::1 action took 0.013 sec., returned true"
2020-05-20T18:34:33.233-07:00 [INFO] "Callback C recurring_event::1 curDoc" {"action": "doCronActionA", "active":true, "hour": "*4X", "id":1, "min": "*4X", "type": "recurring_event", "verbose":{"scheduler":3, "user_func":2}, "dynamic":{"state": "pending", "next_sched":1590024885, "prev_sched":1590024870, "prev_etime":1590024873, "prev_delay":3.218,”prev_atime”:0.013}}
2020-05-20T18:34:33.233-07:00 [INFO] "Callback B recurring_event::1 sched 1590024870, actual 1590024873, delay 3.218, took 0.013"
2020-05-20T18:34:33.233-07:00 [INFO] "Callback A recurring_event::1 gen mutation #1 to doc to force schedule rearm at 2020-05-20T18:34:45.000"
2020-05-20T18:34:33.232-07:00 [INFO] "doCronActionA N1QL idx 2, country France cnt 21"
2020-05-20T18:34:33.232-07:00 [INFO] "doCronActionA N1QL idx 1, country Reino Unido cnt 39"
2020-05-20T18:34:33.232-07:00 [INFO] "doCronActionA N1QL idx 0, country Estados Unidos cnt 127"
2020-05-20T18:34:33.220-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"
2020-05-20T18:34:19.340-07:00 [INFO] "OnUpdate B recurring_event::1 temporizador recurrente fue creado, timer_id 381384185845112994486"
2020-05-20T18:34:19.340-07:00 [INFO] "OnUpdate A recurring_event::1 rcv mutation (initial or rearm) schedule timer at 2020-05-20T18:34:30.000"La línea más antigua en la parte inferior es la mutación que inició el programa (o rearmó el programa), por ejemplo, el mensaje OnUpdate, y vemos las dos primeras ejecuciones completas de la lógica de negocio que codificamos en doCronActionA
- También habrá algunos mensajes relacionados con el "arreglo para 6.5.X cubo creciente ops"pero se registrará en cron_impl_2func_651_help verá mensajes como el siguiente cada 30 segundos aproximadamente:
2020-05-20T18:34::49.185-07:00 [INFO] "seeding one doc to each vBucket in primary_bucket alias (took 221 mililis)"
Vamos a ajustar tanto la frecuencia como la verbosidad de esta tarea concreta. Utilizaremos el estándar cron sintaxis de "*" para ambos hora y min para obtener una programación recurrente de una vez por minuto 4X más lenta que la frecuencia actual. Además, bajaremos el nivel de verbosidad de la lógica del planificador a cero y la función de usuario a 1 para que sólo veamos un mensaje por invocación.
Desde el Consola Web Couchbase > Consulta utilizaremos N1QL para crear una nueva tarea programada en el cubo "muestra-viaje":
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
1234UPDATE `crondata`SET verbose.scheduler = 0, verbose.user_func = 1,active=true, hour="*", min="*"WHERE type="recurring_event" AND id=1 ;
- Haga clic en Ejecute
Después de un acceso de 2 o 3 minutos el Consola Web de Couchbase > Eventos y haga clic en el botón Registro enlace del desplegado cron_impl_2func_651 Función Eventing.
- La programación recurrente es ahora de un minuto y mucho menos verbosa. Sólo se emite un mensaje o línea de registro por cada ejecución de función (una vez más en orden temporal inverso).
2020-05-20T18:43:04.231-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"
2020-05-20T18:42:08.233-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"Sólo se emite un mensaje o línea de registro por cada ejecución de función de usuario programada, es decir. doCronActionA (de nuevo en orden cronológico inverso).
Veamos el trabajo que se realiza
Este código proporciona un marco práctico para ejecutar cualquier función JavaScript en un horario recurrente y nuestra función doCronActionA es upserting (insertar o actualizar) un documento KV de caché calculado una vez por minuto.
Para comprobar los resultados de la función de eventos desplegada, acceda a la pantalla Consola Web de Couchbase > Cubos y haga clic en el enlace Documentos de la página viaje-muestra cubo.
- En el cuadro de texto "N1QL DONDE" pasado el siguiente texto.
1type="cron_cache"
- Haga clic en Recuperar documentos
Ahora debería ver un documento de caché cron_cache::airlines_by_country que se actualiza una vez por minuto mediante la función programada doCronActionA.

- Haga clic en el identificador "cron_cache::airlines_by_country"verá su documento en caché que está siendo actualizado por la lógica de negocio de doCronActionA.

Por supuesto, los datos de origen son "estáticos" en este momento, por lo que los recuentos seguirán siendo los mismos.
Veamos el documento de control
Este código proporciona un marco para mantener un poco de estadísticas sobre cada programa en ejecución.
Para comprobar las estadísticas de la función de eventos desplegada, acceda a la pantalla Consola Web de Couchbase > Cubos y haga clic en el enlace Documentos de la página crondata cubo.
- En el cuadro de texto "N1QL DONDE" pasado el siguiente texto.
1type="recuring_event"
- Haga clic en Recuperar documentos
Ahora debería ver un documento de su documento de control recurring_event::1 que está controlando la función programada doCronActionA.
- Haga clic en el identificador "evento_recurrente::1" verá su documento de control que está siendo actualizado por la lógica del planificador con algunas estadísticas en la dinámica de objetos JSON.

La parte "dinámica" del documento que se añade automáticamente conserva algunas estadísticas de depuración: - prev_sched: es la marca de tiempo UNIX anterior de la última planificación ejecutada
- prev_etime: es la marca de tiempo UNIX real de la última vez que se ejecutó el programa
- prev_delay: es el retraso desde el prev_sched hasta el prev_etime
- prev_atime: es el tiempo que tardó en ejecutarse esta acción, es decir, doCronActionA en ejecutarse.
- next_sched: es la siguiente ejecución programada para esta acción
Estas estadísticas guardadas en el subobjeto dinámico JSON para cada programación son útiles para determinar que su sistema de programación está en buen estado y que la acción que se está ejecutando está terminando a tiempo.
Comprobar que la caché se actualiza cuando cambian los datos
Todo el propósito de doCronActionA es realizar el trabajo a una hora programada o cerca de ella y actualizar un documento de caché con la CLAVE "cron_cache::airlines_by_country".
Hagamos algunas validaciones en el Query Monitor de que nuestra caché se está actualizando 1) mirando el documento de la caché, 2) eliminando algunas aerolíneas del conjunto de documentos de la muestra de viaje, y 3) verificando que el documento de la caché se actualiza mediante la función doCronActionA.
Desde el Consola Web Couchbase > Consulta utilizaremos N1QL para ver y manipular los datos del cubo "muestra-viaje":
- Corta y pega la siguiente sentencia N1QL en el archivo
- Editor de consultas
12SELECT data FROM `travel-sample`WHERE `type` = 'cron_cache' AND id== 'airlines_by_country'; - Haga clic en Ejecute
En la vista JSON del Query Workbench deberías ver:
123456789[{"data": {"France": 21,"United Kingdom": 39,"United States": 127}}] - Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
12DELETE FROM `travel-sample`WHERE `type` = 'airline' AND callsign LIKE 'U%' - Haga clic en Ejecute
En la vista JSON del Query Workbench deberías ver (acabamos de borrar algunos datos)
123{"results": []} - Espere un poco más de un minuto
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
12SELECT data FROM `travel-sample`WHERE `type` = 'cron_cache' AND id== 'airlines_by_country'; - Haga clic en Ejecute
En la vista JSON del Query Workbench debería ver que cuatro (4) compañías aéreas ya no están presentes
123456789[{"data": {"France": 21,"United Kingdom": 39,"United States": 123}}]
Iniciar una segunda tarea programada
Este código proporciona un marco práctico para ejecutar de 1 a N funciones JavaScript en horarios recurrentes.
Dejaremos que la función doCronActionA seguirán funcionando en un horario de un minuto, pero ahora habilitaremos doCronActionB en un horario de 30 segundos (dos veces por minuto). Esta función es una shell vacía y sólo registrará que fue invocada.
Desde el Consola Web Couchbase > Consulta utilizaremos N1QL para ver y manipular datos en el directorio 'crondata' cubo:
- Corta y pega la siguiente sentencia N1QL en el archivo Editor de consultas
123456789101112131415INSERT INTO `crondata` (KEY,VALUE) VALUES ("recurring_event::2",{"type":"recurring_event","id":2,"hour":"*2X","min":"*2X","action":"doCronActionB","verbose": {"user_func": 1,"scheduler": 0},"active": true}); - Haga clic en Ejecute
En este punto está ejecutando dos (2) tareas diferentes cada una en dos (2) horarios diferentes para verificar espere dos o tres minutos e inspeccione los archivos de registro de nuevo
- Acceder a la Consola Web de Couchbase > Eventos y haga clic en el botón Registro enlace del desplegado cron_impl_2func_651 Función Eventing.
Sólo se emite un mensaje o línea de registro por cada ejecución de función (una vez más en orden temporal inverso). Vemos que doCronActionA dispara una vez por minuto mientras doCronActionB dispara el doble, por ejemplo, una vez cada 30 segundos.2020-05-20T19:16:05.259-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"
2020-05-20T19:16:05.255-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
2020-05-20T19:15:37.253-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
2020-05-20T19:15:09.250-07:00 [INFO] "doCronActionA acción de usuario controlada por recurring_event::1"
2020-05-20T19:15:09.249-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
2020-05-20T19:14:34.255-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
[OPCIONAL] Pausa / Editar JavaScript / Reanudar
Esencialmente hemos terminado con esta entrega, siéntete libre de experimentar y modificar cosas y experimentar por ejemplo:
Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651 para expandir y exponer los controles de Función.
- Haga clic en Pausa.
- En el Confirmar función de pausa seleccione "Función de pausa".
- Pulse "Editar JavaScript"
- Si se siente seguro, modifique el doCronActionB para realizar algunas operaciones KV o integrarse con cURL.
Si sólo quieres ver un cambio añade algo simple a la función prueba algo como lo de abajo:
1234567891011121314151617function doCronActionB(doc) {try {// check that doc has desired valuesif (doc.type !== "recurring_event" || doc.active !== true) return;if (doc.verbose.user_func >= 1)log(doc.action + ' user action controlled by ' + doc.type + '::' + doc.id);// YOUR LOGIC HEREvar a = 1 + 7;log('this is my logic, a = 1 +7 = ' + a);} catch (e) {log(doc.action + ' Error exception:', e);return false;}return true;} - Haga clic en Guardar.
- Para volver a la pantalla de Eventos, pulse el botón '< volver a Concurso completo(debajo del editor) o haga clic en el enlace Eventos
- Haga clic en Currículum.
- En el Confirmar función de reanudación seleccione "Reanudar la función".
- Espere aproximadamente un minuto y para la función cron_impl_2func_651 desplegar
- Haga clic en el botón Registro enlace del desplegado cron_impl_2func_651 Función Eventing.
2020-05-20T19:20:41.343-07:00 [INFO] "esta es mi lógica, a = 1 +7 = 8"
2020-05-20T19:20:41.343-07:00 [INFO] "doCronActionB acción de usuario controlada por recurring_event::2"
Limpieza
La limpieza consiste en desinstalar y eliminar la función y, a continuación, eliminar los dos buckets creados. Con esto concluye el Ejemplo.
Eliminar funciones
Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651 para expandir y exponer los controles de Función.
- Haga clic en Despliegue.
- En el Confirmar Función de Despliegue seleccione "Función Despliegue".
- Esperar a la función cron_impl_2func_651 para deshacer el despliegue.
- Haga clic en Borrar.
- En el Confirmar función de borrado seleccione "Borrar función".
Desde el Consola Web de Couchbase > Eventos pantalla:
- Haga clic en el nombre de la función cron_impl_2func_651_help para expandir y exponer los controles de Función.
- Haga clic en Despliegue.
- En el Confirmar Función de Despliegue seleccione "Función Despliegue".
- Esperar a la función cron_impl_2func_651_help para deshacer el despliegue.
- Haga clic en Borrar.
- En el Confirmar función de borrado seleccione "Borrar función".
Retirar cubos
Siguiente Suelta los cubos 'metadatos' 'crondatayviaje-muestra(siempre se pueden volver a crear).
Desde el Consola Web de Couchbase > Cubos y haga clic en el enlace Documentos de la página viaje-muestra cubo.
- Haga clic en en el nombre del cubo "metadatos" para expandir y exponer los controles
- Haga clic en Borrar
- En el Confirmar Borrar Cubo seleccione "Borrar cubo".
- Haga clic en en el nombre del cubo "crondata" para expandir y exponer los controles
- Haga clic en Borrar
- En el Confirmar Borrar Cubo seleccione "Borrar cubo".
- Haga clic en en el nombre del cubo "viaje-muestra" para expandir y exponer los controles
- Haga clic en Borrar
- En el Confirmar Borrar Cubo seleccione "Borrar cubo".
Reflexiones finales
Espero que hayas encontrado este tutorial educativo y hayas desarrollado una mayor apreciación del Servicio de Eventos Couchbase en su conjunto.
Anteriormente señalé que Eventing está diseñado para procesar mutaciones de alta velocidad (en millones por segundo) desde el flujo DCP del bucket de origen asociado a la función. Esta función de Eventing o planificador sólo necesita reaccionar a cambios mínimos en los documentos del planificador.
He iniciado 5.000 programaciones con este código sin cambios, simplemente añadiendo documentos de control. Incluso he ejecutado 120.000 programaciones cada minuto sólo para probar esta implementación (sí, 120.000 es una cantidad bastante ridícula de programaciones independientes). cron horarios a ejecutar). Además puse 120.000 sc3hedules 2 días en el futuro para asegurarme de que no teníamos problemas de recursos degenerados.
Al verse obligado a utilizar dos Funciones Vespertinas para crear el sistema de programación, la función principal cron_impl_2func_651 y un simple ayudante cron_impl_2func_651_help no era tan elegante como esperaba. Además, tener que trabajar alrededor de una fuga op cubo a través de disparar un temporizador en cada vBucket fue decepcionante por decir lo menos. Afortunadamente, debido a este esfuerzo se implementaron cambios y en la próxima versión 6.6.0 podré hacer un planificador más limpio utilizando una única Evening Function. Las mejoras clave en 6.6 son 1) la capacidad de generar un nuevo temporizador desde dentro de un callback de temporizador y 2) la capacidad de cancelar o sobrescribir un temporizador existente por referencia, y 3) la eliminación del uso creciente de recursos en sistemas inactivos con temporizadores programados en el futuro.
Utilicé un cubo independiente 'crondata' para mantener el horario o los documentos de control (no cuento el uso de 'metadatosya que es un bloc de notas del sistema necesario para Eventing y reservado para Eventing en sí) para ofrecer la máxima flexibilidad a la hora de manipular datos en otros buckets. Si hubiera colocado los documentos de control en otro bucket, no podría realizar operaciones N1QL en ese bucket (ya que es un bucket de origen para la función Eventing) y me habría limitado únicamente a operaciones KV para manipular datos en el bucket colocado.
Te reto a que pruebes otros casos de uso de cron y también a que pienses en otras formas de aprovechar un servicio de programación:
- Comprobación de un recuento de elementos en un gran conjunto de datos durante las "horas valle" y realización de una purga incremental.
- Realización del enriquecimiento programado de documentos a través de N1QL.
- Recalcular periódicamente las carteras de valores.
- Gestión de TTL o tiempos de expiración a través de N1QL en un horario recurrente, consulte Cómo gestionar documentos Time-To-Live (TTL) con Couchbase N1QL.
- Integración con puntos finales REST externos de forma repetida, consulte Uso de cURL con el servicio Eventing: Actualización.
- Actualizar el JavaScript de cron_impl_2func_651 para añadir un nuevo campo "prev_astatus" al objeto dinámico JSON para guardar el indicador de resultado verdadero/falso devuelto por la acción de usuario ejecutada anteriormente.
Actualizaciones
Este blog se actualizó el 24 de julio de 2020 para añadir una solución para las versiones 6.5.x que tienen un número creciente de operaciones de cubo de metadatos que eventualmente pueden bloquear las mutaciones para una función de Eventing dada al crear temporizadores en el futuro (como en una hora+) en un sistema por lo demás inactivo.
Próximos pasos
En unas semanas "Implementación de un cron portátil robusto como programador a través de Couchbase Eventing (Parte 2)"donde exploraremos la ejecución de una secuencia de sentencias N1QL dinámicas controladas por la base de datos sin necesidad de editar la función de eventos o definir un script de "acción" codificado dentro de la función de eventos.
Recursos
- Descargar: Descargar Couchbase Server 6.5.1
- Función Eventing: cron_impl_2func_651.json
- Función Eventing Helper: cron_impl_2func_651_help.json
Referencias
- Documentación de Couchbase Eventing:
https://docs.couchbase.com/server/current/eventing/eventing-overview.html - Couchbase Server 6.5 Novedades:
https://docs.couchbase.com/server/6.5/introduction/whats-new.html - Blogs de Couchbase sobre Eventing:
https://www.couchbase.com/blog/tag/eventing/
Nos encantaría saber qué te han parecido las características de la versión 6.5 y cómo beneficiarán a tu negocio en el futuro. Por favor, comparte tu opinión a través de los comentarios o en el Couchbase foro.
Notas a pie de página
[1] La implementación del temporizador en el servicio Eventing está diseñada para manejar grandes cantidades de temporizadores distribuidos en millones a alta velocidad. Un solo nodo Eventing puede manejar más de 100K temporizadores por segundo y la única promesa es ejecutar los temporizadores tan pronto como sea posible, por ejemplo, no hay temporizadores perdidos. Tenga en cuenta que el intervalo de escaneo actual es de siete (7) segundos para recoger los temporizadores que están listos para disparar, por lo que debe esperar algunos retrasos. Para más detalles sobre la programación de temporizadores consulte Temporizadores: Precisión de reloj de pared en la documentación de Couchbase.
[2] Ajustando allow_interbucket_recursion a verdadero estás eliminando las protecciones que se colocaron en el servidor Couchbase para proteger contra la lógica Eventing accidental que puede iniciar bucles recursivos infinitos. No hay nada malo con esto pero es fácil cometer un error cuando se aprovecha la recursividad. En las versiones 6.6 de Couchbase ajustando la lógica Eventing puede ser colapsada de dos (2) Funciones Eventing en una (1) Función Eventing simplificada sin necesidad de ajustar la función allow_interbucket_recursion ajuste.
[3] Existen dos limitaciones importantes. En primer lugar, si un documento se modifica varias veces en poco tiempo, las llamadas pueden unirse en un único evento debido a la deduplicación. En segundo lugar, no es posible discernir entre las operaciones de creación y las de actualización. Para las propuestas de un cron función ninguna de las dos limitaciones presenta un problema.
[4] Por qué no implementé la semántica exacta de crontab, podría haberlo hecho pero la cantidad de código es excesiva - te remito a explorar el https://github.com/kelektiv/node-cron junto con sus dependencias moment y moment-timezone (todos ellos paquetes muy grandes). El getNextRecurringDate(hour_str, min_str) puede no ser tan flexible pero es simple y cubre nuestro caso de uso.
¡Muy interesado en las actualizaciones 6.6.x para utilizar una sola función! Me encanta esta implementación para un programador tipo cron.
¡Acabo de darme cuenta de que esto está disponible en GitHub! ¡Voy a echar un vistazo a esto! Recomiendo actualizar esta entrada del blog con el enlace :) ¡Gracias!
Hola Alex,
Pronto actualizaré el "cron-like scheduler" con una versión 6.6.0 y una versión 6.6.1 (utilizando accesores de cubo avanzados).
Sin embargo, lo más probable es que en un blog de seguimiento (Parte 2) - pero voy de enlace cruzado ellos, contento de haber encontrado mi 6.6.0 prototipo en GitHub.