My ampq system seems loosing messages, so I'd like a way to see if messages are effectively queued before being consumed.
I have several MicroServices communicating by amqp messages on NodeJs, using CloudAmqp. One of this microservice MS[B] generates .pdf, the process it's pretty heavy and requires about a minute for each request. So I send the .pdf asyncronously, triggering a webhook once finished, and generate once per time using a PreFetch = 1
So one MS[A] collects all the requests from the user, answers back to them saying "ok, request received, listen on the webhook" and in parallel it asks to the MS[B] to generate pdfs. MS[B] has prefetch=1, so consumes just one request per time. Once finished, sends the response to the callback queue of MS[A], which triggers the user webhook saying "the pdf, it's ready".
The problem is that MS[B] misses all the messages while busy:
it consumes one request from MS[A]
starts generating the .pdf
while generating, it discards all the other messages that MS[A] sends, as if there would be not any queue
it finishes the .pdf, sending ACK to MS[A]
then it starts again accepting messages, taking the last one received after being idle, losing all the previous ones.
Why? How can I find the problem, what could I monitor?
Communications between other MSs works well, with messages correctly ordered in queues. Just this one, with prefetch=1, loses messages.
I am NOT using the NO-ACK rule. I don't know what try, what test and what monitor to find the problem.
How can I see (if) messages are correctly queued before being consumed, ora just lost?
Below, the implementation of the messaging system
Channel Creation
/*
Starting Point of a connection to the CLOUDAMQP_URL server, then exec the callback
*/
start(callback) {
var self = this;
// if the connection is closed or fails to be established at all, we will reconnect
amqp.connect(process.env.CLOUDAMQP_URL + "?heartbeat=60")
.then(
function (conn) {
// create queues and consume mechanism
self.amqpConn = conn;
setTimeout(() => {
startPublisher();
}, 200);
setTimeout(() => {
createCallbackQueue();
}, 1000);
setTimeout(() => {
callback();
}, 2000);
});
// create publisher channels
function startPublisher() {
self.amqpConn.createConfirmChannel()
.then(function (ch) {
self.pubChannel = ch;
logger.debug("🗣️ pubChannel ready");
while (true) {
var m = self.offlinePubQueue.shift();
if (!m) break;
self.publish(m[0], // exchange
m[1], // routingKey
m[2], // content,
undefined // correlationId
);
}
});
}
// create callback channel
function createCallbackQueue() {
self.amqpConn.createChannel()
.then(function (channel) {
channel.assertQueue(self.CALLBACK_QUEUE_NAME, {
durable: true,
exclusive: true, // callback are exclusive
})
.then(function (q) {
logger.debug(" 👂 Waiting for RPC RESP in " + self.CALLBACK_QUEUE_NAME);
channel.consume(q.queue,
processCallback, {
noAck: false
}
);
});
// process messages of the callback
function processCallback(msg) {
var correlationId = msg.properties.correlationId;
}
//callback received
if (self.correlationIds_map[correlationId]) {
delete self.correlationIds_map[correlationId];
var content = JSON.parse(msg.content.toString());
self.eventEmitter.emit(correlationId, content);
}
}
});
}
return deferred.promise;
}
Consuming Messages
/*
#worker_queue - the name of the queue
*/
// Consume message from 'worker_queue', A worker that acks messages only if processed succesfully
startWorker(worker_queue, routes) {
var self = this;
logger.debug("startWorker " + self.CALLBACK_QUEUE_NAME);
var channel;
worker_queue = self.MICROSERVICE_NAME + worker_queue;
self.amqpConn.createChannel()
.then(
function (ch) {
channel = ch;
ch.prefetch(self.opt.prefetch); // = 1 for MS[B] generating pdf
channel.assertQueue(worker_queue, {
durable: true,
exclusive: true
})
.then(function (q) {
channel.consume(worker_queue, processMsg, {
noAck: false
});
});
});
// call the 'function from interface' passing params, and send the ACK
function processMsg(msg) {
work(msg)
.then(function (data) {
channel.ack(msg, false); // allUpTo = false
})
.catch(function (err) {
channel.ack(msg, false);
// channel.reject(msg, false); // requeue = false
// this.closeOnErr(e);
});
}
// execute the command, and queue back a response, checking if it's an error or not
function work(msg) {
var deferred = Q.defer();
var correlationId;
try {
correlationId = msg.properties.correlationId;
} catch (err) {}
work_function(msg.content, correlationId)
.then(function (resp) {
var content = {
data: resp
};
content = Buffer.from(JSON.stringify(content));
channel.sendToQueue(msg.properties.replyTo,
content, {
correlationId: correlationId,
content_type: 'application/json'
}
);
deferred.resolve(resp);
});
return deferred.promise;
}
}
Publish Messages
publish(exchange, routingKey, content, correlationId) {
var self = this;
var deferred = Q.defer();
self.correlationIds_map[correlationId] = true;
self.pubChannel.publish(exchange, routingKey, content,
{
replyTo: self.CALLBACK_QUEUE_NAME,
content_type : 'application/json',
correlationId: correlationId,
persistent : true
},
function(err, ok) {
if (err)
{
self.offlinePubQueue.push([exchange, routingKey, content]); // try again
self.pubChannel.connection.close();
deferred.resolve('requeued');
}
else
{
deferred.resolve(ok);
}
});
return deferred.promise;
}
This is on client side
socket.on('connect', () => {
console.log('client connect', socket.id);
const token = getToken();
socket.emit('token', token);
});
socket.on('message', data => {
....
//handle message
});
This is on server side
io.on('connection', (client) => {
client.on('token', token => {
verifyToken(token)
.then(({ _id: clientId }) => {
if (!clientId) return;
if (!connections[clientId]) {
connections[clientId] = new Map();
}
connections[clientId].set(client, 1);
client.on('disconnect', () => {
connections[clientId].delete(client);
});
});
});
});
}
async sendMessageToClients (workspaceId, message) {
const workspace = await getWorkspaceQuery(workspaceId);
if (!workspace) return;
const workspaceMembers = workspace.members.map(({ user }) => user);
for (const memberId of workspaceMembers) {
if (connections[memberId]) {
for (const clientConnection of connections[memberId].keys()) {
console.log('send to client', memberId, message.content, clientConnection.connected, clientConnection.id);
clientConnection.emit('message', message);
}
}
}
};
}
I purposely make a client offline by disconnect the wifi connection (make it in offline mode), what happen is that
a. if the disconnection is short, socket.id stay the same and I can get the buffered message send by other client when comes online;
b. but if I the disconnection is longer, the socket.id will change, and I can't get the buffered message send by other client when comes online.
How should I address that?
Since according to here the messages should be ideally buffered after reconnection.
I want to achieve something like this:
call my website url https://mywebsite/api/something
then my next.js website api will call external api
get external api data
update external api data to mongodb database one by one
then return respose it's status.
Below code is working correctly correctly. data is updating on mongodb but when I request to my api url it respond me very quickly then it updates data in database.
But I want to first update data in database and then respond me
No matter how much time its take.
Below is my code
export default async function handler(req, res) {
async function updateServer(){
return new Promise(async function(resolve, reject){
const statusArray = [];
const apiUrl = `https://example.com/api`;
const response = await fetch(apiUrl, {headers: { "Content-Type": "application/json" }});
const newsResults = await response.json();
const articles = await newsResults["articles"];
for (let i = 0; i < articles.length; i++) {
const article = articles[i];
try {
insertionData["title"] = article["title"];
insertionData["description"] = article["description"];
MongoClient.connect(mongoUri, async function (error, db) {
if (error) throw error;
const articlesCollection = db.db("database").collection("collectionname");
const customQuery = { url: article["url"] };
const customUpdate = { $set: insertionData };
const customOptions = { upsert: true };
const status = await articlesCollection.updateOne(customQuery,customUpdate,customOptions);
statusArray.push(status);
db.close();
});
} catch (error) {console.log(error);}
}
if(statusArray){
console.log("success", statusArray.length);
resolve(statusArray);
} else {
console.log("error");
reject("reject because no statusArray");
}
});
}
updateServer().then(
function(statusArray){
return res.status(200).json({ "response": "success","statusArray":statusArray }).end();
}
).catch(
function(error){
return res.status(500).json({ "response": "error", }).end();
}
);
}
How to achieve that?
Any suggestions are always welcome!
I have written the below lambda code to send SMS. The SMS was created, but SMS did not deliver to the devices. I guess It got stuck in either SNS Queue or Lambda trigger queue.
let AWS = require('aws-sdk');
const sns = new AWS.SNS();
exports.handler = async (event, callback) => {
if (!event.request.session || event.request.session.length === 0) {
const phone = event.request.userAttributes.phone_number
const otp = Math.floor(100000 + Math.random() * 900000)
const message = "OTP to login to Stable is "+otp
sns.publish({
Message: message,
MessageAttributes: {
'AWS.SNS.SMS.SMSType': {
DataType: 'String',
StringValue: 'Transactional'
},
'AWS.SNS.SMS.SenderID': {
DataType: 'String',
StringValue: 'sender'
},
},
PhoneNumber: phone
}).promise()
.then(data => {
console.log("Sent message to", phone);
callback(null, data);
})
.catch(err => {
console.log("Sending failed", err);
callback(err);
});
event.response.privateChallengeParameters = {
answer: otp
};
event.response.challengeMetadata = "CUSTOM_CHALLENGE";
}
console.log('raja');
console.log(event);
return event;
};
How do I receive the message in the device? Is there any configuration is missing ?
return event; line is returning before the SNS callback is complete, #kulls confirmed that removing this line fixed the issue
To Send Message I use AWS.Lambda+AWS.IOT
var AWS = require('aws-sdk');
var iotdata = new AWS.IotData({"endpoint": 'XXXXXX'});
exports.handler = function(event, context) {
var params = {
topic : "testtopic/action1",
payload : "payload",
qos: 0
};
iotdata.publish(params,
function(err, data) {
if (err) console.log(err, err.stack);
else console.log(data);
}
);
};
Some time I receive messages (with wrong order or big delay), but usually any messages at all.
For receive messages I use AWS.Console -> AWS IoT -> MQTT client
What wrong with my lambda?