Mongo db queue processing is delayed at times using agenda - performance

We are using a mongodb-queue to do some processing and we are using the agenda scheduler to run the job every 3 mins to get a message from the queue and process the same. The issue that we are observing is that its not working consistently as expected, at times the message remains in the queue for sometime (not even acknowledged,means picked up) before it get processed once it starts processing the subsequent ones in the queue are getting processed faster again till that delay happens again.
if you look at this deleted timestamp the last three transactions on top ran much later to the one before it whereas its supposed to process 3 to 4 mins later than the 4th record.
find below the code we use to fetch and process from queue
module.exports = function (agenda_processing) {
var isStatic = false;
agenda_processing.disableRemoteMethodByName('updateAttributes', isStatic);
// agenda_processing = Object.assign(agenda_processing, httpManager);
isStatic = true;
agenda_processing.disableRemoteMethodByName('updateAll', isStatic);
agenda_processing.disableRemoteMethodByName('deleteById', isStatic);
agenda_processing.disableRemoteMethodByName('create', isStatic);
agenda_processing.disableRemoteMethodByName('upsert', isStatic);
agenda_processing.disableRemoteMethodByName('count', isStatic);
agenda_processing.disableRemoteMethodByName('findOne', isStatic);
agenda_processing.disableRemoteMethodByName('exists', isStatic);
agenda_processing.disableRemoteMethodByName('find', isStatic);
agenda_processing.disableRemoteMethodByName('findById', isStatic);
var jobsManager = ''
async function graceful() {
if (jobsManager) {
await jobsManager.stop();
}
setTimeout(() => {
process.exit(0);
}, 1000)
}
process.on('uncaughtException', graceful);
process.on("SIGTERM", graceful);
process.on("SIGINT", graceful);
//To deploy to dev commented out
setTimeout(() => {
setUpJobForProcessingQueue()
}, 3000)
function setUpJobForProcessingQueue () {
const dbUrl = config.spkmsdb.url
jobsManager = awbjobs.init(dbUrl, async () => {
await defineAndStartJobs()
})
//console.log(jobsManager)
};
async function defineAndStartJobs () {
let connector = agenda_processing.app.models.sites.getDataSource().connector.db
queue = processingqueue.initQueue(connector)
var jobNm = "processing-job"
jobsManager.define(jobNm, async function (job,done) {
try {
winstonLogger.info('Agenda: Entering the define callback')
await getDataFromQueueAndProcess(queue)
done()
winstonLogger.info('Agenda: Called done')
} catch(err) {
done(err)
}
}.bind(jobNm))
await jobsManager.every("180 seconds", jobNm) //3 minutes
await jobsManager.start()
winstonLogger.info('awb jobs have been set up')
}
}
agenda is intailised like below:
const agenda = new Agenda({ db: { address: dbConStr ,options: { useUnifiedTopology: true, useNewUrlParser: true }}});
agenda.on('ready', onReady)
Queue is internalized as
queuename = 'processing-queue'
const queue = mongoDbQueue(db, queuename, {maxRetries:1, visibility:3600});
Any help to resolve this consistency will be quite a help. thanks in advance.

The problem was with the agenda initializer the job never got created in the mongo db as the collection for the job was not paused while initializing the agenda which caused the scheduler to behave weirdly picking up from the queue unevenly especially while we had multiple instances of the application using its own scheduler jobs (collection name will be dynamic based on instance).
const agenda = new Agenda({ db: { address: dbConStr, options: { useUnifiedTopology: true, useNewUrlParser: true }, collection: 'jobscollection' } });

Related

Sequelize not closing connections when using AWS Lambdas

