Esta é a primeira de uma série de várias partes para aproveitar o Serviço de eventos do Couchbase para executar várias tarefas agendadas em intervalos recorrentes específicos em um cron completamente dentro do banco de dados, sem necessidade de infraestrutura adicional, por meio de uma única Eventing Function de uso geral.
Nesta parte, vamos nos concentrar na execução de rotinas de usuário fixas, funções JavaScript definidas dentro de uma Eventing Function.
Mais adiante, em artigos subsequentes, estenderemos o cron como Eventing Function para agendar e executar instruções N1QL dinâmicas orientadas por banco de dados e, por fim, exploraremos o agendamento de funções JavaScript dinâmicas orientadas por banco de dados.
Histórico
O Couchbase Eventing Service fornece uma estrutura para escrever suas próprias rotinas, funções JavaScript simples, para processar alterações em documentos. Esse serviço fornece toda a infraestrutura necessária para criar funções escalonáveis e robustas baseadas na nuvem, permitindo que você se concentre no desenvolvimento de lógica comercial pura para interagir quase em tempo real com as alterações nos dados. Suas funções podem acessar o serviço de dados do Couchbase (KV), o serviço de consulta do Couchbase (N1QL) e os pontos de extremidade REST externos ao sistema Couchbase.

O JSON modelo de dados no Couchbase veio de JavaScriptPor isso, é natural que o Eventing Service exponha a capacidade de escrever código JavaScript para analisar e manipular documentos JSON em qualquer tipo de evento de alteração, incluindo inserções, atualizações, mesclagens e exclusões (denominadas mutações).
As Eventing Functions normalmente permitem que você implemente e execute fragmentos de código personalizados que reagem a milhares e até milhões de mutações por segundo em seus documentos. Vários casos de uso típicos são documentado para desenvolver Eventing Functions de alta velocidade e em escala que respondem a mutações de documentos do Couchbase.

Em vez disso, este artigo se concentrará em um caso de uso de velocidade muito baixa do Eventing Service, criando um crontab distribuído confiável "no banco de dados", permitindo que você execute funções JavaScript que interagem com os serviços do Couchbase em uma programação periódica regular.
Agendamento da lógica de negócios para execução em uma data ou hora específica
Cron, com o nome de "CronosA palavra grega "tempo" é um dos utilitários mais úteis em um sistema Linux. No Linux, o cron é acionado por um arquivo crontab (tabela cron), um arquivo de configuração que especifica os comandos do shell a serem executados periodicamente em uma determinada programação.
Uma desvantagem da execução cron é que ele não foi projetado para ser um serviço distribuído; ele é executado em uma única caixa e, portanto, apresenta um único ponto de falha. Se o sistema ficar off-line por várias horas, todas as tarefas agendadas serão perdidas.
Sim, há alguns distribuídos cron implementações como o Cloud Service do Google, as tarefas agendadas do AWS e o Azure Functions / Time Triggers. Mas as ofertas de cada fornecedor de nuvem têm seus próprios idiomas e não são diretamente portáteis.
Além disso, a metodologia de configuração e controle precisa ser protegida, por exemplo, se você controlar um sistema distribuído cron por meio de uma API REST sobre HTTP/S, você precisa levar isso em conta no seu plano de segurança.
Usar o próprio Couchbase para executar comandos periódicos
Com uma pequena quantidade de código e planejamento, você pode aproveitar o serviço Eventing do Couchbase para fornecer cron para suas operações ou manutenções programadas no banco de dados. A criação do agendador no banco de dados permite que você obtenha os seguintes benefícios:
- Portabilidade entre provedores de nuvem: se você hospedar novamente o cluster do Couchbase, seu agendador não será afetado.
- Capacidade de suporte: se você utilizar o Couchbase, terá um único fornecedor para fornecer suporte e outros serviços.
- Distribuído, sem ponto único de falha e todos os serviços do Couchbase suportam réplicas distribuídas.
- Execução garantida: sua tarefa é executada mesmo após a recuperação de uma falha de nó.
Programação do Couchbase, temporizadores, o molho secreto
Os temporizadores são construções do Couchbase Eventing Service por meio das quais os desenvolvedores podem especificar uma rotina (lógica de negócios) a ser acionada em um momento futuro. Usaremos essa funcionalidade para implementar um serviço puramente configurável do Couchbase crontab que lhe permite acionar tarefas repetitivas como parte de seus fluxos de trabalho, seja para executar uma simples consulta N1QL ou para criar um mecanismo de regras complexo.
Em todos os projetos subsequentes, limitaremos nossa cron implementações em uma resolução de 15 segundos ou mais. Temos essa limitação porque, embora os temporizadores sejam dimensionados para milhões e tenham garantia de disparo e execução, eles não têm precisão de relógio de parede e atualmente têm um atraso de estado estável limitado a menos de 14 segundos [1].
É claro que, se você precisar de um cronograma mais apertado, ou seja, menos de 15 segundos, deverá simplesmente processar a própria mutação na lógica Eventing sem usar uma construção de cronômetro para agendar uma chamada de volta no futuro.
No momento em que este texto foi escrito, a versão atual do Couchbase é a 6.5.1, que duas limitações que precisamos contornar ao criar um cron sistema.
- Nas versões 5.5.x, 6.0.x e 6.5.x, uma função que é invocada por um retorno de chamada do temporizador não pode criar um novo temporizador de forma confiável (uma solução alternativa no espaço do usuário pode ser feita por meio de uma segunda função cooperativa).
- Nas versões 6.5.x, a criação de temporizadores no futuro (como em uma hora ou mais) em um sistema ocioso pode resultar em um número crescente de operações de balde de metadados que podem, eventualmente, bloquear mutações para uma determinada função de evento (na versão 6.5.X, uma solução alternativa no espaço do usuário pode ser realizada por meio de uma segunda função cooperativa). A gravidade é regida por:
- O número de vBuckets que mantêm um timer ativo. Portanto, se houver apenas alguns cronômetros no futuro, o problema poderá não ser percebido ou se materializar. Esse é o caso de apenas algumas programações do cron, mas, para completar, caso você adicione a funcionalidade de data, coloquei uma correção para esse problema no código fornecido neste artigo.
- Se um timer de Eventing foi disparado recentemente em um vBucket (o que elimina o problema para o vBucket em questão com base em cada função). Portanto, os sistemas com muita atividade de temporizador de curto prazo não terão esse problema, mesmo que os temporizadores estejam programados para um futuro distante.
Felizmente, na versão 6.6.0, os dois problemas ou restrições acima foram eliminados e um agendador pode ser criado em uma única Eventing Function unificada e simples.

