UNPKG

37.4 kBMarkdownView Raw
1<p align="center">
2 <img src="https://cdn.jsdelivr.net/gh/agenda/agenda@master/agenda.svg" alt="Agenda" width="100" height="100">
3</p>
4<p align="center">
5 A light-weight job scheduling library for Node.js
6</p>
7
8# Agenda offers
9
10- Minimal overhead. Agenda aims to keep its code base small.
11- Mongo backed persistence layer.
12- Promises based API.
13- Scheduling with configurable priority, concurrency, repeating and persistence of job results.
14- Scheduling via cron or human readable syntax.
15- Event backed job queue that you can hook into.
16- [Agenda-rest](https://github.com/agenda/agenda-rest): optional standalone REST API.
17- [Inversify-agenda](https://github.com/lautarobock/inversify-agenda) - Some utilities for the development of agenda workers with Inversify.
18- [Agendash](https://github.com/agenda/agendash): optional standalone web-interface.
19
20### Feature Comparison
21
22Since there are a few job queue solutions, here a table comparing them to help you use the one that
23better suits your needs.
24
25Agenda is great if you need a MongoDB job scheduler, but try **[Bree](https://jobscheduler.net)** if you need something simpler (built by a previous maintainer).
26
27| Feature | Bull | Bee | Agenda |
28| :--------------- | :-------------: | :------: | :----: |
29| Backend | redis | redis | mongo |
30| Priorities | ✓ | | ✓ |
31| Concurrency | ✓ | ✓ | ✓ |
32| Delayed jobs | ✓ | | ✓ |
33| Global events | ✓ | | |
34| Rate Limiter | ✓ | | |
35| Pause/Resume | ✓ | | |
36| Sandboxed worker | ✓ | | |
37| Repeatable jobs | ✓ | | ✓ |
38| Atomic ops | ✓ | ✓ | |
39| Persistence | ✓ | ✓ | ✓ |
40| UI | ✓ | | ✓ |
41| REST API | | | ✓ |
42| Optimized for | Jobs / Messages | Messages | Jobs |
43
44_Kudos for making the comparison chart goes to [Bull](https://www.npmjs.com/package/bull#feature-comparison) maintainers._
45
46# Installation
47
48### Notice
49
50In order to support new MongoDB 5.0 and mongodb node.js driver/package the next release (5.x.x) of Agenda will be major. The required node version will become >=12. The mongodb dependency version will become >=3.2.
51
52Install via NPM
53
54 npm install agenda
55
56You will also need a working [Mongo](https://www.mongodb.com/) database (v3) to point it to.
57
58# CJS / Module Imports
59
60for regular javascript code, just use the default entrypoint
61
62```js
63const Agenda = require("agenda");
64```
65
66For Typescript, Webpack or other module imports, use `agenda/es` entrypoint:
67e.g.
68
69```ts
70import { Agenda } from "agenda/es";
71```
72
73**_NOTE_**: If you're migrating from `@types/agenda` you also should change imports to `agenda/es`.
74Instead of `import Agenda from 'agenda'` use `import Agenda from 'agenda/es'`.
75
76# Example Usage
77
78```js
79const mongoConnectionString = "mongodb://127.0.0.1/agenda";
80
81const agenda = new Agenda({ db: { address: mongoConnectionString } });
82
83// Or override the default collection name:
84// const agenda = new Agenda({db: {address: mongoConnectionString, collection: 'jobCollectionName'}});
85
86// or pass additional connection options:
87// const agenda = new Agenda({db: {address: mongoConnectionString, collection: 'jobCollectionName', options: {ssl: true}}});
88
89// or pass in an existing mongodb-native MongoClient instance
90// const agenda = new Agenda({mongo: myMongoClient});
91
92agenda.define("delete old users", async (job) => {
93 await User.remove({ lastLogIn: { $lt: twoDaysAgo } });
94});
95
96(async function () {
97 // IIFE to give access to async/await
98 await agenda.start();
99
100 await agenda.every("3 minutes", "delete old users");
101
102 // Alternatively, you could also do:
103 await agenda.every("*/3 * * * *", "delete old users");
104})();
105```
106
107```js
108agenda.define(
109 "send email report",
110 { priority: "high", concurrency: 10 },
111 async (job) => {
112 const { to } = job.attrs.data;
113 await emailClient.send({
114 to,
115 from: "example@example.com",
116 subject: "Email Report",
117 body: "...",
118 });
119 }
120);
121
122(async function () {
123 await agenda.start();
124 await agenda.schedule("in 20 minutes", "send email report", {
125 to: "admin@example.com",
126 });
127})();
128```
129
130```js
131(async function () {
132 const weeklyReport = agenda.create("send email report", {
133 to: "example@example.com",
134 });
135 await agenda.start();
136 await weeklyReport.repeatEvery("1 week").save();
137})();
138```
139
140# Full documentation
141
142Agenda's basic control structure is an instance of an agenda. Agenda's are
143mapped to a database collection and load the jobs from within.
144
145## Table of Contents
146
147- [Configuring an agenda](#configuring-an-agenda)
148- [Agenda Events](#agenda-events)
149- [Defining job processors](#defining-job-processors)
150- [Creating jobs](#creating-jobs)
151- [Managing jobs](#managing-jobs)
152- [Starting the job processor](#starting-the-job-processor)
153- [Multiple job processors](#multiple-job-processors)
154- [Manually working with jobs](#manually-working-with-a-job)
155- [Job Queue Events](#job-queue-events)
156- [Frequently asked questions](#frequently-asked-questions)
157- [Example Project structure](#example-project-structure)
158- [Known Issues](#known-issues)
159- [Debugging Issues](#debugging-issues)
160- [Acknowledgements](#acknowledgements)
161
162## Configuring an agenda
163
164All configuration methods are chainable, meaning you can do something like:
165
166```js
167const agenda = new Agenda();
168agenda
169 .database(...)
170 .processEvery('3 minutes')
171 ...;
172```
173
174Agenda uses [Human Interval](http://github.com/rschmukler/human-interval) for specifying the intervals. It supports the following units:
175
176`seconds`, `minutes`, `hours`, `days`,`weeks`, `months` -- assumes 30 days, `years` -- assumes 365 days
177
178More sophisticated examples
179
180```js
181agenda.processEvery("one minute");
182agenda.processEvery("1.5 minutes");
183agenda.processEvery("3 days and 4 hours");
184agenda.processEvery("3 days, 4 hours and 36 seconds");
185```
186
187### database(url, [collectionName])
188
189Specifies the database at the `url` specified. If no collection name is given,
190`agendaJobs` is used.
191
192```js
193agenda.database("localhost:27017/agenda-test", "agendaJobs");
194```
195
196You can also specify it during instantiation.
197
198```js
199const agenda = new Agenda({
200 db: { address: "localhost:27017/agenda-test", collection: "agendaJobs" },
201});
202```
203
204Agenda will emit a `ready` event (see [Agenda Events](#agenda-events)) when properly connected to the database.
205It is safe to call `agenda.start()` without waiting for this event, as this is handled internally.
206If you're using the `db` options, or call `database`, then you may still need to listen for `ready` before saving jobs.
207
208### mongo(dbInstance)
209
210Use an existing mongodb-native MongoClient/Db instance. This can help consolidate connections to a
211database. You can instead use `.database` to have agenda handle connecting for you.
212
213You can also specify it during instantiation:
214
215```js
216const agenda = new Agenda({ mongo: mongoClientInstance.db("agenda-test") });
217```
218
219Note that MongoClient.connect() returns a mongoClientInstance since [node-mongodb-native 3.0.0](https://github.com/mongodb/node-mongodb-native/blob/master/CHANGES_3.0.0.md), while it used to return a dbInstance that could then be directly passed to agenda.
220
221### name(name)
222
223Sets the `lastModifiedBy` field to `name` in the jobs collection.
224Useful if you have multiple job processors (agendas) and want to see which
225job queue last ran the job.
226
227```js
228agenda.name(os.hostname + "-" + process.pid);
229```
230
231You can also specify it during instantiation
232
233```js
234const agenda = new Agenda({ name: "test queue" });
235```
236
237### processEvery(interval)
238
239Takes a string `interval` which can be either a traditional javascript number,
240or a string such as `3 minutes`
241
242Specifies the frequency at which agenda will query the database looking for jobs
243that need to be processed. Agenda internally uses `setTimeout` to guarantee that
244jobs run at (close to ~3ms) the right time.
245
246Decreasing the frequency will result in fewer database queries, but more jobs
247being stored in memory.
248
249Also worth noting is that if the job queue is shutdown, any jobs stored in memory
250that haven't run will still be locked, meaning that you may have to wait for the
251lock to expire. By default it is `'5 seconds'`.
252
253```js
254agenda.processEvery("1 minute");
255```
256
257You can also specify it during instantiation
258
259```js
260const agenda = new Agenda({ processEvery: "30 seconds" });
261```
262
263### maxConcurrency(number)
264
265Takes a `number` which specifies the max number of jobs that can be running at
266any given moment. By default it is `20`.
267
268```js
269agenda.maxConcurrency(20);
270```
271
272You can also specify it during instantiation
273
274```js
275const agenda = new Agenda({ maxConcurrency: 20 });
276```
277
278### defaultConcurrency(number)
279
280Takes a `number` which specifies the default number of a specific job that can be running at
281any given moment. By default it is `5`.
282
283```js
284agenda.defaultConcurrency(5);
285```
286
287You can also specify it during instantiation
288
289```js
290const agenda = new Agenda({ defaultConcurrency: 5 });
291```
292
293### lockLimit(number)
294
295Takes a `number` which specifies the max number jobs that can be locked at any given moment. By default it is `0` for no max.
296
297```js
298agenda.lockLimit(0);
299```
300
301You can also specify it during instantiation
302
303```js
304const agenda = new Agenda({ lockLimit: 0 });
305```
306
307### defaultLockLimit(number)
308
309Takes a `number` which specifies the default number of a specific job that can be locked at any given moment. By default it is `0` for no max.
310
311```js
312agenda.defaultLockLimit(0);
313```
314
315You can also specify it during instantiation
316
317```js
318const agenda = new Agenda({ defaultLockLimit: 0 });
319```
320
321### defaultLockLifetime(number)
322
323Takes a `number` which specifies the default lock lifetime in milliseconds. By
324default it is 10 minutes. This can be overridden by specifying the
325`lockLifetime` option to a defined job.
326
327A job will unlock if it is finished (ie. the returned Promise resolves/rejects
328or `done` is specified in the params and `done()` is called) before the
329`lockLifetime`. The lock is useful if the job crashes or times out.
330
331```js
332agenda.defaultLockLifetime(10000);
333```
334
335You can also specify it during instantiation
336
337```js
338const agenda = new Agenda({ defaultLockLifetime: 10000 });
339```
340
341### sort(query)
342
343Takes a `query` which specifies the sort query to be used for finding and locking the next job.
344
345By default it is `{ nextRunAt: 1, priority: -1 }`, which obeys a first in first out approach, with respect to priority.
346
347### disableAutoIndex(boolean)
348
349Optional. Disables the automatic creation of the default index on the jobs table.
350By default, Agenda creates an index to optimize its queries against Mongo while processing jobs.
351
352This is useful if you want to use your own index in specific use-cases.
353
354## Agenda Events
355
356An instance of an agenda will emit the following events:
357
358- `ready` - called when Agenda mongo connection is successfully opened and indices created.
359 If you're passing agenda an existing connection, you shouldn't need to listen for this, as `agenda.start()` will not resolve until indices have been created.
360 If you're using the `db` options, or call `database`, then you may still need to listen for the `ready` event before saving jobs. `agenda.start()` will still wait for the connection to be opened.
361- `error` - called when Agenda mongo connection process has thrown an error
362
363```js
364await agenda.start();
365```
366
367## Defining Job Processors
368
369Before you can use a job, you must define its processing behavior.
370
371### define(jobName, [options], handler)
372
373Defines a job with the name of `jobName`. When a job of `jobName` gets run, it
374will be passed to `handler(job, done)`. To maintain asynchronous behavior, you may
375either provide a Promise-returning function in `handler` _or_ provide `done` as a
376second parameter to `handler`. If `done` is specified in the function signature, you
377must call `done()` when you are processing the job. If your function is
378synchronous or returns a Promise, you may omit `done` from the signature.
379
380`options` is an optional argument which can overwrite the defaults. It can take
381the following:
382
383- `concurrency`: `number` maximum number of that job that can be running at once (per instance of agenda)
384- `lockLimit`: `number` maximum number of that job that can be locked at once (per instance of agenda)
385- `lockLifetime`: `number` interval in ms of how long the job stays locked for (see [multiple job processors](#multiple-job-processors) for more info).
386 A job will automatically unlock once a returned promise resolves/rejects (or if `done` is specified in the signature and `done()` is called).
387- `priority`: `(lowest|low|normal|high|highest|number)` specifies the priority
388 of the job. Higher priority jobs will run first. See the priority mapping
389 below
390- `shouldSaveResult`: `boolean` flag that specifies whether the result of the job should also be stored in the database. Defaults to false
391
392Priority mapping:
393
394```
395{
396 highest: 20,
397 high: 10,
398 normal: 0,
399 low: -10,
400 lowest: -20
401}
402```
403
404Async Job:
405
406```js
407agenda.define("some long running job", async (job) => {
408 const data = await doSomelengthyTask();
409 await formatThatData(data);
410 await sendThatData(data);
411});
412```
413
414Async Job (using `done`):
415
416```js
417agenda.define("some long running job", (job, done) => {
418 doSomelengthyTask((data) => {
419 formatThatData(data);
420 sendThatData(data);
421 done();
422 });
423});
424```
425
426Sync Job:
427
428```js
429agenda.define("say hello", (job) => {
430 console.log("Hello!");
431});
432```
433
434`define()` acts like an assignment: if `define(jobName, ...)` is called multiple times (e.g. every time your script starts), the definition in the last call will overwrite the previous one. Thus, if you `define` the `jobName` only once in your code, it's safe for that call to execute multiple times.
435
436## Creating Jobs
437
438### every(interval, name, [data], [options])
439
440Runs job `name` at the given `interval`. Optionally, data and options can be passed in.
441Every creates a job of type `single`, which means that it will only create one
442job in the database, even if that line is run multiple times. This lets you put
443it in a file that may get run multiple times, such as `webserver.js` which may
444reboot from time to time.
445
446`interval` can be a human-readable format `String`, a cron format `String`, or a `Number`.
447
448`data` is an optional argument that will be passed to the processing function
449under `job.attrs.data`.
450
451`options` is an optional argument that will be passed to [`job.repeatEvery`](#repeateveryinterval-options).
452In order to use this argument, `data` must also be specified.
453
454Returns the `job`.
455
456```js
457agenda.define("printAnalyticsReport", async (job) => {
458 const users = await User.doSomethingReallyIntensive();
459 processUserData(users);
460 console.log("I print a report!");
461});
462
463agenda.every("15 minutes", "printAnalyticsReport");
464```
465
466Optionally, `name` could be array of job names, which is convenient for scheduling
467different jobs for same `interval`.
468
469```js
470agenda.every("15 minutes", [
471 "printAnalyticsReport",
472 "sendNotifications",
473 "updateUserRecords",
474]);
475```
476
477In this case, `every` returns array of `jobs`.
478
479### schedule(when, name, [data])
480
481Schedules a job to run `name` once at a given time. `when` can be a `Date` or a
482`String` such as `tomorrow at 5pm`.
483
484`data` is an optional argument that will be passed to the processing function
485under `job.attrs.data`.
486
487Returns the `job`.
488
489```js
490agenda.schedule("tomorrow at noon", "printAnalyticsReport", { userCount: 100 });
491```
492
493Optionally, `name` could be array of job names, similar to the `every` method.
494
495```js
496agenda.schedule("tomorrow at noon", [
497 "printAnalyticsReport",
498 "sendNotifications",
499 "updateUserRecords",
500]);
501```
502
503In this case, `schedule` returns array of `jobs`.
504
505### now(name, [data])
506
507Schedules a job to run `name` once immediately.
508
509`data` is an optional argument that will be passed to the processing function
510under `job.attrs.data`.
511
512Returns the `job`.
513
514```js
515agenda.now("do the hokey pokey");
516```
517
518### create(jobName, data)
519
520Returns an instance of a `jobName` with `data`. This does _NOT_ save the job in
521the database. See below to learn how to manually work with jobs.
522
523```js
524const job = agenda.create("printAnalyticsReport", { userCount: 100 });
525await job.save();
526console.log("Job successfully saved");
527```
528
529## Managing Jobs
530
531### jobs(mongodb-native query, mongodb-native sort, mongodb-native limit, mongodb-native skip)
532
533Lets you query (then sort, limit and skip the result) all of the jobs in the agenda job's database. These are full [mongodb-native](https://github.com/mongodb/node-mongodb-native) `find`, `sort`, `limit` and `skip` commands. See mongodb-native's documentation for details.
534
535```js
536const jobs = await agenda.jobs(
537 { name: "printAnalyticsReport" },
538 { data: -1 },
539 3,
540 1
541);
542// Work with jobs (see below)
543```
544
545### cancel(mongodb-native query)
546
547Cancels any jobs matching the passed mongodb-native query, and removes them from the database. Returns a Promise resolving to the number of cancelled jobs, or rejecting on error.
548
549```js
550const numRemoved = await agenda.cancel({ name: "printAnalyticsReport" });
551```
552
553This functionality can also be achieved by first retrieving all the jobs from the database using `agenda.jobs()`, looping through the resulting array and calling `job.remove()` on each. It is however preferable to use `agenda.cancel()` for this use case, as this ensures the operation is atomic.
554
555### disable(mongodb-native query)
556
557Disables any jobs matching the passed mongodb-native query, preventing any matching jobs from being run by the Job Processor.
558
559```js
560const numDisabled = await agenda.disable({ name: "pollExternalService" });
561```
562
563Similar to `agenda.cancel()`, this functionality can be acheived with a combination of `agenda.jobs()` and `job.disable()`
564
565### enable(mongodb-native query)
566
567Enables any jobs matching the passed mongodb-native query, allowing any matching jobs to be run by the Job Processor.
568
569```js
570const numEnabled = await agenda.enable({ name: "pollExternalService" });
571```
572
573Similar to `agenda.cancel()`, this functionality can be acheived with a combination of `agenda.jobs()` and `job.enable()`
574
575### purge()
576
577Removes all jobs in the database without defined behaviors. Useful if you change a definition name and want to remove old jobs. Returns a Promise resolving to the number of removed jobs, or rejecting on error.
578
579_IMPORTANT:_ Do not run this before you finish defining all of your jobs. If you do, you will nuke your database of jobs.
580
581```js
582const numRemoved = await agenda.purge();
583```
584
585## Starting the job processor
586
587To get agenda to start processing jobs from the database you must start it. This
588will schedule an interval (based on `processEvery`) to check for new jobs and
589run them. You can also stop the queue.
590
591### start
592
593Starts the job queue processing, checking [`processEvery`](#processeveryinterval) time to see if there
594are new jobs. Must be called _after_ `processEvery`, and _before_ any job scheduling (e.g. `every`).
595
596### stop
597
598Stops the job queue processing. Unlocks currently running jobs.
599
600This can be very useful for graceful shutdowns so that currently running/grabbed jobs are abandoned so that other
601job queues can grab them / they are unlocked should the job queue start again. Here is an example of how to do a graceful
602shutdown.
603
604```js
605async function graceful() {
606 await agenda.stop();
607 process.exit(0);
608}
609
610process.on("SIGTERM", graceful);
611process.on("SIGINT", graceful);
612```
613
614### drain
615
616Stops the job queue processing and waits till all current jobs finishes.
617
618This can be very useful for graceful shutdowns so that currently running/grabbed jobs are finished before shutting down. Here is an example of how to do a graceful
619shutdown.
620
621```js
622async function graceful() {
623 await agenda.drain();
624 process.exit(0);
625}
626
627process.on("SIGTERM", graceful);
628process.on("SIGINT", graceful);
629```
630
631### close(force)
632
633Closes database connection. You don't normally have to do this, but it might be useful for testing purposes.
634
635Using `force` boolean you can force close connection.
636
637Read more from [Node.js MongoDB Driver API](https://mongodb.github.io/node-mongodb-native/2.0/api/Db.html#close)
638
639```js
640await agenda.close({ force: true });
641```
642
643## Multiple job processors
644
645Sometimes you may want to have multiple node instances / machines process from
646the same queue. Agenda supports a locking mechanism to ensure that multiple
647queues don't process the same job.
648
649You can configure the locking mechanism by specifying `lockLifetime` as an
650interval when defining the job.
651
652```js
653agenda.define("someJob", { lockLifetime: 10000 }, (job, cb) => {
654 // Do something in 10 seconds or less...
655});
656```
657
658This will ensure that no other job processor (this one included) attempts to run the job again
659for the next 10 seconds. If you have a particularly long running job, you will want to
660specify a longer lockLifetime.
661
662By default it is 10 minutes. Typically you shouldn't have a job that runs for 10 minutes,
663so this is really insurance should the job queue crash before the job is unlocked.
664
665When a job is finished (i.e. the returned promise resolves/rejects or `done` is
666specified in the signature and `done()` is called), it will automatically unlock.
667
668## Manually working with a job
669
670A job instance has many instance methods. All mutating methods must be followed
671with a call to `await job.save()` in order to persist the changes to the database.
672
673### repeatEvery(interval, [options])
674
675Specifies an `interval` on which the job should repeat. The job runs at the time of defining as well in configured intervals, that is "run _now_ and in intervals".
676
677`interval` can be a human-readable format `String`, a cron format `String`, or a `Number`.
678
679`options` is an optional argument containing:
680
681`options.timezone`: should be a string as accepted by [moment-timezone](https://momentjs.com/timezone/) and is considered when using an interval in the cron string format.
682
683`options.skipImmediate`: `true` | `false` (default) Setting this `true` will skip the immediate run. The first run will occur only in configured interval.
684
685`options.startDate`: `Date` the first time the job runs, should be equal or after the start date.
686
687`options.endDate`: `Date` the job should not repeat after the endDate. The job can run on the end-date itself, but not after that.
688
689`options.skipDays`: `human readable string` ('2 days'). After each run, it will skip the duration of 'skipDays'
690
691```js
692job.repeatEvery("10 minutes");
693await job.save();
694```
695
696```js
697job.repeatEvery("3 minutes", {
698 skipImmediate: true,
699});
700await job.save();
701```
702
703```js
704job.repeatEvery("0 6 * * *", {
705 timezone: "America/New_York",
706});
707await job.save();
708```
709
710### repeatAt(time)
711
712Specifies a `time` when the job should repeat. [Possible values](https://github.com/matthewmueller/date#examples)
713
714```js
715job.repeatAt("3:30pm");
716await job.save();
717```
718
719### schedule(time)
720
721Specifies the next `time` at which the job should run.
722
723```js
724job.schedule("tomorrow at 6pm");
725await job.save();
726```
727
728### priority(priority)
729
730Specifies the `priority` weighting of the job. Can be a number or a string from
731the above priority table.
732
733```js
734job.priority("low");
735await job.save();
736```
737
738### setShouldSaveResult(setShouldSaveResult)
739
740Specifies whether the result of the job should also be stored in the database. Defaults to false.
741
742```js
743job.setShouldSaveResult(true);
744await job.save();
745```
746
747The data returned by the job will be available on the `result` attribute after it succeeded and got retrieved again from the database, e.g. via `agenda.jobs(...)` or through the [success job event](#agenda-events)).
748
749### unique(properties, [options])
750
751Ensure that only one instance of this job exists with the specified properties
752
753`options` is an optional argument which can overwrite the defaults. It can take
754the following:
755
756- `insertOnly`: `boolean` will prevent any properties from persisting if the job already exists. Defaults to false.
757
758```js
759job.unique({ "data.type": "active", "data.userId": "123", nextRunAt: date });
760await job.save();
761```
762
763_IMPORTANT:_ To [guarantee uniqueness](https://docs.mongodb.com/manual/reference/method/db.collection.update/#use-unique-indexes) as well as avoid high CPU usage by MongoDB make sure to create a unique index on the used fields, like `name`, `data.type` and `data.userId` for the example above.
764
765### fail(reason)
766
767Sets `job.attrs.failedAt` to `now`, and sets `job.attrs.failReason` to `reason`.
768
769Optionally, `reason` can be an error, in which case `job.attrs.failReason` will
770be set to `error.message`
771
772```js
773job.fail("insufficient disk space");
774// or
775job.fail(new Error("insufficient disk space"));
776await job.save();
777```
778
779### run(callback)
780
781Runs the given `job` and calls `callback(err, job)` upon completion. Normally
782you never need to call this manually.
783
784```js
785job.run((err, job) => {
786 console.log("I don't know why you would need to do this...");
787});
788```
789
790### save()
791
792Saves the `job.attrs` into the database. Returns a Promise resolving to a Job instance, or rejecting on error.
793
794```js
795try {
796 await job.save();
797 console.log("Successfully saved job to collection");
798} catch (e) {
799 console.error("Error saving job to collection");
800}
801```
802
803### remove()
804
805Removes the `job` from the database. Returns a Promise resolving to the number of jobs removed, or rejecting on error.
806
807```js
808try {
809 await job.remove();
810 console.log("Successfully removed job from collection");
811} catch (e) {
812 console.error("Error removing job from collection");
813}
814```
815
816### disable()
817
818Disables the `job`. Upcoming runs won't execute.
819
820### enable()
821
822Enables the `job` if it got disabled before. Upcoming runs will execute.
823
824### touch()
825
826Resets the lock on the job. Useful to indicate that the job hasn't timed out
827when you have very long running jobs. The call returns a promise that resolves
828when the job's lock has been renewed.
829
830```js
831agenda.define("super long job", async (job) => {
832 await doSomeLongTask();
833 await job.touch();
834 await doAnotherLongTask();
835 await job.touch();
836 await finishOurLongTasks();
837});
838```
839
840## Job Queue Events
841
842An instance of an agenda will emit the following events:
843
844- `start` - called just before a job starts
845- `start:job name` - called just before the specified job starts
846
847```js
848agenda.on("start", (job) => {
849 console.log("Job %s starting", job.attrs.name);
850});
851```
852
853- `complete` - called when a job finishes, regardless of if it succeeds or fails
854- `complete:job name` - called when a job finishes, regardless of if it succeeds or fails
855
856```js
857agenda.on("complete", (job) => {
858 console.log(`Job ${job.attrs.name} finished`);
859});
860```
861
862- `success` - called when a job finishes successfully
863- `success:job name` - called when a job finishes successfully
864
865```js
866agenda.on("success:send email", (job) => {
867 console.log(`Sent Email Successfully to ${job.attrs.data.to}`);
868});
869```
870
871- `fail` - called when a job throws an error
872- `fail:job name` - called when a job throws an error
873
874```js
875agenda.on("fail:send email", (err, job) => {
876 console.log(`Job failed with error: ${err.message}`);
877});
878```
879
880## Frequently Asked Questions
881
882### What is the order in which jobs run?
883
884Jobs are run with priority in a first in first out order (so they will be run in the order they were scheduled AND with respect to highest priority).
885
886For example, if we have two jobs named "send-email" queued (both with the same priority), and the first job is queued at 3:00 PM and second job is queued at 3:05 PM with the same `priority` value, then the first job will run first if we start to send "send-email" jobs at 3:10 PM. However if the first job has a priority of `5` and the second job has a priority of `10`, then the second will run first (priority takes precedence) at 3:10 PM.
887
888The default [MongoDB sort object](https://docs.mongodb.com/manual/reference/method/cursor.sort/) is `{ nextRunAt: 1, priority: -1 }` and can be changed through the option `sort` when configuring Agenda.
889
890### What is the difference between `lockLimit` and `maxConcurrency`?
891
892Agenda will lock jobs 1 by one, setting the `lockedAt` property in mongoDB, and creating an instance of the `Job` class which it caches into the `_lockedJobs` array. This defaults to having no limit, but can be managed using lockLimit. If all jobs will need to be run before agenda's next interval (set via `agenda.processEvery`), then agenda will attempt to lock all jobs.
893
894Agenda will also pull jobs from `_lockedJobs` and into `_runningJobs`. These jobs are actively being worked on by user code, and this is limited by `maxConcurrency` (defaults to 20).
895
896If you have multiple instances of agenda processing the same job definition with a fast repeat time you may find they get unevenly loaded. This is because they will compete to lock as many jobs as possible, even if they don't have enough concurrency to process them. This can be resolved by tweaking the `maxConcurrency` and `lockLimit` properties.
897
898### Sample Project Structure?
899
900Agenda doesn't have a preferred project structure and leaves it to the user to
901choose how they would like to use it. That being said, you can check out the
902[example project structure](#example-project-structure) below.
903
904### Can I Donate?
905
906Thanks! I'm flattered, but it's really not necessary. If you really want to, you can find my [gittip here](https://www.gittip.com/rschmukler/).
907
908### Web Interface?
909
910Agenda itself does not have a web interface built in but we do offer stand-alone web interface [Agendash](https://github.com/agenda/agendash):
911
912<a href="https://raw.githubusercontent.com/agenda/agendash/master/job-details.png"><img src="https://raw.githubusercontent.com/agenda/agendash/master/job-details.png" style="max-width:100%" alt="Agendash interface"></a>
913
914### Mongo vs Redis
915
916The decision to use Mongo instead of Redis is intentional. Redis is often used for
917non-essential data (such as sessions) and without configuration doesn't
918guarantee the same level of persistence as Mongo (should the server need to be
919restarted/crash).
920
921Agenda decides to focus on persistence without requiring special configuration
922of Redis (thereby degrading the performance of the Redis server on non-critical
923data, such as sessions).
924
925Ultimately if enough people want a Redis driver instead of Mongo, I will write
926one. (Please open an issue requesting it). For now, Agenda decided to focus on
927guaranteed persistence.
928
929### Spawning / forking processes
930
931Ultimately Agenda can work from a single job queue across multiple machines, node processes, or forks. If you are interested in having more than one worker, [Bars3s](http://github.com/bars3s) has written up a fantastic example of how one might do it:
932
933```js
934const cluster = require("cluster");
935const os = require("os");
936
937const httpServer = require("./app/http-server");
938const jobWorker = require("./app/job-worker");
939
940const jobWorkers = [];
941const webWorkers = [];
942
943if (cluster.isMaster) {
944 const cpuCount = os.cpus().length;
945 // Create a worker for each CPU
946 for (let i = 0; i < cpuCount; i += 1) {
947 addJobWorker();
948 addWebWorker();
949 }
950
951 cluster.on("exit", (worker, code, signal) => {
952 if (jobWorkers.indexOf(worker.id) !== -1) {
953 console.log(
954 `job worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...`
955 );
956 removeJobWorker(worker.id);
957 addJobWorker();
958 }
959
960 if (webWorkers.indexOf(worker.id) !== -1) {
961 console.log(
962 `http worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...`
963 );
964 removeWebWorker(worker.id);
965 addWebWorker();
966 }
967 });
968} else {
969 if (process.env.web) {
970 console.log(`start http server: ${cluster.worker.id}`);
971 // Initialize the http server here
972 httpServer.start();
973 }
974
975 if (process.env.job) {
976 console.log(`start job server: ${cluster.worker.id}`);
977 // Initialize the Agenda here
978 jobWorker.start();
979 }
980}
981
982function addWebWorker() {
983 webWorkers.push(cluster.fork({ web: 1 }).id);
984}
985
986function addJobWorker() {
987 jobWorkers.push(cluster.fork({ job: 1 }).id);
988}
989
990function removeWebWorker(id) {
991 webWorkers.splice(webWorkers.indexOf(id), 1);
992}
993
994function removeJobWorker(id) {
995 jobWorkers.splice(jobWorkers.indexOf(id), 1);
996}
997```
998
999### Recovering lost Mongo connections ("auto_reconnect")
1000
1001Agenda is configured by default to automatically reconnect indefinitely, emitting an [error event](#agenda-events)
1002when no connection is available on each [process tick](#processeveryinterval), allowing you to restore the Mongo
1003instance without having to restart the application.
1004
1005However, if you are using an [existing Mongo client](#mongomongoclientinstance)
1006you'll need to configure the `reconnectTries` and `reconnectInterval` [connection settings](http://mongodb.github.io/node-mongodb-native/3.0/reference/connecting/connection-settings/)
1007manually, otherwise you'll find that Agenda will throw an error with the message "MongoDB connection is not recoverable,
1008application restart required" if the connection cannot be recovered within 30 seconds.
1009
1010# Example Project Structure
1011
1012Agenda will only process jobs that it has definitions for. This allows you to
1013selectively choose which jobs a given agenda will process.
1014
1015Consider the following project structure, which allows us to share models with
1016the rest of our code base, and specify which jobs a worker processes, if any at
1017all.
1018
1019```
1020- server.js
1021- worker.js
1022lib/
1023 - agenda.js
1024 controllers/
1025 - user-controller.js
1026 jobs/
1027 - email.js
1028 - video-processing.js
1029 - image-processing.js
1030 models/
1031 - user-model.js
1032 - blog-post.model.js
1033```
1034
1035Sample job processor (eg. `jobs/email.js`)
1036
1037```js
1038let email = require("some-email-lib"),
1039 User = require("../models/user-model.js");
1040
1041module.exports = function (agenda) {
1042 agenda.define("registration email", async (job) => {
1043 const user = await User.get(job.attrs.data.userId);
1044 await email(
1045 user.email(),
1046 "Thanks for registering",
1047 "Thanks for registering " + user.name()
1048 );
1049 });
1050
1051 agenda.define("reset password", async (job) => {
1052 // Etc
1053 });
1054
1055 // More email related jobs
1056};
1057```
1058
1059lib/agenda.js
1060
1061```js
1062const Agenda = require("agenda");
1063
1064const connectionOpts = {
1065 db: { address: "localhost:27017/agenda-test", collection: "agendaJobs" },
1066};
1067
1068const agenda = new Agenda(connectionOpts);
1069
1070const jobTypes = process.env.JOB_TYPES ? process.env.JOB_TYPES.split(",") : [];
1071
1072jobTypes.forEach((type) => {
1073 require("./jobs/" + type)(agenda);
1074});
1075
1076if (jobTypes.length) {
1077 agenda.start(); // Returns a promise, which should be handled appropriately
1078}
1079
1080module.exports = agenda;
1081```
1082
1083lib/controllers/user-controller.js
1084
1085```js
1086let app = express(),
1087 User = require("../models/user-model"),
1088 agenda = require("../worker.js");
1089
1090app.post("/users", (req, res, next) => {
1091 const user = new User(req.body);
1092 user.save((err) => {
1093 if (err) {
1094 return next(err);
1095 }
1096 agenda.now("registration email", { userId: user.primary() });
1097 res.send(201, user.toJson());
1098 });
1099});
1100```
1101
1102worker.js
1103
1104```js
1105require("./lib/agenda.js");
1106```
1107
1108Now you can do the following in your project:
1109
1110```bash
1111node server.js
1112```
1113
1114Fire up an instance with no `JOB_TYPES`, giving you the ability to process jobs,
1115but not wasting resources processing jobs.
1116
1117```bash
1118JOB_TYPES=email node server.js
1119```
1120
1121Allow your http server to process email jobs.
1122
1123```bash
1124JOB_TYPES=email node worker.js
1125```
1126
1127Fire up an instance that processes email jobs.
1128
1129```bash
1130JOB_TYPES=video-processing,image-processing node worker.js
1131```
1132
1133Fire up an instance that processes video-processing/image-processing jobs. Good for a heavy hitting server.
1134
1135# Debugging Issues
1136
1137If you think you have encountered a bug, please feel free to report it here:
1138
1139[Submit Issue](https://github.com/agenda/agenda/issues/new)
1140
1141Please provide us with as much details as possible such as:
1142
1143- Agenda version
1144- Environment (OSX, Linux, Windows, etc)
1145- Small description of what happened
1146- Any relevant stack track
1147- Agenda logs (see below)
1148
1149#### To turn on logging, please set your DEBUG env variable like so:
1150
1151- OSX: `DEBUG="agenda:*" ts-node src/index.js`
1152- Linux: `DEBUG="agenda:*" ts-node src/index.js`
1153- Windows CMD: `set DEBUG=agenda:*`
1154- Windows PowerShell: `$env:DEBUG = "agenda:*"`
1155
1156While not necessary, attaching a text file with this debug information would
1157be extremely useful in debugging certain issues and is encouraged.
1158
1159# Known Issues
1160
1161#### "Multiple order-by items are not supported. Please specify a single order-by item."
1162
1163When running Agenda on Azure cosmosDB, you might run into this issue caused by Agenda's sort query used for finding and locking the next job. To fix this, you can pass [custom sort option](https://github.com/agenda/agenda#sortquery): `sort: { nextRunAt: 1 }`
1164
1165# Acknowledgements
1166
1167- Agenda was originally created by [@rschmukler](https://github.com/rschmukler).
1168- [Agendash](https://github.com/agenda/agendash) was originally created by [@joeframbach](https://github.com/joeframbach).
1169- These days Agenda has a great community of [contributors](https://github.com/agenda/agenda/graphs/contributors) around it. [Join us!](https://github.com/agenda/agenda/wiki)
1170
1171# License
1172
1173[The MIT License](LICENSE.md)