I'm having an issue where despite replicating the pattern for using Sequelize in AWS Lambdas as described in the docs here: https://sequelize.org/docs/v6/other-topics/aws-lambda/ - the connections are still kept for 10+ minutes even though they are idle.
Here's my Sequelize class, that lives inside the DB package in a private CodeArtifact repo.
const { Sequelize, DataTypes } = require("sequelize");
const fs = require('fs');
const path = require('path');
const basename = path.basename(__filename);
let modelsMap = new Map();
let sequelize = null;
module.exports = class SequelizeClient {
constructor() {}
static async getSequelize(configuration, options){
if(!sequelize){
console.log('load sequelize');
sequelize = await SequelizeClient.#loadSequelize(configuration, options)
} else {
sequelize.connectionManager.initPools();
// restore `getConnection()` if it has been overwritten by `close()`
if (sequelize.connectionManager.hasOwnProperty("getConnection")) {
delete sequelize.connectionManager.getConnection;
}
}
return sequelize;
}
static #initSequelize(options) {
return new Sequelize(
options.database,
options.user,
options.password,
{
host: options.host,
port: parseInt(options.port) || 5432,
logging: console.log,
dialect: "postgres",
pool: {
max: 2,
min: 0,
idle: 1000,
acquire: 3000,
evict: 1000
}
},
);
}
static async #loadSequelize(configuration) {
sequelize = SequelizeClient.#initSequelize(configuration);
await sequelize.authenticate();
return sequelize;
}
static async getModels(sequelize) {
const databaseName = sequelize.config.database;
let models = modelsMap.get(databaseName);
if(models){
return models;
}
models = {};
console.log('load models');
fs
.readdirSync(`${__dirname}/models`)
.filter((file) => file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js')
.forEach((file) => {
const model = require(path.join(`${__dirname}/models`, file))(sequelize, DataTypes);
const name = model.name.charAt(0).toUpperCase() + model.name.slice(1);
models[name] = model;
});
Object.keys(models).forEach((modelName) => {
if (models[modelName].associate) {
models[modelName].associate(models);
}
});
modelsMap.set(databaseName, models);
return models;
}
async static closeConnections(){
await sequelize.connectionManager.close();
}
}
And here's how I use this code inside a Lambda.
const { SequelizeClient } = require("#myPrivatePackage/database");
exports.handler = async (event) => {
try {
const sequelize = await SequelizeClient.getSequelize(someDbParams);
const { MyModel } = await SequelizeClient.getModels(sequelize);
return await MyModel.findOne(whereObj);
} finally {
SequelizeClient.closeConnections();
}
};
Obviously, I cleaned up the code to remove things that are not directly impacting the issue, such as specific queries, code that I'd like to avoid being public etc.
Based on the info I gathered online I'd assume that the connections would be closed once the query is done, but checking pgsql with this
SELECT pid, now() - query_start as duration, query
FROM pg_stat_activity
WHERE pid <> pg_backend_pid()
AND datname = 'db_name'
order by duration desc;
Shows a lot of queries that have duration in excess of 5-10min. Eventually, after around 15min the connections go away.
Any idea as to what I'm doing wrong? Or missing? Or fundamently not understanding when it comes to these concepts? I've been scratching my head for a few days now.

How to make a bot send a message to a specific channel after receiving a reaction from a certain message

So I'm trying to develop a bot for a very small project (I'm not a programmer or anything, just need to do this one thing). All I need it to do is to collect reactions from a specific message I sent and send another message to a channel as soon as it detects a reaction. The message would contain the reactor's tag and some text. I would need it to be activelly collecting reactions all the time, without any limit. I tried looking through the documentation but I don't really know how to implement the .awaitmessageReactions or whatever it's called. Can you help me out?
You can use method createReactionCollector for this. But when bot go down, this collector will stop.
const Discord = require('discord.js');
const bot = new Discord.Client();
let targetChannelId = '1232132132131231';
bot.on('ready', () => {
console.log(`${bot.user.tag} is ready on ${bot.guilds.cache.size} servers!`);
});
bot.on('message', (message) => {
if (message.content === 'test') {
message.channel.send(`i\`m await of reactions on this message`).then((msg) => {
const filter = (reaction, user) => !user.bot;
const collector = msg.createReactionCollector(filter);
collector.on('collect', (reaction, user) => {
let channel = message.guild.channels.cache.get(targetChannelId);
if (channel) {
let embed = new Discord.MessageEmbed();
embed.setAuthor(
user.tag,
user.displayAvatarURL({
dynamic: true,
format: 'png',
}),
);
}
embed.setDescription(`${user} (${user.tag}) has react a: ${reaction.emoji}`);
channel.send(embed);
});
collector.on('end', (reaction, reactionCollector) => {
msg.reactions.removeAll();
});
});
}
});
bot.login('token');
Or you can use emitter messageReactionAdd and handle reaction on specific message.
const Discord = require('discord.js')
const token = require('./token.json').token
const bot = new Discord.Client({ partials: ['MESSAGE', 'CHANNEL', 'REACTION'] });
bot.once('ready', () => {
console.log(`${bot.user.tag} is ready on ${bot.guilds.cache.size} guilds`)
})
let targetChannelId = '668497133011337224'
bot.on('messageReactionAdd', async (reaction, user) => {
if (reaction.partial) {
// If the message this reaction belongs to was removed the fetching might result in an API error, which we need to handle
try {
await reaction.fetch();
} catch (error) {
console.log('Something went wrong when fetching the message: ', error);
// Return as `reaction.message.author` may be undefined/null
return;
}
}
if (reaction.message.id === '730000158745559122') {
let channel = reaction.message.guild.channels.cache.get(targetChannelId);
console.log(reaction.emoji)
if (channel) {
let embed = new Discord.MessageEmbed();
embed.setAuthor(
user.tag,
user.displayAvatarURL({
dynamic: true,
format: 'png',
}),
);
embed.setDescription(`${user} has react a: ${reaction.emoji}`);
channel.send(embed);
}
}
});
bot.login(token)