Pré-requisitos
Neste artigo, usaremos a versão mais recente do GA, ou seja, a versão 6.5.1 do Couchbase (talvez seja necessário fazer algumas alterações nas Eventing Functions descritas para versões anteriores do Couchbase). O exemplo deste artigo será executado com base no conjunto de dados de amostra de viagem fornecido com o servidor Couchbase.
DICA PRO: Somente para usuários avançados, Se você estiver familiarizado com o Couchbase Eventing e também com nossas ferramentas CLI / REST, poderá pular a maior parte deste blog e baixar um arquivo ZIP para configurar e executar rapidamente o sistema de agendamento apresentado abaixo. Clique com o botão direito do mouse no link a seguir e escolha Salvar link como para fazer o download do arquivo cron_impl_2func_CLI.zip, mova-o para um nó Eventing, extraia o arquivo ZIP e consulte o arquivo README.txt extraído.
No entanto, se você não estiver familiarizado com o Couchbase ou com o serviço Eventing, consulte o GET STARTED e um exemplo de Eventing, especificamente o seguinte:
- Configure um servidor Couchbase 6.5.1 funcional de acordo com as instruções em Comece aqui!
- Certifique-se de que você pode executar uma consulta N1QL no amostra de viagem de acordo com as instruções em Execute sua primeira consulta N1QL.
- Entenda como implantar uma função básica de Eventing de acordo com as instruções do Arquivamento de documentos que também usa o amostra de viagem conjunto de dados.
- Certifique-se de que você tenha o amostra de viagem na visualização de Compartimentos da interface do usuário.
- Certifique-se de que você tenha um balde chamado metadados na visualização Buckets da interface do usuário, ele deve ter o tamanho mínimo de 200 MB.
- Na visualização Buckets da interface do usuário, crie um bucket chamado dados cadastrais com o tamanho mínimo de 200 MB. Para obter etapas detalhadas sobre como criar buckets, consulte Criar um balde.
- Conjunto allow_interbucket_recursion para verdadeiro a fim de permitir que duas (2) funções de Eventing alterem o mesmo documento KV [2].
1curl -X POST -u "$CB_USERNAME:$CB_PASSWORD" 'https://localhost:8091/_p/event/api/v1/config' -d '{ "allow_interbucket_recursion":true }'
Implementação #1, programação do tipo "cron" com código rígido
Para nossa primeira implementação, por exemplo, a Parte 1 da série, projetaremos uma estrutura de controle simples que é apenas um documento KV JSON e também duas (2) Eventing Functions que responderão e agirão de acordo com as informações da estrutura de controle.
Abaixo está um projeto de um documento JSON, ou estrutura de controle, que nos permitirá ter vários "eventos" agendados. Cada evento agendado terá seu próprio documento de controle com uma CHAVE exclusiva, como recurring_event::1, recurring_event::1, ... recurring_event::N. A própria estrutura JSON contém informações para "reconstituir a chave", pois nosso sistema de agendamento responderá a alterações ou atualizações (mutações) nos documentos de controle, como alternar o estado "ativo" para ativar ou desativar a ação ou alterar o campo "verbose", que controla a quantidade e o estilo do registro.
A seguir, um exemplo de documento de controle com KEY evento_recorrente::1 que executará a função JavaScript doCronActionA às 14:54 (2:30 pm) todos os dias.
| Registro de controle JSON | Descrição |
|---|---|
| { | |
| "type": "recurring_event", | A CHAVE será <>::<> |
| "id":1, | |
| "hour":14, | A hora do dia 0-23, *, *2X, *4X para acionar |
| "min":54, | O minuto na hora 0-59, *, *2X, *4X para acionar |
| "action": "doCronActionA", | Função JavaScript a ser executada quando o cronômetro for acionado |
| "active":true, | Sinalizador para ativar ou desativar essa programação |
| "verbose": { | Controle de registro [OPCIONAL |
| "user_func":2, | Nível de registro para a lógica de ação: 0=nenhum, etc. etc. |
| "scheduler":3 | Nível de registro para a lógica cron: 0=nenhum, etc. etc. |
| }, | |
| "dynamic": { | [DYNAMIC] controle e estatísticas do sistema |
| "estado": "braço", | "arm"|"rearm"|"pending" qualquer valor != "pending" inicia uma programação |
| "next_sched": 0, | Número de segundos desde a época até a próxima programação desejada |
| "prev_sched": 0, | Número de segundos desde a época para a programação anterior |
| "prev_etime": 0, | Número de segundos desde a época para a hora de execução real da programação anterior |
| "prev_delay": 0, | Número de segundos em que o cronômetro foi atrasado em relação à programação |
| "prev_atime": 0 | Número de segundos usados pelo usuário 'action' |
| } | |
| } |
Como o Linux tradicional crontab você pode definir hour e min como números inteiros legais, e também pode definir hora para "*" para processar todas as horas ou definir min para "*" para processar todos os minutos.
Embora não apoiemos o crontab sintaxe, oferecemos suporte a duas configurações não padrão, como segue, se você definir ambos hora e min se você definir ambos como "*4X", executaremos e rearmaremos quatro (4) vezes por minuto e, se você definir ambos como "*2X", executaremos e rearmaremos duas (2) vezes por minuto. Abaixo está uma tabela de programações compatíveis com a descrição:
| hora | min | Os valores podem ser números ou cadeias de caracteres |
|---|---|---|
| 13 | 32 | Corra às 13:32 (ou 13:32) |
| * | 15 | Executar a cada hora aos 15 minutos |
| 8 | 12 | Corra uma vez por dia às 8:32 (ou 8:32 da manhã) |
| * | * | Executar uma vez por minuto |
| *2X | *2X | Funciona duas vezes por minuto - requer que a hora e o minuto estejam definidos como "*2X" |
| *4X | *4X | Funciona quatro vezes por minuto - requer que a hora e o minuto estejam definidos como "*2X" |
Eventualmente, usaremos o Query Workbench para inserir o cron documentos de controle, todos os quais devem ter uma CHAVE exclusiva de evento_recorrente::# para um horário de execução programado de 14:54 (14:54), para a ação doCronActionA, poderíamos usar a seguinte instrução N1QL.
Não se preocupe em executar nenhum comando N1QL neste momento, executaremos as instruções N1QL mais tarde, depois que tivermos criado e implementado nossa Eventing Function.
É possível criar um registro de controle (ou registros) no bucket amostra de viageme, em seguida, liste-o, arme-o, desarme-o, ajuste a programação que ele segue, altere o nível de verbosidade do registro em log ou exclua-o da seguinte forma:
| Ação | Declaração N1QL |
|---|---|
| Criar uma programação | INSERT INTO amostra de viagem (KEY,VALUE) VALORES ("recurring_event::1", { "type": "recurring_event", "id":1, "hour": "14″, "min": "54″, "action": "doCronActionA", "active":true } ); |
| Criar um índice para consultar dados sem especificar chaves | CREATE primary INDEX on dados cadastrais ; |
| Mostrar todas as programações ordenadas por id | SELECT * FROM dados cadastrais WHERE type="recurring_event" order by id ; |
| Mostrar programação específica | SELECT * FROM dados cadastrais WHERE type="recurring_event" AND id=1 ; |
| Armar ou definir ativo | ATUALIZAÇÃO dados cadastrais SET active = true WHERE type="recurring_event" AND id=1 ; |
| Desarmar ou definir como inativo | ATUALIZAÇÃO dados cadastrais SET active = false WHERE type="recurring_event" AND id=1 ; |
| Ajuste do tempo de disparo | ATUALIZAÇÃO dados cadastrais SET hour = 11, min = 30 WHERE type="recurring_event" AND id=1 ; |
| Ajustar o registro da "ação" | ATUALIZAÇÃO dados cadastrais SET verbose.user_data = 0 WHERE type="recurring_event" AND id=1 ; |
| Ajustar o registro da lógica do agendador | ATUALIZAÇÃO dados cadastrais SET verbose.scheduler = 0 WHERE type="recurring_event" AND id=1 ; |
| Excluir a programação | DELETE FROM dados cadastrais WHERE type="recurring_event" AND id=1 ; |
Suponha que tenhamos quatro (4) programações ativas. A execução do primeiro comando N1QL acima listará todas elas, por exemplo
|
1 2 |
SELECT active, action, hour, min, type, id, verbose.user_func, verbose.scheduler FROM `crondata` where type="recurring_event" order by id ; |
Retornaria algo como o seguinte resultado (exibição de tabela no Query Workbench):
| ativo | ação | hora | id | min | programador | tipo | user_func |
|---|---|---|---|---|---|---|---|
| verdadeiro | "doCronActionA" | 14 | 1 | 54 | 1 | "recurring_event" (evento recorrente) | 2 |
| verdadeiro | "doCronActionB" | * | 2 | * | 1 | "recurring_event" (evento recorrente) | 1 |
| verdadeiro | "doCronActionC" | *2X | 3 | *2X | 4 | "recurring_event" (evento recorrente) | 4 |
| verdadeiro | "doCronActionD" | * | 4 | 0 | 0 | "recurring_event" (evento recorrente) | 1 |
Na tabela acima, temos quatro ações: a primeira é executada uma vez por dia, a segunda é executada a cada minuto, a terceira a cada 30 segundos e a quarta é executada uma vez por hora. Em uma próxima edição desta série, adicionaremos o recurso "dia da semana".
O objeto aninhado do registro de controle JSON "detalhado", se não for fornecido, terá como padrão { "user_func":1, "scheduler":1 } indicando um nível de registro baixo ou conciso para a função de ação e também para a lógica de agendamento. Um valor de 0 suprimirá todas as mensagens de registro, ou seja, doCronActionD, enquanto valores maiores serão mais detalhados, ou seja, conforme definido em doCronActionC.
O objeto aninhado do registro de controle JSON "dinâmico" se normalmente nunca for fornecido e terá como padrão {"state": "arm", "next_sched": 0, "prev_sched": 0, "prev_etime": 0, "prev_delay": 0, "prev_atime": 0 } esse é um bloco de rascunho para a programação lógica de Eventing em execução e também fornece estatísticas úteis sobre os tempos de execução, portanto, deve ser tratado como somente leitura.
Neste ponto, temos um projeto de controle de alto nível, mas precisamos de lógica para processar nossas estruturas de controle. É aqui que o Eventing Service do Couchbase, especificamente uma Eventing Function, entra em ação.
As funções de eventos
Esse projeto requer duas (2) funções de eventos: uma função JavaScript principal "cron_impl_2func_651" e uma pequena função JavaScript auxiliar "cron_impl_2func_651_help". Discutiremos cada seção das funções JavaScript que compõem o código JavaScript combinado da implementação inicial, com quase 610 linhas (sendo que cerca de 44% das linhas são comentários e espaços em branco)
Não se preocupe em recortar e colar agoraSe você preferir, poderá usar o link para baixar (para importação) as duas Eventing Functions necessárias e todas as configurações necessárias em dois arquivos denominados "cron_impl_2func_651.json" "cron_impl_2func_651_help.json" e também, se preferir, as duas funções unificadas completas que podem ser recortadas e coladas diretamente.
Nossa função de evento principal "cron_impl_2func_651" será composta de nove (9) funções JavaScript
- Três (3) funções de lógica comercial (duas das quais estão vazias).
- doCronActionA(doc) - um exemplo de ação de usuário N1QL a ser executado
- doCronActionB(doc) - um shell de ação do usuário vazio para experimentos
- doCronActionC(doc) - um shell de ação do usuário vazio para experimentos
- Um (1) ponto de entrada para Eventing.
- OnUpdate(doc, meta) - o ponto de entrada padrão do Eventing para inserções ou atualizações
- Um (1) cron analisador de sintaxe para gerar a próxima programação.
- getNextRecurringDate(hour_str, min_str) - lógica do cron para encontrar a próxima data agendada
- Três (3) funções de suporte para verificar se a lógica comercial existe ou formatar resultados.
- verifyFunctionExistsViaEval(curDoc, id) - certifique-se de que temos uma função para executar
- toNumericFixed(number, precision) - formata um float em um estilo compacto
- toLocalISOTime(d) - formata uma data em um estilo compacto
- Uma (1) função de retorno de chamada quando os cronômetros são executados.
- Callback(doc) - uma função de retorno de chamada para cronômetros programados
Nossa Helper Eventing Function "cron_impl_2func_651_help" será composta por uma (1) função JavaScript
- Um (1) ponto de entrada para Eventing.
- OnUpdate(doc, meta) - o ponto de entrada padrão do Eventing para inserções ou atualizações
Nas seções seguintes, examinaremos cada uma das funções JavaScript acima.
Precisamos de uma função JavaScript, por exemplo, a lógica comercial a ser executada em uma programação periódica.
A primeira coisa que queremos é uma rotina ou função que tenha nossa lógica de negócios que será executada com base em nossas regras do crontab. Chamaremos o método JavaScript doCronActionA(doc)no entanto, ele pode ser chamado de qualquer coisa, por exemplo doPeriodicLedgerBalance(doc), Os únicos requisitos para nossas funções de "ação" que implementam nossa lógica comercial programada são os seguintes:
- Tem um parâmetro: doc, um documento de controle, conforme descrito acima, do tipo="recurring_event".
- O nome real do JavaScript corresponde ao campo "action" no documento de controle.
- Devoluções verdadeiro sobre sucesso e falso em caso de falha
- Utiliza doc.verbose.user_func para controlar o registro; se 0, ele é silencioso; se 1, ele emite uma única linha; se 2, ele emite qualquer informação de registro necessária para depurar a função etc. etc.
Escreveremos nossa função doCronActionA(doc)para executar uma consulta N1QL incorporada) para combinar contagens de companhias aéreas por país e, em seguida, criar um único documento KV de dados calculados.
|
1 |
SELECT country, count( * ) AS cnt FROM `travel-sample` WHERE `type` = 'airline' GROUP BY country; |
Em meu sistema de teste, um pequeno nó único nãoMDS (executando todos os serviços do Couchbase), o N1QL acima leva cerca de 20 ms. (para fins de clareza, fingir que é supercomplexo leva 10 segundos para ser concluído).
A ideia aqui é que o documento KV final calculado e resumido possa ser carregado rapidamente por 100 mil (ou um milhão) de mutações Eventing por segundo sem a sobrecarga adicional de comunicação com os nós do serviço Query e do processamento de instruções N1QL em cada mutação.
Deve ser óbvio que o objetivo dessa lógica comercial específica, doCronActionA(doc)O objetivo do cache semi-estático é criar um cache semi-estático que seja atualizado periodicamente em um cronograma.
Tudo o que estamos realmente fazendo (e é bastante rápido) é obter uma contagem de companhias aéreas por país a partir do conjunto de documentos de amostra de viagem. À medida que usamos o N1QL, criamos um documento e, por fim, o escrevemos no KV como um documento resumido. O ponto principal a ser destacado aqui é que não queremos repetir o mesmo trabalho para milhões de mutações cada, especialmente porque alguns cálculos podem levar 10 segundos de tempo de computação do serviço de consulta cada vez que iniciamos uma consulta N1QL incorporada a partir de uma função Eventing.
Abaixo, mostramos a função JavaScript que queremos executar uma vez por dia (ou talvez uma vez por hora, etc.). Observe que o nome da função corresponde ao nome no campo de ação da estrutura de controle. Para obter mais detalhes sobre a terminologia de Eventing e as construções de linguagem, consulte os documentos e exemplos do Couchbase em Serviço de eventos: 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; } |
A função acima simplesmente 1) consulta o bucket de amostra de viagens para extrair dados; nesse caso, a contagem de companhias aéreas para cada país; 2) cria um novo documento KV e uma nova chave e os grava no bucket de amostra de viagens para uso posterior.
Além disso, como parte deste exemplo, criamos um registro que responde a uma configuração de verbosidade numérica que a) registra uma única linha se o documento de controle tiver um valor para doc.verbose.user_func == 1 ou b) emite mais informações se o valor de doc.verbose.user_func >= 2.
Essa é uma estrutura genérica que pode executar um (1) cron ação ou até mesmo mil (1000) de cron ações. Por isso, forneci dois shells de função "vazios" adicionais - como já foi dito, eles poderiam ter qualquer nome.
|
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; } |
e
|
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; } |
Essas funções acima, doCronActionB e doCronActionC, são triviais, pois simplesmente registram informações no log do aplicativo Eventing da função Eventing. Consulte Funções de registro em log para obter mais detalhes. É claro que você precisa de um documento de controle do tipo = "recurring_event" com active = true e uma ação como action = "doCronActionB" para realmente ativá-los e executá-los.
Precisamos de um ponto de entrada ou manipulador de eventos
A partir da versão 6.5, dois pontos de entrada ou manipuladores são compatíveis com o Eventing Service OnUpdate(doc, meta) e OnDelete(meta) estamos interessados apenas no OnUpdate(doc,meta) para este exemplo.
O OnUpdate(doc,meta) é chamado quando qualquer documento no bucket de origem é criado ou modificado (mutado) e filtra imediatamente os documentos que não interessam. [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); } } |
A chave aqui é que o cron A lógica do nosso manipulador só se preocupa com os documentos que têm doc.type de "recurring_event" e também um doc.active de true. Além disso, neste exemplo, criamos um rastreamento para o cron lógica de manutenção que só é registrada no log do aplicativo se o documento de controle tiver um valor para doc.verbose >= 3.
Se você executar apenas algumas programações, poderá desativar o trabalho no espaço do usuário ou "Correção para operações de balde de crescimento 6.5.X" comentando quatro linhas de código no bloco OnUpdate acima para "cron_impl_2func_651" da seguinte forma:
|
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"] = {}; // } |
Precisamos de código para contornar possíveis operações de aumento de balde para a versão 6.5.X
A partir da versão 6.5.X, precisamos de um "Correção para operações de balde de crescimento 6.5.X", o que acontece em sistemas ociosos com muitos cronômetros programados para o futuro. Esse código garante que um timer de Eventing tenha sido disparado recentemente em um vBucket (o que elimina o problema do vBucket em questão com base em cada função).
|
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); } } |
Precisamos de um utilitário para calcular o próximo horário na programação
A próxima função getNextRecurringDate(hour, min) determinará um horário para executar a ação, conforme definido como parte de nossa programação. Essa não é uma implementação completa de cronEm vez disso, ele contém os principais recursos padrão para execução uma vez por dia, uma vez por hora, uma vez por minuto. Ele também contém uma sintaxe não padrão para permitir a execução duas vezes por minuto ou quatro vezes por minuto.
Conforme descrito anteriormente, a função getNextRecurringDate(hour, min) permite o seguinte (a tabela está duplicada abaixo), sendo que os dois últimos não são padrão.[4]
| hora | min | Os valores podem ser números ou cadeias de caracteres |
|---|---|---|
| 13 | 32 | Corra às 13:32 (ou 13:32) |
| * | 15 | Executar a cada hora aos 15 minutos |
| 8 | 12 | Corra uma vez por dia às 8:32 (ou 8:32 da manhã) |
| * | * | Executar uma vez por minuto |
| *2X | *2X | Funciona duas vezes por minuto - requer que a hora e o minuto estejam definidos como "*2X" |
| *4X | *4X | Funciona quatro vezes por minuto - requer que a hora e o minuto estejam definidos como "*2X" |
Abaixo está uma implementação da lógica necessária para determinar o próximo momento para acionar um temporizador Eventing em nossa agenda, caso a lógica do usuário em nosso primeiro exemplo doCronActionA(doc) não for concluído em tempo hábil, por exemplo, excesso de tempo real, a próxima quantidade da programação será selecionada. Observe os temporizadores e suas funções principais. Portanto, se uma Eventing Function tiver um tempo limite de execução padrão de 60 segundos, se necessário, essa configuração poderá ser ajustada ou 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; } |
Precisamos de alguns pequenos serviços públicos
A função utilitária comum que simplesmente verifica se nosso JavaScript existe é usada por ambos OnUpdate(doc,meta), mostrado acima, e o cronômetro Callback(doc), mostrado mais tarde. Abaixo está verifyFunctionExistsViaEval(curDoc, id) que recebe dois argumentos: um documento de controle JSON e a KEY para esse documento.
Isso nos permite saber imediatamente, na implantação, se houve um problema com uma incompatibilidade de nomes entre o documento de registro de controle JSON e o nome real da função de lógica comercial no 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; } |
Observe que, se houver uma tentativa de executar uma função inexistente, o usuário final receberá um aviso no log do aplicativo cron_impl_2func_651.log para corrigir o problema.
2020-04-22T16:20:38.725-07:00 [INFO] "Avisar/Desabilitar (Sem Ação e Sem Rearmamento), porque a 'ação' necessária de doCronMyNewFunction(doc) não existe, o id é" "recurring_event::1"
Essa correção pode ser feita por meio de uma pausa/retomada que adiciona a função e, em seguida, ajusta o documento de controle com o ID ou KEY especificado (por meio de uma alternância ativa para falso e depois para verdadeiro) ou ajusta o documento de controle para apontar para uma função existente em seu manipulador.
Em seguida, o utilitário toNumericFixed(number, precision) permite apenas uma formatação compacta e agradável de floats para nossas mensagens 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 fim, o utilitário toLocalISOTime(d) permite apenas uma boa formatação compacta de datas para nossas mensagens 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); } |
Precisamos de uma chamada de retorno do temporizador para executar a lógica do usuário e rearmar o temporizador
A função JavaScript final em "cron_impl_2func_651" é o retorno de chamada do Timer, que é chamado quando o timer programado é acionado. A função de retorno de chamada deve ser uma função de nível superior que recebe um único argumento, o contexto.
Nesse caso, em nosso manipulador OnUpdate, fizemos referência a uma função JavaScript de Callback(doc) com um contexto de doc (nosso documento de controle do agendador ativo de type="recurring_event")
Na versão 6.6, podemos criar outro cronômetro dentro de um cronômetro, mas em todas as versões anteriores precisaremos acionar uma mutação para uma função "auxiliar" (evitamos cuidadosamente a recursão infinita). Na versão 6.6, a função auxiliar não é necessária e a lógica foi substancialmente simplificada.
|
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); } } |
Precisamos de uma função auxiliar para acionar uma nova mutação
Como antes da versão 6.6 (que ainda não foi lançada) não é possível criar um temporizador a partir de um retorno de chamada de um temporizador em execução, precisamos de uma segunda Eventing Function (juntamente com "allow_interbucket_recursion":true) para acionar uma mutação de modo que possamos gerar todos os nossos temporizadores no ponto de entrada OnUpdate(doc,meta) da Eventing Function principal. Fazemos isso da seguinte forma:
- cron_impl_2func_651 OnUpdate(doc,meta) recebe uma mutação, programa um cronômetro
- cron_impl_2func_651 Após um determinado período de tempo, quando o cronômetro amadurece, o Callback(doc) A rotina é executada, primeiro executa a ação do usuário desejada e, em seguida, cria uma mutação #1 no documento de controle (que não é vista pela função de criação para evitar recursão)
- cron_impl_2func_651_help OnUpdate(doc,meta) recebe uma mutação, faz outra mutação #2 no documento de controle, o que aciona 1. acima em um ciclo sem fim.
Observe que, na versão 6.6 do Couchbase, não precisamos de uma função auxiliar, pois é permitido criar um timer de dentro de um timer em execução. Isso simplifica muito a lógica necessária para criar um cron sistema[2].
A única função JavaScript em "cron_impl_2func_651_help" OnUpdate(doc,meta) é mostrado abaixo.
|
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; } |
A função auxiliar precisa de alguns utilitários
Esses utilitários fornecem um Correção para operações de balde de crescimento 6.5.X assegurando que um temporizador Eventing seja acionado em cada vBucket em tempo hábil.
|
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; } |
Agora, vamos implementar as duas Eventing Functions
Revisamos muitos códigos e o design do agendador inicial, agora é hora de ver tudo funcionando em conjunto.
Lembre-se de que, neste exemplo, há três compartimentos amostra de viagem (um conjunto de dados padrão de amostra), metadados(o bucket de metadados é um bloco de notas para Eventing e pode ser compartilhado com outras funções de Eventing) e, por fim, o dados cadastrais (que contém nossas programações cron). O amostra de viagem tem um tamanho de 100 MB e os outros dois baldes metadados e dados cadastrais devem ter um tamanho de 200 MB e já existir de acordo com as instruções em "Pré-requisitos".
- Verifique a configuração atual do seu bucket acessando o Console da Web do Couchbase > Buckets página:

Para implementar a função Eventing "cron_impl_2func_651", você pode seguir um destes dois métodos:
- Complexidade básica, Método #1 Download/Importação
- Complexidade média, método #2 Adicionar função manualmente, recortar e colar JavaScript
Método #1 Download/Importação
Importar a primeira função "cron_impl_2func_651"
Faça o download da primeira Eventing Function com todas as configurações necessárias, clique com o botão direito do mouse no link a seguir e selecione Salvar link como para fazer o download do arquivo cron_impl_2func_651.json em seu sistema de arquivos local.
Do Console da Web do Couchbase > Eventos página, clique em IMPORTAÇÃO, navegue até o arquivo cron_impl_2func_651.json, selecione-o e abra-o. A ADICIONAR FUNÇÃO é exibida.
No ADICIONAR FUNÇÃO para elementos individuais da função, forneça as informações abaixo. Observe o arquivo JSON cron_impl_2func_651.json pré-configurará todas as configurações corretamente para este exemplo:
- Para o Balde de origem verifique se ele está definido como dados cadastrais.
- Para o Balde de metadados verifique se ele está definido como metadados.
- Verifique se cron_impl_2func_651 é o nome da função que você está criando no Nome da função caixa de texto.
- [Etapa opcional] Digite o texto Um agendador do tipo cron - parte 1, no Descrição caixa de texto.
- Para o Configurações use os valores padrão.
- Para o Amarrações verifique se existem dois vínculos.
- Para a vinculação, o "bucket alias" especifica cron_bkt como o "nome de alias" do bucket e selecione
dados cadastrais como o bucket associado, e o modo deve ser "leitura e gravação". - Para a vinculação, o "bucket alias" especifica ts_bkt como o "nome de alias" do bucket e selecione
amostra de viagem como o bucket associado, e o modo deve ser "leitura e gravação". - Suas configurações na caixa de diálogo devem ser parecidas com as seguintes:

