This content originally appeared on SitePoint and was authored by Craig Buckler
This tutorial explains and demonstrates queuing systems. Queues are often used to process long-running tasks such as email newsletter delivery.
It’s not always practical to execute a task the moment it’s requested.
Consider an email newsletter administration system. After writing, an administrator must hit a big red “SEND NOW” button. The application could send every email immediately and show a “completed” response. That would work for a dozen messages, but how long would it take for 1,000 subscribers or more? The browser request would time out before the process completed.
Another example: a user can upload any number of photographs to a gallery application. The system resizes and sharpens each image for alternative dimensions. This process could run on upload, but it would to incur a delay for every image.
It’s more effective to decouple tasks in these situations. The user receives an instant response but task processing occurs in the background. Other applications or servers handle tasks and schedule re-attempts on failure. The user can receive alerts or examine logs to determine progress.
Queues
A queue is a data structure which holds a collection of items:
- Any process can send (or enqueue) an item at any time — such as send newsletter X to recipient Y.
- Any process can receive (or dequeue) the item at the front of the queue — for example, the item that’s been in the queue for longest.
Queues are a first-in-first-out (FIFO) structure. The first item added to the queue will be the first out.
Implementing a Basic JavaScript Queue
You can create a queue using a JavaScript array. The push()
method adds an item to the end of an Array while the shift()
method removes and returns an item from the start:
const queue = [];
queue.push( 'item 1' );
queue.push( 'item 2' );
console.log( queue.shift() ); // item 1
console.log( queue.shift() ); // item 2
console.log( queue.shift() ); // undefined
Individual array elements can hold any data. You can push strings, numbers, Booleans, other arrays, or objects.
You can use an ES6 class to define any number of separate queues:
class Queue {
constructor() { this.q = []; }
send( item ) { this.q.push( item ); }
receive() { return this.q.shift(); }
}
// define two queues
const q1 = new Queue();
const q2 = new Queue();
q1.send('item 1');
q2.send('item 2');
console.log( q1.receive() ); // item 1
console.log( q1.receive() ); // undefined
console.log( q2.receive() ); // item 2
These simple examples may be useful for less critical client-side code such as queuing UI updates so processing occurs in a single DOM update. localStorage or IndexedDB can offer a level of data persistence if necessary.
Queuing Platforms
In-memory queues are less practical for complex server applications:
- Two or more separate applications can’t (easily) access the same queue.
- Queue data disappears when the application terminates.
Purpose built message-broker software provides more robust queuing. Platforms vary, but offer features such as:
- data persistence in a choice of databases with replication, sharding, and clustering options
- a range of access protocols, often including HTTP and Web Sockets
- any number of separate queues
- delayed messaging, where message processing can occur at a later time
- transaction-like support, where a message is re-queued when processing isn’t confirmed
- publish-subscribe patterns, where applications receive an event when a new item appears on a queue
Message-broker software includes Redis, RabbitMQ, Apache ActiveMQ, and Gearman. Cloud messaging services include Amazon SQS, Azure Service Bus, and Google Pub/Sub.
These may be viable options for enterprise-level applications. Yet they could be overkill if you have simpler requirements and already use a database.
Use MongoDB as a Message Broker
It’s possible to develop a sophisticated queuing system in a couple of hundred lines of Node.js code.
The queue-mongodb
module described here uses MongoDB for data storage, but the same concepts could be adopted by any SQL or NoSQL database. The code is available on GitHub and npm.
Quick Start
Make sure you have Node.js 14 or above installed, then create a new project folder such as queue-test
. Add a new package.json
file:
{
"name": "queue-test",
"version": "1.0.0",
"description": "Queue test",
"type": "module",
"scripts": {
"send": "node ./send.js",
"receive": "node ./receive.js"
}
}
Note: "type": "module"
configures the project to use ES6 modules. The "scripts"
will send and receive queued items.
Install the queue-mongodb module:
npm install @craigbuckler/queue-mongodb
Then create a .env
file with your MongoDB database connection credentials. For example:
QUEUE_DB_HOST=localhost
QUEUE_DB_PORT=27017
QUEUE_DB_USER=root
QUEUE_DB_PASS=mysecret
QUEUE_DB_NAME=qdb
QUEUE_DB_COLL=queue
Note: this creates a queue
collection (QUEUE_DB_COLL
) in the qdb
database (QUEUE_DB_NAME
). You can use an existing database, but make sure the collection doesn’t clash with another.
Database read/write access must be granted to the user root
(QUEUE_DB_USER
) with password mysecret
(QUEUE_DB_PASS
). Set both values blank if no authentication is required.
Start a MongoDB database if it’s not already running. Those with Docker and Docker Compose can create a new docker-compose.yml
file:
version: '3'
services:
queuedb:
environment:
- MONGO_INITDB_ROOT_USERNAME=${QUEUE_DB_USER}
- MONGO_INITDB_ROOT_PASSWORD=${QUEUE_DB_PASS}
image: mongo:4.4-bionic
container_name: queuedb
volumes:
- queuedata:/data/db
ports:
- "${QUEUE_DB_PORT}:${QUEUE_DB_PORT}"
restart: always
volumes:
queuedata:
Then run docker-compose up
to download and start MongoDB with a persistent data volume.
Docker is available Linux, macOS, and Windows 10. See the Docker installation instructions.
Create a new send.js
file to add a randomly generated email messages to a queue named news
:
// Queue module
import { Queue } from '@craigbuckler/queue-mongodb';
// initialize queue named 'news'
const newsQ = new Queue('news');
// random name
const name = String.fromCharCode(65 + Math.random() * 26).repeat(1 + Math.random() * 10);
// add object to queue
const send = await newsQ.send({
name: name,
email: `${ name.toLowerCase() }@test.com`,
date: new Date(),
message: `Hey there, ${ name }!`
});
console.log('send', send);
// get number of items remaining in queue
console.log('items queued:', await newsQ.count());
// close connection and quit
await newsQ.close();
Execute it with npm run send
and you’ll see output such as this:
send {
_id: 607d692563bd6d05bb459931,
sent: 2021-04-19T11:27:33.000Z,
data: {
name: 'AAA',
email: 'aaa@test.com',
date: 2021-04-19T11:27:33.426Z,
message: 'Hey there, AAA!'
}
}
items queued: 1
The .send()
method returns an qItem
object containing:
- the MongoDB document
_id
- the date/time the item was originally queued, and
- a copy of the message
data
Run the script any number of times to add further items to the queue. The items queued
will increment on every run.
Now create a new receive.js
file to retrieve messages from the same queue:
// Queue module
import { Queue } from '@craigbuckler/queue-mongodb';
// initialize queue named 'news'
const newsQ = new Queue('news');
let qItem;
do {
qItem = await newsQ.receive();
if (qItem) {
console.log('\nreceive', qItem);
// ... process qItem.data ...
// ... to send email ...
}
} while (qItem);
// number of items remaining in queue
console.log('items queued:', await newsQ.count());
await newsQ.close();
Run npm run receive
to fetch and process queued items:
receive {
_id: 607d692563bd6d05bb459931,
sent: 2021-04-19T11:27:33.000Z,
data: {
name: 'AAA',
email: 'aaa@test.com',
date: 2021-04-19T11:27:33.426Z,
message: 'Hey there, AAA!'
}
}
items queued: 0
No email is sent in this example, but that could be implemented using Nodemailer or another suitable module.
If processing fails — perhaps because the mail server is down — an item can be re-queued with this:
newsQ.send( qItem.data, 600 );
The second 600
argument is an optional number of seconds or future date. This command re-queues the item after 600 seconds (ten minutes) have elapsed.
This is a simple example, but any application can send data to any number of queues. Another process, perhaps started as a cron
job, can receive and process items when necessary.
Continue reading How to Implement a Simple Task Queue in Node.js on SitePoint.
This content originally appeared on SitePoint and was authored by Craig Buckler
Craig Buckler | Sciencx (2021-05-03T16:00:07+00:00) How to Implement a Simple Task Queue in Node.js. Retrieved from https://www.scien.cx/2021/05/03/how-to-implement-a-simple-task-queue-in-node-js-2/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.