How can I stop the subscription in GraphQL?

Here's a piece of code I'm interested in (it's taken from /examples/ directory):
Subscription: {
counter: {
subscribe: (parent, args, { pubsub }) => {
const channel = Math.random().toString(36).substring(2, 15) // random channel name
let count = 0
// added var refreshIntervalId =
var refreshIntervalId = setInterval(() => pubsub.publish(channel, { counter: { count: count++ } }), 2000) // <----
return pubsub.asyncIterator(channel)
},
// my new changes that hopefully will work
onDisconnect: (webSocket, context) => {
clearInterval(refreshIntervalId);
}
}
I'm a bit concerned what's the best way (how can I pass refreshIntervalId between subscribe() and onDisconnect() to stop the interval once the connection is closed.
Update: I realized that I should insert onDisconnect under server's option (not in the resolver block), so I probably think I shouldn't really worried about it at all (it should handle disconnection by default).

Admin on rest - implementing aor-realtime

I'm having a real hard time understanding how to implement aor-realtime (trying to use it with firebase; reads only, no write).
The first place I get stuck: This library generates a saga, right? How do I connect that with a restClient/resource? I have a few custom sagas that alert me on errors, but there is a main restClient/resource backing those. Those sagas just handles some side-effects. In this case, I just don't understand what the role of the client is, and how it interacts with the generated saga (or visa-versa)
The other question is with persistence: Updates stream in and the initial set of records is not loaded in one go. Should I be calling observer.next() with each update? or cache the updated records and call next() with the entire collection to-date.
Here's my current attempt at doing the later, but I'm still lost with how to connect it to my Admin/Resource.
import realtimeSaga from 'aor-realtime';
import { client, getToken } from '../firebase';
import { union } from 'lodash'
let cachedToken
const observeRequest = path => (type, resource, params) => {
// Filtering so that only chats are updated in real time
if (resource !== 'chat') return;
let results = {}
let ids = []
return {
subscribe(observer) {
let databaseRef = client.database().ref(path).orderByChild('at')
let events = [ 'child_added', 'child_changed' ]
events.forEach(e => {
databaseRef.on(e, ({ key, val }) => {
results[key] = val()
ids = union([ key ], ids)
observer.next(ids.map(id => results[id]))
})
})
const subscription = {
unsubscribe() {
// Clean up after ourselves
databaseRef.off()
results = {}
ids = []
// Notify the saga that we cleaned up everything
observer.complete();
}
};
return subscription;
},
};
};
export default path => realtimeSaga(observeRequest(path));
How do I connect that with a restClient/resource?
Just add the created saga to the custom sagas of your Admin component.
About the restClient, if you need it in your observer, then pass it the function which return your observer as you did with path. That's actually how it's done in the readme.
Should I be calling observer.next() with each update? or cache the updated records and call next() with the entire collection to-date.
It depends on the type parameter which is one of the admin-on-rest fetch types:
CRUD_GET_LIST: you should return the entire collection, updated
CRUD_GET_ONE: you should return the resource specified in params (which should contains its id)
Here's the solution I came up with, with guidance by #gildas:
import realtimeSaga from "aor-realtime";
import { client } from "../../../clients/firebase";
import { union } from "lodash";
const observeRequest = path => {
return (type, resource, params) => {
// Filtering so that only chats are updated in real time
if (resource !== "chats") return;
let results = {}
let ids = []
const updateItem = res => {
results[res.key] = { ...res.val(), id: res.key }
ids = Object.keys(results).sort((a, b) => results[b].at - results[a].at)
}
return {
subscribe(observer) {
const { page, perPage } = params.pagination
const offset = perPage * (page - 1)
const databaseRef = client
.database()
.ref(path)
.orderByChild("at")
.limitToLast(offset + perPage)
const notify = () => observer.next({ data: ids.slice(offset, offset + perPage).map(e => results[e]), total: ids.length + 1 })
databaseRef.once('value', snapshot => {
snapshot.forEach(updateItem)
notify()
})
databaseRef.on('child_changed', res => {
updateItem(res)
notify()
})
const subscription = {
unsubscribe() {
// Clean up after ourselves
databaseRef.off();
// Notify the saga that we cleaned up everything
observer.complete();
}
};
return subscription;
}
};
}
};
export default path => realtimeSaga(observeRequest(path));

Create an Rx.Subject using Subject.create that allows onNext without subscription

When creating an Rx.Subject using Subject.create(observer, observable), the Subject is so lazy. When I try to use subject.onNext without having a subscription, it doesn't pass messages on. If I subject.subscribe() first, I can use onNext immediately after.
Let's say I have an Observer, created like so:
function createObserver(socket) {
return Observer.create(msg => {
socket.send(msg);
}, err => {
console.error(err);
}, () => {
socket.removeAllListeners();
socket.close();
});
}
Then, I create an Observable that accepts messages:
function createObservable(socket) {
return Observable.fromEvent(socket, 'message')
.map(msg => {
// Trim out unnecessary data for subscribers
delete msg.blobs;
// Deep freeze the message
Object.freeze(msg);
return msg;
})
.publish()
.refCount();
}
The subject is created using these two functions.
observer = createObserver(socket);
observable = createObservable(socket);
subject = Subject.create(observer, observable);
With this setup, I'm not able to subject.onNext immediately (even if I don't care about subscribing). Is this by design? What's a good workaround?
These are actually TCP sockets, which is why I haven't relied on the super slick websocket subjects.
The basic solution, caching nexts before subscription with ReplaySubject:
I think all you wanted to do is use a ReplaySubject as your observer.
const { Observable, Subject, ReplaySubject } = Rx;
const replay = new ReplaySubject();
const observable = Observable.create(observer => {
replay.subscribe(observer);
});
const mySubject = Subject.create(replay, observable);
mySubject.onNext(1);
mySubject.onNext(2);
mySubject.onNext(3);
mySubject.subscribe(x => console.log(x));
mySubject.onNext(4);
mySubject.onNext(5);
Results in:
1
2
3
4
5
A socket implementation (example, don't use)
... but if you're looking at doing a Socket implementation, it gets a lot more complicated. Here is a working socket implementation, but I don't recommend you use it. Rather, I'd suggest that you use one of the community supported implementations either in rxjs-dom (if you're an RxJS 4 or lower) or as part of RxJS 5, both of which I've helped work on.
function createSocketSubject(url) {
let replay = new ReplaySubject();
let socket;
const observable = Observable.create(observer => {
socket = new WebSocket(url);
socket.onmessage = (e) => {
observer.onNext(e);
};
socket.onerror = (e) => {
observer.onError(e);
};
socket.onclose = (e) => {
if (e.wasClean) {
observer.onCompleted();
} else {
observer.onError(e);
}
}
let sub;
socket.onopen = () => {
sub = replay.subscribe(x => socket.send(x));
};
return () => {
socket && socket.readyState === 1 && socket.close();
sub && sub.dispose();
}
});
return Subject.create(replay, observable);
}
const socket = createSocketSubject('ws://echo.websocket.org');
socket.onNext('one');
socket.onNext('two');
socket.subscribe(x => console.log('response: ' + x.data));
socket.onNext('three');
socket.onNext('four');
Here's the obligatory JsBin

Resources