- Depois de verificar todas as informações necessárias na caixa de diálogo ADD FUNCTION, clique em Next: Add Code (Adicionar código). O código cron_impl_2func_651 é exibida (com o código JavaScript pré-carregado).

- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos guia.
Importar a segunda função "cron_impl_2func_651_help"
Faça o download da segunda Eventing Function com todas as configurações necessárias, clique com o botão direito do mouse no link a seguir e selecione Salvar link como para fazer o download do arquivo cron_impl_2func_651_help.json em seu sistema de arquivos local.
Do Console da Web do Couchbase > Eventos página, clique em IMPORTAÇÃO, navegue até o arquivo cron_impl_2func_651_help.json, selecione-o e abra-o. A ADICIONAR FUNÇÃO é exibida.
No ADICIONAR FUNÇÃO para elementos individuais da função, forneça as informações abaixo. Observe o arquivo JSON cron_impl_2func_651_help.json pré-configurará todas as configurações corretamente para este exemplo:
- Para o Balde de origem verifique se ele está definido como dados cadastrais.
- Para o Balde de metadados verifique se ele está definido como metadados.
- Verifique se cron_impl_2func_651_help é o nome da função que você está criando no Nome da função caixa de texto.
- [Etapa opcional] Digite o texto Um auxiliar de agendamento do tipo cron - parte 1, no Descrição caixa de texto.
- Para o Configurações use os valores padrão.
- Para o Amarrações verifique se existe apenas uma ligação.
- Para a vinculação, o "bucket alias" especifica cron_bkt como o "nome de alias" do bucket e selecione
dados cadastrais como o bucket associado, e o modo deve ser "leitura e gravação". - Suas configurações na caixa de diálogo devem ser parecidas com as seguintes:

