NodeJs parallel request in a loop too slow - node.js

so I have this feature that I am working on that takes an object (Over 10k items) picks an item from this object, sends it to an api, which process it then give a response, then proceeds to the next item.
Currently using the async library, the mapLimit method and it works as aspected.
But my problem is that it’s takes too long to loop through the entire dataset cause of the length.
This feature is suppose to be a continual process, once the entire object is iterated, wait for few seconds then do the same thing again.
I tried forking a child_process for this, broke the objects into chunks and created a process for each chunks till it was completed. This worked well as intended but the memory consumption was massive other processes on the server failed as a result of lack of available memory, even after exiting the process after it was completed.
Please how do I achieve this at a faster rate?
I use this to get the list of wallets.
getListofWallet = async () => {
try {
const USDT = await usdt.query(sql`
SELECT * FROM USDT ORDER BY id DESC;
`);
let counter = 0;
let completion = 0;
async.mapLimit(USDT, 6, async (user) => {
let userDetail = {
email: user.email,
id: user.user_id,
address: user.address
}
try {
await this.getWalletTransaction(userDetail);
completion++;
} catch (TronGridException) {
completion++;
console.log(":: A TronGrid Exception Occured");
console.log(TronGridException);
}
if (USDT.length == completion || USDT.length == (completion-5)) {
setTimeout(() => {
this.getListofWallet();
}, 60000);
console.log('~~~~~~~ Finished Wallet Batch ~~~~~~~~~');
}
}
);
} catch (error) {
console.log(error);
console.log('~~~~~~~Restarting TronWallet File after Crash ~~~~~~~~~');
this.getListofWallet();
}
}
Then I use this to process to data sent and perform the neccessary action.
getWalletTransaction = async (walletDetail) => {
const config = {
headers: {
'TRON-PRO-API-KEY': process.env.api_key,
'Content-Type': 'application/json'
}
};
const getTransactionFromAddress = await axios.get(`https://api.trongrid.io/v1/accounts/${walletDetail.address}/transactions/trc20`, config);
const response = getTransactionFromAddress.data;
const currentTimeInMillisecond = 1642668127000; //1632409548000
response.data.forEach(async (value) => {
if (value.block_timestamp >= currentTimeInMillisecond && value.token_info.address == "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t") {
let doesHashExist = await transactionCollection.query(sql`SELECT * FROM transaction_collection WHERE txhash=${value.transaction_id};`);
if (doesHashExist.length == 0) {
if (walletDetail.address == value.to) {
const checkExistence = await CryptoTransactions2.query(sql`
SELECT * FROM CryptoTransaction2 WHERE txHash=${value.transaction_id};
`);
if (checkExistence.length == 0) {
const xCollection = {
collection: "CryptoTransaction2",
queryObject: {
currency: "USDT",
sender: ObjectID("60358d21ec2b4b33e2fcd62e"),
receiver: ObjectID(walletDetail.id),
amount: parseFloat(tronWeb.fromSun(value.value)),
txHash: value.transaction_id,
description: "New wallet Deposit " + "60358d21ec2b4b33e2fcd62e" + " into " + value.to,
category: "deposit",
createdAt: new Date(),
updatedAt: new Date(),
},
};
await new MongoDbService().createTransaction(xCollection);
//create record inside cryptotransactions db.
await CryptoTransactions2.query(sql`INSERT INTO CryptoTransaction2 (txHash) VALUES (${value.transaction_id})`)
});
}

Related

NodeJS insert voucher code to first person who calls API

I don't know if this has a solution already but I can't find it or I don't know what to search.
I have a rest api which returns a list of products and I want to add a voucher code to the response of the first person who calls the api. I'm using redis to cache the information of the user who received the code, that expires within 15 mins.
async function addVoucherCode(response, userId) {
try {
const key = "KEY_VOUCHER_CODE";
let cachedData = await redis.get(key);
if (cachedData) {
if (cachedData.userId === userId) response.voucherCode = cachedData.voucherCode;
return;
}
const voucherCode = await createVoucherCode(userId); //call to create voucher code and save to db
if (!voucherCode) return;
await redis.setEx(key, 15 * 60, {userId, voucherCode});
response.voucherCode = cachedData.voucherCode;
} catch (err) {
console.error("[Error] addVoucherCode: ", err);
}
}
I created a function that mimics a simultaneous request, and when I checked the response, all them have a voucher code, not just the first.
async function getProducts(url, params) {
try {
const customers = [
{ id: 1, token: "Bearer eyJhbGciOi....1" },
{ id: 2, token: "Bearer eyJhbGciOi....2"},
{ id: 3, token: "Bearer eyJhbGciOi....3"},
{ id: 4, token: "Bearer eyJhbGciOi....4"}
];
const data = await Promise.all(customers.map( async customer => {
return await fetch(url + "?" + params.toString(), {
headers: {
Authorization: customer.token
},
}).then(res => res.json());
}));
data.forEach((item, indx) => {
if(item.voucherCode) {
const id = customers[indx].id;
console.log(`Customer ${id} has a voucher!!!!!!!!!!!!!`)
}
})
} catch (err) {
console.error("[Error] getProducts: ", err);
}
}
Result
Customer 1 has a voucher!!!!!!!!!!!!!
Customer 2 has a voucher!!!!!!!!!!!!!
Customer 3 has a voucher!!!!!!!!!!!!!
Customer 4 has a voucher!!!!!!!!!!!!!
I tried adding a 200ms delay inside addVoucherCode but same result. Thanks in advance for the help.
You are calling addVoucherCode in a sync loop, so it'll run 4 times in parallel (and the 4 GET commands will be issued at the same time, it'll reply with null to all of them, and all of them will call createVoucherCode).
There are 2 things you can do to fix it:
Cache the promise of createVoucherCode:
const createVoucherCodePromises = new Map();
function createVoucherCode(userId) {
if (!createVoucherCodePromises.has(userId)) {
createVoucherCodePromises.set(
userId,
_createVoucherCode(userId)
.finally(() => createVoucherCodePromises.delete(userId))
);
}
return createVoucherCodePromises.get(userId);
}
async function _createVoucherCode(userId) {
// ...
}
NOTE: this will not solve the problem if you have multiple node processes running at the same time.
Use SET with NX (won't override existing values) and GET (return existing/old value)
> SET key voucher1 NX GET
OK
> SET key voucher2 NX GET # will return the existing value without overriding it
"voucher1"
> GET key
"voucher1"

Is the Node.js global object compatible with Discord interactions?

Project Overview: Discord bot that uses discord.js where users can play rock-paper-scissors against each other through a queue system. It also has different ranks and an Elo system. Queues are stored in the global object as queue objects (see createQueue below).
Issue: Every 15 queues or so, someone will attempt to enter the queue and instead of joining the queue with another player already in it, it will put the player in a queue with the same lobby ID, but the other player won't be in it. It is strange and inconsistent behavior I have attributed to the unpredictability of globals, but I am assuming that I don't have the full story so I came here to ask. If you need more context, I am willing to provide it. Also, potential solutions would be appreciated if possible :)
Example Issue - both players have entered a queue with the same lobby ID, but are not in the same queue
manageQueues.js:
const createQueue = async (rank) => {
return {
players: [],
playerIdsIndexed: {},
timeouts: {},
game: {
number: 0,
p1: {
user: {},
choice: '',
score: 0
},
p2: {
user: {},
choice: '',
score: 0
}
},
lobby: {
id: global.lobbyId++,
rank: rank
}
};
};
const addPlayerToQueue = async (player, rank, timeout) => {
// There is no existing rank queue
if (!global[`${rank}Queue`]) {
console.log('No existing rank queue, making one...');
global[`${rank}Queue`] = await createQueue(rank);
}
// Failsafe
if (Object.keys(global[`${rank}Queue`].playerIdsIndexed).length >= 2) { console.log('Failsafe'); return undefined; }
// Player already in queue
if (global[`${rank}Queue`].playerIdsIndexed[player.id]) { console.log('Player already in queue'); return 'in'; }
// Player not in queue & it has room
const { players, timeouts, playerIdsIndexed, lobby: { id } } = global[`${rank}Queue`];
players.push(player);
playerIdsIndexed[player.id] = player;
if (timeout) {
timeouts[player.id] = setTimeout(async () => {
await removePlayerFromQueue(player, rank);
}, timeout);
}
return global[`${rank}Queue`];
};
queue.js (the command interaction):
async execute(interaction) {
const { user, channel, guild } = interaction;
const queueLength = interaction.options.getInteger('queue_length');
const queue = await addPlayerToQueue(user, channel.name, (queueLength ? queueLength : defaultTimeout) * 60 * 1000);
if (!queue) return interaction.reply({ content: 'The bot is currently making the queue.', ephemeral: true });
if (queue === 'in') return interaction.reply({ content: 'You are already in a queue.', ephemeral: true });
const { players, lobby: { rank, id } } = queue;
console.log(`Players in queue now: ${players.length}`);
await interaction.reply({ embeds: [queueEmbed(queue, interaction)] });
console.log(`${user.username} joined Lobby ${id} in ${capitalize(rank)}`);
const rankRole = guild.roles.cache.find(r => r.name === `${capitalize(rank)} Ping`);
if (players.length === 1 && Object.keys(ranks).includes(rank) && rankRole) {
await interaction.channel.send({ content: `<#&${rankRole.id}>` });
}
if (players.length === 2) {
global[`${rank}Queue`] = null;
await game(queue, interaction);
}
return;
}

Transactions With Mongoose and NodeJs, calling an external function

I have some code that have implemented Transaction and it works perfectly, but now I need to call a function inside this code(it has a comment in my code example), both parts have to have success or then nothing have to be done. It is not working properly. How can I integrate the "sendCashback, function" to work perfectly with my Transaction?
This is my code:
var transaction: any
let cashback_sent: any
try{
const transactiontimectrl = Number(process.env.TRANSACTION_TIME_STOP)
let dt = new Date();
dt.setSeconds(dt.getSeconds() - transactiontimectrl);
const timectrl = dt.toISOString();
console.log('teste transaction1')
console.log(timectrl)
const session = await mongoose.startSession();
await session.withTransaction(async () => {
const transactionRes = await Transaction.findOne(
{
from: from,
//to: to,
//value: valuetodebit,
createdAt: {$gt: timectrl}
}).session(session)
if(!transactionRes){
transaction = await Transaction.create([
{
from,
to,
value: value,
division_factor: destinationUserGroup.division_factor,
title: title || null,
description: description || null,
type: type || null,
hasCashback: hasCashback,
realmoney: realmoney,
valuetodebit: valuetodebit
}],{ session })
}else{
throw new Error('Erro, uma transação semelhante foi realizada recentemente')
}
if (!transaction) {
throw new Error('Erro, tente novamente mais tarde')
}
console.log('#sadihjaisvq3')
let fromBalance
//Saldo real
if (realmoney) {
fromBalance = await Balance.findOne({
type: IBalanceType.GENERAL,
user: from
}).session(session)
}
//Saldo cashback
else {
fromBalance = await Balance.findOne({
type: IBalanceType.CASHBACK,
user: from
}).session(session)
}
console.log('****criar balance2')
let toBalance = await Balance.findOne({
type: IBalanceType.GENERAL,
user: to
}).session(session)
console.log('****criar balance1', toBalance)
if (!toBalance) {
console.log('****criar balance')
toBalance = await Balance.create(
{
user: to
})
}
let toLivetPay = await Balance.findOne({
type: IBalanceType.GENERAL,
user: toLivet
}).session(session)
if (!fromBalance || !toBalance || !toLivetPay) throw new Error()
if (!credit) {
fromBalance.value = fromBalance.value + parseFloat(valuetodebit) * -1
}
// fromBalance.value = fromBalance.value + parseFloat(valuetodebit) * -1
toBalance.value = toBalance.value + parseFloat(value)
let valorCash = Number(value) / 0.75
let valorLivet = Number(valorCash) * 0.25
toLivetPay.value = toLivetPay.value + parseFloat(String(valorLivet))
await fromBalance.save()
await toBalance.save()
await toLivetPay.save()
if (hasCashback) {
if (!saque) {
//EXTERNAL FUNCTION---------------------------------------------------------
cashback_sent = await sendCashback(
transaction[0],
body.originUser!,
body.destinationUser!
)
//------------------------------------------------------------------------
}
if (!cashback_sent) {
throw new Error('Erro ao distribuir cashback')
}
console.log('depois do cashback')
}
})
The transaction advanced usage is what you need. To work with async await, checkout this link.
As described in the linked mongoose documentation, you can call session.startTransaction() method to start a transaction, and whenever you wish to abort, call session.abortTransaction() to abort the transaction, after all the operations in the transaction are success, call session.commitTransaction() to commit the transaction.

Make sure firestore collection docChanges keeps alive

The final solution is at the bottom of this post.
I have a nodeJS server application that listens to a rather big collection:
//here was old code
This works perfectly fine: these are lots of documents and the server can serve them from cache instead of database, which saves me tons of document reads (and is a lot faster).
I want to make sure, this collection is staying alive forever, this means reconnecting if a change is not coming trough.
Is there any way to create this certainty? This server might be online for years.
Final solution:
database listener that saves the timestamp on a change
export const lastRolesChange = functions.firestore
.document(`${COLLECTIONS.ROLES}/{id}`)
.onWrite(async (_change, context) => {
return firebase()
.admin.firestore()
.collection('syncstatus')
.doc(COLLECTIONS.ROLES)
.set({
lastModified: context.timestamp,
docId: context.params.id
});
});
logic that checks if the server has the same updated timesta.mp as the database. If it is still listening, it should have, otherwise refresh listener because it might have stalled.
import { firebase } from '../google/auth';
import { COLLECTIONS } from '../../../configs/collections.enum';
class DataObjectTemplate {
constructor() {
for (const key in COLLECTIONS) {
if (key) {
this[COLLECTIONS[key]] = [] as { id: string; data: any }[];
}
}
}
}
const dataObject = new DataObjectTemplate();
const timestamps: {
[key in COLLECTIONS]?: Date;
} = {};
let unsubscribe: Function;
export const getCachedData = async (type: COLLECTIONS) => {
return firebase()
.admin.firestore()
.collection(COLLECTIONS.SYNCSTATUS)
.doc(type)
.get()
.then(async snap => {
const lastUpdate = snap.data();
/* we compare the last update of the roles collection with the last update we
* got from the listener. If the listener would have failed to sync, we
* will find out here and reset the listener.
*/
// first check if we already have a timestamp, otherwise, we set it in the past.
let timestamp = timestamps[type];
if (!timestamp) {
timestamp = new Date(2020, 0, 1);
}
// if we don't have a last update for some reason, there is something wrong
if (!lastUpdate) {
throw new Error('Missing sync data for ' + type);
}
const lastModified = new Date(lastUpdate.lastModified);
if (lastModified.getTime() > timestamp.getTime()) {
console.warn('Out of sync: refresh!');
console.warn('Resetting listener');
if (unsubscribe) {
unsubscribe();
}
await startCache(type);
return dataObject[type] as { id: string; data: any }[];
}
return dataObject[type] as { id: string; data: any }[];
});
};
export const startCache = async (type: COLLECTIONS) => {
// tslint:disable-next-line:no-console
console.warn('Building ' + type + ' cache.');
const timeStamps: number[] = [];
// start with clean array
dataObject[type] = [];
return new Promise(resolve => {
unsubscribe = firebase()
.admin.firestore()
.collection(type)
.onSnapshot(querySnapshot => {
querySnapshot.docChanges().map(change => {
timeStamps.push(change.doc.updateTime.toMillis());
if (change.oldIndex !== -1) {
dataObject[type].splice(change.oldIndex, 1);
}
if (change.newIndex !== -1) {
dataObject[type].splice(change.newIndex, 0, {
id: change.doc.id,
data: change.doc.data()
});
}
});
// tslint:disable-next-line:no-console
console.log(dataObject[type].length + ' ' + type + ' in cache.');
timestamps[type] = new Date(Math.max(...timeStamps));
resolve(true);
});
});
};
If you want to be sure you have all changes, you'll have to:
keep a lastModified type field in each document,
use a query to get documents that we modified since you last looked,
store the last time you queried on your server.
Unrelated to that, you might also be interested in the recently launched ability to serve bundled Firestore content as it's another way to reduce the number of charged reads you have to do against the Firestore server.

Do node js worker never times out?

I have an iteration that can take up to hours to complete.
Example:
do{
//this is an api action
let response = await fetch_some_data;
// other database action
await perform_operation();
next = response.next;
}while(next);
I am assuming that the operation doesn't times out. But I don't know it exactly.
Any kind of explanation of nodejs satisfying this condition is highly appreciated. Thanks.
Update:
The actual development code is as under:
const Shopify = require('shopify-api-node');
const shopServices = require('../../../../services/shop_services/shop');
const { create } = require('../../../../controllers/products/Products');
exports.initiate = async (redis_client) => {
redis_client.lpop(['sync'], async function (err, reply) {
if (reply === null) {
console.log("Queue Empty");
return true;
}
let data = JSON.parse(reply),
shopservices = new shopServices(data),
shop_data = await shopservices.get()
.catch(error => {
console.log(error);
});
const shopify = new Shopify({
shopName: shop_data.name,
accessToken: shop_data.access_token,
apiVersion: '2020-04',
autoLimit: false,
timeout: 60 * 1000
});
let params = { limit: 250 };
do {
try {
let response = await shopify.product.list(params);
if (await create(response, shop_data)) {
console.log(`${data.current}`);
};
data.current += data.offset;
params = response.nextPageParameters;
} catch (error) {
console.log("here");
console.log(error);
params = false;
};
} while (params);
});
}
Everything is working fine till now. I am just making sure that the execution will ever happen in node or not. This function is call by a cron every minute, and data for processing is provided by queue data.

Resources