- Depois de verificar todas as informações necessárias na caixa de diálogo ADD FUNCTION, clique em Next: Add Code (Adicionar código). O código cron_impl_2func_651_help é exibida (com o código JavaScript pré-carregado).

- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos guia.
Método #2 Adicionar manualmente a função, recortar e colar JavaScript
Criar manualmente "cron_impl_2func_651"
Para adicionar a primeira função Eventing da lista Console da Web do Couchbase > Eventos página, clique em ADICIONAR FUNÇÃOpara adicionar uma nova função. A função ADICIONAR FUNÇÃO é exibida.
No ADICIONAR FUNÇÃO para elementos de função individuais, forneça as informações abaixo:
- Para o Balde de origem definido como dados cadastrais.
- Para o Balde de metadados definido como metadados.
- Fazer cron_impl_2func_651 é o nome da função que você está criando no Nome da função caixa de texto.
- [Etapa opcional] Digite o texto Um agendador do tipo cron - parte 1, no Descrição caixa de texto.
- Para o Configurações use os valores padrão.
- Para o Amarrações crie dois vínculos:
- Para a vinculação, o "bucket alias" especifica cron_bkt como o "nome de alias" do bucket e selecione
dados cadastrais como o bucket associado, e o modo deve ser "leitura e gravação". - Para a vinculação, o "bucket alias" especifica ts_bkt como o "nome de alias" do bucket e selecione
amostra de viagem como o bucket associado, e o modo deve ser "leitura e gravação". - Depois de definir suas configurações, sua caixa de diálogo deverá ter a seguinte aparência:

- Depois de fornecer todas as informações necessárias no formulário ADICIONAR FUNÇÃO clique em Próximo: Adicionar código. O cron_impl_2func_651 é exibida. A caixa de diálogo cron_impl_2func_651 contém inicialmente um bloco de código de espaço reservado. Você substituirá seu cron_impl_2func_651 nesse bloco.

- Copie o código-fonte JavaScript da Eventing Function a seguir (618 linhas) e cole-o no bloco de código de espaço reservado 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);}}
- Após a colagem, a tela é exibida como mostrado abaixo:

- Clique em Salvar.
- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos
Criar manualmente "cron_impl_2func_651_help"
Para adicionar a segunda função Eventing do Console da Web do Couchbase > Eventos página, clique em ADICIONAR FUNÇÃOpara adicionar uma nova função. A função ADICIONAR FUNÇÃO é exibida.
No ADICIONAR FUNÇÃO para elementos de função individuais, forneça as informações abaixo:
- Para o Balde de origem definido como dados cadastrais.
- Para o Balde de metadados definido como metadados.
- Fazer cron_impl_2func_651_help é o nome da função que você está criando no Nome da função caixa de texto.
- [Etapa opcional] Digite o texto Um auxiliar de agendamento do tipo cron - parte 1, no Descrição caixa de texto.
- Para o Configurações use os valores padrão.
- Para o Amarrações crie uma ligação:
- Para a vinculação, o "bucket alias" especifica cron_bkt como o "nome de alias" do bucket e selecione
dados cadastrais como o bucket associado, e o modo deve ser "leitura e gravação". - Depois de definir suas configurações, sua caixa de diálogo deverá ter a seguinte aparência:

- Depois de fornecer todas as informações necessárias no formulário ADICIONAR FUNÇÃO clique em Próximo: Adicionar código. O cron_impl_2func_651_help é exibida. A caixa de diálogo cron_impl_2func_651_help contém inicialmente um bloco de código de espaço reservado. Você substituirá seu cron_impl_2func_651_help nesse bloco.

- Copie o seguinte código-fonte JavaScript da Eventing Function (187 linhas) e cole-o no bloco de código de espaço reservado 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;}
- Após a colagem, a tela é exibida como mostrado abaixo:

- Clique em Salvar.
- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos
Implementar as duas funções
Agora, estamos prontos para iniciar as funções de Eventing. A partir do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651_help para expandir e expor os controles de função.

- Clique em Implementar.
- No Confirmar a função de implantação selecione "A partir de agora" na opção Limite de alimentação.

Vamos começar com a outra função Eventing. A partir do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651 para expandir e expor os controles de função.

- Clique em Implementar.
- No Confirmar a função de implantação selecione "A partir de agora" na opção Limite de alimentação.

Configurar um cron tarefa a ser executada quatro (4) vezes por minuto
Neste ponto, nossa função Eventing está aguardando uma mutação especificamente em qualquer documento do tipo="recurring_event" que tenha um campo active=true.
Do Console da Web do Couchbase > Consulta usaremos o N1QL para criar uma nova tarefa agendada no bucket "travel-sample":
- Recorte e cole a seguinte instrução N1QL no arquivo 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});
- Clique em Executar

Ativar nosso primeiro cron tarefa
O documento de controle que fizemos anteriormente não foi ativado porque especificamos "active":false; além disso, o cronograma acima será executado apenas uma vez por hora, mas queremos testar as coisas e vê-las funcionar em um futuro próximo.
Primeiro, precisamos de um índice para poder manipular nossos documentos de controle no N1QL; isso só precisa ser feito uma vez
- Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
1CREATE primary INDEX on `crondata` ;
Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultasAgora, ativaremos a tarefa, mas ajustaremos a programação de repetição para cada 15 segundos para ver exatamente como o sistema está se comportando. Fazemos isso modificando o documento de controle com KEY recurring_event::1
-
123UPDATE `crondata`SET active=TRUE, hour="*4X", min="*4X"WHERE type="recurring_event" AND id=1 ;
- Clique em Executar

Usamos a sintaxe não padrão de ="*4X" para agendar um item recorrente quatro vezes por minuto e podemos ver nossa função de trabalho doCronActionA executando e também a lógica de manutenção para agendar a função por meio de instruções de registro, pois definimos verbose=3.
O agendador agora está sendo executado quatro vezes por minuto. É possível ver a atividade nas estatísticas e nos arquivos de log do aplicativo para as Eventing Functions cron_impl_2func_651 e cron_impl_2func_651_help.
- Acesse o Console da Web do Couchbase > Painel você verá uma explosão de atividade a cada 15 segundos:

- Acesse o Console da Web do Couchbase > Eventos e clique no botão Registro link do implantado cron_impl_2func_651 Função de registro de eventos. Essa caixa de diálogo Registro de funções lista as instruções de registro em ordem inversa (itens mais recentes primeiro). A saída inicial deve ser semelhante à seguinte:
2020-05-20T18:34:33.340-07:00 [INFO] "OnUpdate B recurring_event::1 recurring timer was created, 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":{"United States":127, "United Kingdom":39, "France":21}}
2020-05-20T18:34:33.233-07:00 [INFO] "A ação Callback R recurring_event::1 levou 0,013 seg., retornou 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 United Kingdom cnt 39"
2020-05-20T18:34:33.232-07:00 [INFO] "doCronActionA N1QL idx 0, country United States cnt 127"
2020-05-20T18:34:33.220-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"
2020-05-20T18:34:19.340-07:00 [INFO] "OnUpdate B recurring_event::1 recurring timer was created, 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"A linha mais antiga na parte inferior é a mutação que iniciou a programação (ou rearmou a programação), por exemplo, a mensagem OnUpdate, e vemos as duas primeiras execuções completas da lógica comercial que codificamos em doCronActionA
- Também haverá algumas mensagens relacionadas ao "Correção para operações de balde de crescimento 6.5.X", mas isso será registrado em cron_impl_2func_651_help você verá mensagens como as seguintes a cada 30 segundos:
2020-05-20T18:34::49.185-07:00 [INFO] "Seeding one doc to each vBucket in primary_bucket alias (took 221 mililis)"
Vamos ajustar a frequência e a verbosidade dessa tarefa específica. Usaremos o padrão cron sintaxe de '*' para ambos hora e min para obter uma programação recorrente de uma vez por minuto, 4 vezes mais lenta do que a frequência atualmente em execução. Além disso, reduziremos o nível de verbosidade da lógica do agendador para zero e a função do usuário para 1, para que vejamos apenas uma mensagem por invocação.
Do Console da Web do Couchbase > Consulta usaremos o N1QL para criar uma nova tarefa agendada no bucket "travel-sample":
- Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
1234UPDATE `crondata`SET verbose.scheduler = 0, verbose.user_func = 1,active=true, hour="*", min="*"WHERE type="recurring_event" AND id=1 ;
- Clique em Executar
Após 2 ou 3 minutos de acesso, o Console da Web do Couchbase > Eventos e clique no botão Registro link do implantado cron_impl_2func_651 Função de eventos.
- O cronograma recorrente agora é de um minuto e muito menos detalhado. Apenas uma mensagem ou linha de registro é emitida para cada execução de função (mais uma vez na ordem inversa de tempo).
2020-05-20T18:43:04.231-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"
2020-05-20T18:42:08.233-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"Apenas uma mensagem ou linha de log é emitida para cada execução de função de usuário programada, ou seja doCronActionA (mais uma vez na ordem inversa do tempo).
Vamos dar uma olhada no trabalho que está sendo realizado
Esse código fornece uma estrutura prática para executar qualquer função JavaScript em um cronograma recorrente e nossa função doCronActionA está fazendo upserting (inserção ou atualização) de um documento KV de cache calculado uma vez por minuto.
Para verificar os resultados da função de eventos implementada, acesse o Console da Web do Couchbase > Buckets e clique no link Documents (Documentos) da página amostra de viagem balde.
- Na caixa de texto "N1QL WHERE" após o texto a seguir.
1type="cron_cache"
- Clique em Recuperar documentos
Agora você deve ver um documento, um documento de cache cron_cache::airlines_by_country que está sendo atualizado uma vez por minuto pela função agendada doCronActionA.

- Clique na identificação "cron_cache::airlines_by_country", você verá o documento em cache que está sendo atualizado pela lógica comercial do doCronActionA.

Espere um minuto e edite o documento novamente e você verá o campo "data" sendo atualizado. É claro que os dados de origem estão "estáticos" no momento, portanto, as contagens permanecerão as mesmas.
Vejamos o documento de controle
Esse código fornece uma estrutura para manter algumas estatísticas sobre cada programação em execução.
Para verificar as estatísticas da função de eventos implementada, acesse a seção Console da Web do Couchbase > Buckets e clique no link Documents (Documentos) da página dados cadastrais balde.
- Na caixa de texto "N1QL WHERE" após o texto a seguir.
1type="recuring_event"
- Clique em Recuperar documentos
Agora você deve ver um documento de controle recurring_event::1 que está acionando a função agendada doCronActionA.
- Clique na identificação "evento_recorrente::1", você verá o documento de controle que está sendo atualizado pela lógica do agendador com algumas estatísticas na dinâmica do objeto JSON.

A parte "dinâmica" do documento que é adicionada automaticamente mantém algumas estatísticas de depuração: - prev_sched: é o carimbo de data/hora UNIX anterior da agenda que foi executada pela última vez
- prev_etime: é o registro de data e hora real do UNIX quando a agenda foi executada pela última vez
- prev_delay: é o atraso do prev_sched para o prev_etime
- prev_atime: é o tempo necessário para executar essa ação, ou seja, para executar doCronActionA.
- next_sched: é a próxima execução programada para essa ação
Essas estatísticas mantidas no subobjeto dinâmico JSON para cada agendamento são úteis para determinar se o seu sistema de agendamento está saudável e se a ação que está sendo executada está sendo concluída em tempo hábil.
Verifique se o cache é atualizado nas alterações de dados
Todo o objetivo do doCronActionA é executar o trabalho em um horário programado ou próximo a ele e atualizar um documento de cache com a CHAVE "cron_cache::airlines_by_country".
Vamos fazer algumas validações no Query Monitor para verificar se nosso cache está sendo atualizado: 1) examinando o documento do cache, 2) excluindo algumas companhias aéreas do conjunto de documentos de amostra de viagem e 3) verificando se o documento do cache é atualizado pela função doCronActionA.
Do Console da Web do Couchbase > Consulta usaremos o N1QL para visualizar e manipular os dados no bucket "travel-sample":
- Recorte e cole a seguinte instrução N1QL no arquivo
- Editor de consultas
12SELECT data FROM `travel-sample`WHERE `type` = 'cron_cache' AND id== 'airlines_by_country'; - Clique em Executar
Na exibição JSON do Query Workbench, você deverá ver:
123456789[{"data": {"France": 21,"United Kingdom": 39,"United States": 127}}] - Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
12DELETE FROM `travel-sample`WHERE `type` = 'airline' AND callsign LIKE 'U%' - Clique em Executar
Na exibição JSON do Query Workbench, você deverá ver (acabamos de excluir alguns dados)
123{"results": []} - Aguarde um pouco mais de um minuto
- Recorte e cole a seguinte instrução N1QL no arquivo Editor de consultas
12SELECT data FROM `travel-sample`WHERE `type` = 'cron_cache' AND id== 'airlines_by_country'; - Clique em Executar
Na exibição JSON do Query Workbench, você verá que quatro (4) linhas aéreas não estão mais presentes
123456789[{"data": {"France": 21,"United Kingdom": 39,"United States": 123}}]
Iniciar uma segunda tarefa agendada
Esse código fornece uma estrutura prática para a execução de 1 a N funções JavaScript em programações recorrentes.
Permitiremos que a função doCronActionA continuarão a ser executados em um cronograma de um minuto, mas agora habilitaremos doCronActionB em um cronograma de 30 segundos (duas vezes por minuto). Essa função é um shell vazio e registrará apenas que foi invocada.
Do Console da Web do Couchbase > Consulta usaremos o N1QL para exibir e manipular dados na página 'dados cadastrais' balde:
- Recorte e cole a seguinte instrução N1QL no arquivo 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}); - Clique em Executar
Nesse momento, você está executando duas (2) tarefas diferentes, cada uma delas em duas (2) programações diferentes para verificar. Aguarde de dois a três minutos e inspecione os arquivos de registro novamente
- Acesse o Console da Web do Couchbase > Eventos e clique no botão Registro link do implantado cron_impl_2func_651 Função de eventos.
Apenas uma mensagem ou linha de registro é emitida para cada execução de função (mais uma vez na ordem inversa de tempo). Vemos que doCronActionA dispara uma vez por minuto enquanto doCronActionB dispara duas vezes mais, por exemplo, uma vez a cada 30 segundos.2020-05-20T19:16:05.259-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"
2020-05-20T19:16:05.255-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
2020-05-20T19:15:37.253-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
2020-05-20T19:15:09.250-07:00 [INFO] "doCronActionA ação do usuário controlada por recurring_event::1"
2020-05-20T19:15:09.249-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
2020-05-20T19:14:34.255-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
[OPCIONAL] Pausa / Editar JavaScript / Retomar
Basicamente, terminamos esta parte; sinta-se à vontade para experimentar e modificar as coisas e fazer experimentos, por exemplo:
Do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651 para expandir e expor os controles de função.
- Clique em Pausa.
- No Confirmar a função de pausa selecione "Função de pausa".
- Clique em "Editar JavaScript"
- Se estiver se sentindo confiante, modifique o doCronActionB para executar algumas operações de KV ou integrar-se ao cURL.
Se você quiser apenas ver uma alteração, adicione algo simples à função e tente algo como o seguinte:
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;} - Clique em Salvar.
- Para retornar à tela Eventing, clique no botão '< voltar para Eventing' (abaixo do editor) ou clique no link Eventos
- Clique em Currículo.
- No Confirmar função de retomada selecione "Retomar a função".
- Aguarde cerca de um minuto e a função cron_impl_2func_651 para implementar
- Clique no botão Registro link do implantado cron_impl_2func_651 Função de eventos.
2020-05-20T19:20:41.343-07:00 [INFO] "esta é a minha lógica, a = 1 +7 = 8"
2020-05-20T19:20:41.343-07:00 [INFO] "Ação do usuário doCronActionB controlada por recurring_event::2"
Limpeza
A limpeza envolve o cancelamento da implantação e a exclusão da função e, em seguida, a remoção dos dois buckets que você criou. Isso conclui o exemplo.
Remover funções
Do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651 para expandir e expor os controles de função.
- Clique em Desimplantação.
- No Confirmar a função de cancelar a implantação selecione "Função Undeploy".
- Aguarde a função cron_impl_2func_651 para cancelar a implantação.
- Clique em Excluir.
- No Confirmar a função de exclusão selecione "Excluir função".
Do Console da Web do Couchbase > Eventos tela:
- Clique no nome da função cron_impl_2func_651_help para expandir e expor os controles de função.
- Clique em Desimplantação.
- No Confirmar a função de cancelar a implantação selecione "Função Undeploy".
- Aguarde a função cron_impl_2func_651_help para cancelar a implantação.
- Clique em Excluir.
- No Confirmar a função de exclusão selecione "Excluir função".
Remover baldes
Próximo: "Solte os baldes".metadados' 'dados cadastrais', e 'amostra de viagem' (eles sempre podem ser recriados).
Do Console da Web do Couchbase > Buckets e clique no link Documents (Documentos) da página amostra de viagem balde.
- Clique em no nome do balde "metadados" para expandir e expor os controles
- Clique em Excluir
- No Confirmar Excluir compartimento selecione "Excluir balde".
- Clique em no nome do balde "dados cadastrais" para expandir e expor os controles
- Clique em Excluir
- No Confirmar Excluir compartimento selecione "Excluir balde".
- Clique em no nome do balde "amostra de viagem" para expandir e expor os controles
- Clique em Excluir
- No Confirmar Excluir compartimento selecione "Excluir balde".
Considerações finais
Espero que você tenha achado este passo a passo educativo e que tenha adquirido mais conhecimento sobre o Couchbase Eventing Service como um todo.
Anteriormente, observei que o Eventing foi projetado para processar mutações de alta velocidade (em milhões por segundo) a partir do fluxo DCP do bucket de origem associado à função. Essa função ou agendador de eventos só precisa reagir a alterações mínimas nos documentos do agendador.
Iniciei 5.000 programações com esse código inalterado apenas adicionando documentos de controle. Cheguei a executar 120.000 programações a cada minuto apenas para testar essa implementação (sim, 120.000 é uma quantidade absurda de programações independentes). cron programações a serem executadas). Além disso, coloquei 120.000 sc3hedules 2 dias no futuro para garantir que não tivéssemos problemas de recursos degenerados.
Sendo forçado a usar duas Evening Functions para criar o sistema de agendamento, a função principal cron_impl_2func_651 e um ajudante simples cron_impl_2func_651_help não foi tão elegante quanto eu esperava. Além disso, ter de contornar um vazamento de opções de balde disparando um cronômetro em cada vBucket foi, no mínimo, decepcionante. Felizmente, devido a esse esforço, foram implementadas mudanças e, na próxima versão 6.6.0, poderei criar um agendador mais limpo usando uma única Evening Function. Os principais aprimoramentos na versão 6.6 são: 1) a capacidade de gerar um novo cronômetro a partir de uma chamada de retorno de cronômetro; 2) a capacidade de cancelar ou substituir um cronômetro existente por referência; e 3) a eliminação do uso crescente de recursos em sistemas ociosos com cronômetros agendados no futuro.
Utilizei um balde autônomo 'dados cadastrais' para manter o cronograma ou os documentos de controle (não considero o uso de 'metadados', pois é um bloco de notas do sistema necessário para a Eventing e reservado para a própria Eventing) para oferecer a maior flexibilidade na manipulação de dados em outros blocos. Se eu tivesse colocado os documentos de controle em outro compartimento, não seria capaz de realizar operações N1QL nesse compartimento (já que ele é um compartimento de origem para a função Eventing) e estaria limitado apenas a operações KV para manipular dados no compartimento colocado.
Eu o desafio a experimentar outros casos de uso do cron e também a pensar em outras maneiras de aproveitar um serviço de agendamento:
- Verificação de uma contagem de itens em um grande conjunto de dados durante "horários fora de pico" e realização de limpeza incremental
- Realização de enriquecimento programado de documentos via N1QL.
- Recalcular carteiras de ações em um cronograma regular.
- Gerenciando TTL ou tempos de expiração via N1QL em uma programação recorrente, consulte Como gerenciar documentos TTL (Time-To-Live) com o Couchbase N1QL.
- Integrando com pontos de extremidade REST externos em um cronograma repetitivo, consulte Usando cURL com o Eventing Service: Atualização.
- Atualize o JavaScript de cron_impl_2func_651 para adicionar um novo campo "prev_astatus" ao objeto dinâmico JSON para salvar o sinalizador de resultado verdadeiro/falso retornado da ação do usuário executada anteriormente.
Atualizações
Este blog foi atualizado em 24 de julho de 2020 para adicionar uma solução alternativa para as versões 6.5.x que têm um número crescente de operações de balde de metadados que podem eventualmente bloquear mutações para uma determinada função Eventing ao criar temporizadores no futuro (como em uma hora ou mais) em um sistema ocioso.
Próximas etapas
Em algumas semanas "Implementação de um agendador robusto e portátil do tipo cron por meio do Couchbase Eventing (Parte 2)" será lançado, no qual exploraremos a execução de uma sequência de instruções N1QL dinâmicas orientadas pelo banco de dados sem a necessidade de editar a Eventing Function ou de definir um script de "ação" codificado dentro da Eventing Function.
Recursos
- Download: Download do Couchbase Server 6.5.1
- Função de eventos: cron_impl_2func_651.json
- Ajudante de eventos Função: cron_impl_2func_651_help.json
Referências
- Documentação do Couchbase Eventing:
https://docs.couchbase.com/server/current/eventing/eventing-overview.html - Couchbase Server 6.5 O que há de novo:
https://docs.couchbase.com/server/6.5/introduction/whats-new.html - Blogs do Couchbase sobre eventos:
https://www.couchbase.com/blog/tag/eventing/
Gostaríamos muito de saber se você gostou dos recursos da versão 6.5 e como ela beneficiará seus negócios daqui para frente. Compartilhe seu feedback nos comentários ou na seção Couchbase fórum.
Notas de rodapé
[1] A implementação do timer no Eventing Service foi projetada para lidar com um grande número de timers distribuídos na casa dos milhões em alta velocidade. Um único nó Eventing pode lidar com mais de 100 mil temporizadores por segundo e a única promessa é executar os temporizadores o mais rápido possível, ou seja, sem perda de temporizadores. Considere que o intervalo de varredura atual é de sete (7) segundos para coletar os temporizadores que estão prontos para disparar e, portanto, você deve esperar alguns atrasos. Para obter mais detalhes sobre o agendamento de temporizadores, consulte Temporizadores: Precisão do relógio de parede na documentação do Couchbase.
[2] Por meio de ajustes allow_interbucket_recursion para verdadeiro você está removendo as proteções que foram colocadas no servidor Couchbase para proteger contra a lógica de Eventing acidental, que pode iniciar loops recursivos infinitos. Não há nada de errado nisso, mas é fácil cometer um erro ao aproveitar a recursão. Nas versões 6.6 do Couchbase, o ajuste da lógica Eventing pode ser reduzido de duas (2) Eventing Functions para uma (1) Eventing Function simplificada, sem a necessidade de ajustar o allow_interbucket_recursion configuração.
[3] Existem duas grandes limitações. Primeiro, se um documento for modificado várias vezes em um curto período, as chamadas poderão ser agrupadas em um único evento devido à deduplicação. Segundo, não é possível discernir entre as operações de criação e atualização. Para as propostas de um cron nenhuma das limitações apresenta um problema.
[4] Por que eu não implementei a semântica exata do crontab? Eu poderia ter implementado, mas a quantidade de código é excessiva. https://github.com/kelektiv/node-cron juntamente com suas dependências de moment e moment-timezone (todos pacotes muito grandes). O getNextRecurringDate(hour_str, min_str) pode não ser tão flexível, mas é simples e abrange nosso caso de uso.
Estou muito interessado nas atualizações da versão 6.6.x para usar uma única função! Adoro essa implementação para um agendador do tipo cron.
Acabei de perceber que isso está disponível no GitHub! Vou dar uma olhada nisso! Recomendo atualizar esta postagem do blog com o link :) Obrigado!
Oi Alex,
Em breve, atualizarei o "agendador do tipo cron" com uma versão 6.6.0 e uma versão 6.6.1 (usando acessores avançados de bucket).
No entanto, provavelmente em um blog posterior (Parte 2), mas farei um link cruzado entre eles. Fico feliz que tenha encontrado meu protótipo 6.6.0 no GitHub.