I have been trying to insert large data about(400-1000) json object array to mongodb using mongoose + expressjs When i changed data about(50) items insertMany works great without problem. But if data is more than 100 it giving me an error.
Departed.insertMany(results)
.then(dep => {
console.log(dep)
res.sendStatus(201)
})
.catch(err => {
console.log(err)
})
in morgan console i got following:
creation { active: true,
_id: 5b73e8af19722d1689d863b0,
name: 'TEST DATA 241',
map: '',
created_at: 2018-08-15T08:47:43.196Z,
updated_at: 2018-08-15T08:47:43.196Z,
__v: 0 }
insert read 453
(node:5769) [DEP0079] DeprecationWarning: Custom inspection function on Objects via .inspect() is deprecated
also on client side(chrome, dev tools network tab) status got
(failed)
net::ERR_EMPTY_RESPONSE
I have read mongo's insertMany() has limit about 1000 and i am using mongo 4.0 version. Even i chunked large json into several arrays and tried to insert it but still got same results. Actual snippets are
router.post('/xls', upload.single('file'), async (req, res, next) => {
try {
if (req.body && req.file) {
console.log('req', req.file)
const segments = req.file.originalname.split('.')
let exceltojson = segments[segments.length - 1] === 'xlsx' ? xlsx : xls
exceltojson(
{
input: req.file.path,
output: 'output.json'
},
async (err, result) => {
if (err) console.log(err)
const section = await Section.create({
name: req.body.section,
map: req.body.map
})
const results = await result.map(item => {
return {
branch: req.body.branch,
section: String(section._id),
...item
}
})
await console.log('creation', section)
console.log('insert read', results.length)
if (results.length >= 100) {
console.log('more than 100')
const data = _.chunk(results, 100)
data.forEach(async chunk => {
console.log('foreach')
Departed.insertMany(chunk)
.then(dep => {
console.log(dep)
res.sendStatus(201)
})
.catch(err => {
console.log(err)
})
})
}
}
)
}
} catch (error) {
next(error)
}
})
Your problem is not related to any insertMany limit. You have a race condition in your code where you don't wait for all chunks to be inserted, before sending the status back:
data.forEach(async chunk => {
console.log('foreach')
Departed.insertMany(chunk)
.then(dep => { // this will be called as soon as one of the inserts finish
console.log(dep)
res.sendStatus(201)
})
.catch(err => {
console.log(err)
})
})
Change this in something like (untested):
Promise.all(data.map(chunk => Departed.insertMany(chunk)))
.then(dep => { // this will be called when all inserts finish
console.log(dep)
res.sendStatus(201)
})
.catch(err => {
console.log(err)
})
})
Another alternative is to use the bulkWrite API which is is faster than sending multiple independent operations because with bulkWrite() there is only one round trip to MongoDB:
router.post('/xls', upload.single('file'), async (req, res, next) => {
try {
if (req.body && req.file) {
console.log('req', req.file)
const segments = req.file.originalname.split('.')
let exceltojson = segments[segments.length - 1] === 'xlsx' ? xlsx : xls
exceltojson(
{
input: req.file.path,
output: 'output.json'
},
async (err, result) => {
if (err) console.log(err)
const section = await Section.create({
name: req.body.section,
map: req.body.map
})
let chunk = [];
result.forEach(item => {
chunk.push({
insertOne: {
document: {
branch: req.body.branch,
section: String(section._id),
...item
}
}
});
if (chunk.length === 500) {
const blkResult = await Departed.bulkWrite(chunk);
console.log(blkResult)
res.sendStatus(201)
}
});
if (chunk.length > 0) {
const dep = await Departed.bulkWrite(chunk);
console.log(dep)
res.sendStatus(201)
}
}
)
}
} catch (error) {
next(error)
}
})
Related
How to pass id in where clause using node.js and MySQL?
Viewer.jsx
async function redeem(cid) {
fetch('http://localhost:4000/getDetailsFromCid/${cid}').then(response => {
return response.json()
})
.then(posts => {
console.log("posts", posts)
})
.then((err) => {
console.log(err);
})
}
index.js
app.get("/api/getDetailsFromCid/:cid", (req, res) => {
const cid = req.params.cid;
db.query("SELECT * FROM Details WHERE cid = ?", [cid],
(err, result) => {
if (err) {
console.log(err)
}
res.send(result)
});
});
Error
Viewer.jsx:19 GET http://localhost:4000/getDetailsFromCid/$%7Bcid%7D 404 (Not Found)
you need to use `` not ''
fetch(`http://localhost:4000/getDetailsFromCid/${cid}`)
I have 2 pages in my controller folder like this:
General.js
const Person = require('../models/person');
let personInfo = new Promise((success,reject)=>{
Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
success(par);
} else {
reject("Error");
}
})
.catch((err) => { console.log(err); });
});
module.exports.personInfo = personInfo;
Account.js
exports.Ac = (req, res, next) => {
let person = new require('./general');
person.personInfo
.then((par) => {
return res.render('/myPage/Account', {
title: 'Account',
group: par
});
})
.catch((err) => { console.log(err); });
}
Problem is , this promise working when server beginning but only once after its not working , par value always being same. If i change datas on my database , datas not changing on my web page.
anti-pattern
This is the explicit promise construction anti-pattern -
let personInfo = new Promise((success,reject)=>{
Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
success(par);
} else {
reject("Error");
}
})
.catch((err) => { console.log(err); });
});
You can replace it with -
let personInfo = Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
return par
} else {
throw Error("Error: empty par");
}
})
.catch(console.error) // <- don't catch here
And you should probably leave the .catch off and expect the caller to handle error handling. The .catch handler below would never trigger if the error is catch'd before
exports.Ac = (req, res, next) => {
let person = new require('./general');
person.personInfo
.then((par) => {
res.render('/myPage/Account', { // no "return" needed
title: 'Account',
group: par
});
})
.catch((err) => { console.log(err); }); // <- keep catch here
}
The reason it is only happening once, is because Promises can only be resolved or rejected once. You'll have to replace personInfo with a function -
const Person = require('../models/person');
const fetchPersonInfo = () =>
Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
return par
} else {
throw Error("Error: empty par");
}
})
});
module.exports.fetchPersonInfo = fetchPersonInfo;
async await
You might also want to consider reading up on async/await as they make your life a lot better
const Person = require('../models/person');
async function fetchPersonInfo () { // <- async
const par = await Person.find({ Group: 'pre'}) // <- await
if (par.length > 0)
return par;
else
throw Error("Error: empty par");
});
module.exports.fetchPersonInfo = fetchPersonInfo;
const { fetchPersonInfo } = new require('./general'); // <- top-level import
exports.Ac = async (req, res, next) => { // <- async
try {
const par = await fetchPersonInfo() // <- await
res.render('/myPage/Account', {
title: 'Account',
group: par
});
} catch (err) {
console.log(err)
}
}
Below is the code that I'm using to implement pagination for data retrieved from the firebase realtime database. Basically, I'm trying to get the first n content according to page number, and then getting the last n content from the data retrieved in the first query.
function getUserSnapshotOrVerifyUserId(username, idToken, cb) {
if (username == null || username.length == 0 || idToken == null || idToken.length == 0)
return cb({
status: "error",
errorMessage: "Missing params."
}, null);
admin.auth().verifyIdToken(idToken).then(decodedToken => {
let uid = decodedToken.uid;
admin.database().ref().child("users").orderByChild("username").equalTo(username).once('value', snapshot => {
if (!snapshot.exists())
return cb({
status: "error",
message: "invalid-profile"
});
snapshot.forEach(child => {
const id = child.val().id;
if (id !== uid)
return cb({
status: "error",
message: "Invalid ID"
});
admin.database().ref("users/" + id).once("value", snapshot => {
if (!snapshot.exists())
return cb({
status: "error",
errorMessage: "user not found."
});
return cb(null, id, snapshot);
});
});
});
}).catch(err => cb({
status: "error",
message: err
}));
}
exports.getUserContentTestPagination = functions.https.onRequest((req, res) => {
corsHandler(req, res, async () => {
try {
const username = req.body.username || req.query.username;
const idToken = req.body.idToken;
const limit = 2;
const page = req.body.page || 1;
const limitToFirst = page * limit;
const limitToLast = limit;
getUserSnapshotOrVerifyUserId(username, idToken, async (err, id) => {
if(err) return res.json(err);
const uploadsRef = admin.database().ref('uploads').orderByChild('createdBy').equalTo(id)
const firstnquery = uploadsRef.limitToFirst(limitToFirst);
const lastnquery = firstnquery.limitToLast(limitToLast);
lastnquery.once("value", snapshot => {
res.json({
snapshot
})
})
})
} catch (err) {
res.json({
status: "error",
message: err
})
}
});
});
This is returning a function timeout, however, when I try to get the first n data using firstnquery, it is returning the first n data as expected. So the problem is with lastnquery. Any help would be appreciated.
UPDATE 1:
exports.getUserContentTestPagination = functions.https.onRequest((req, res) => {
corsHandler(req, res, async () => {
try {
const username = req.body.username || req.query.username;
const idToken = req.body.idToken;
const limit = 2;
const page = req.body.page || 1;
let lastKnownKeyValue = null;
getUserSnapshotOrVerifyUserId(username, idToken, async (err, id) => {
if(err) return res.json(err);
const uploadsRef = admin.database().ref('uploads');
const pageQuery = uploadsRef.orderByChild('createdBy').equalTo(id).limitToFirst(limit);
pageQuery.once('value', snapshot => {
snapshot.forEach(childSnapshot => {
lastKnownKeyValue = childSnapshot.key;
});
if(page === 1){
res.json({
childSnapshot
})
} else {
const nextQuery = uploadsRef.orderByChild('createdBy').equalTo(id).startAt(lastKnownKeyValue).limitToFirst(limit);
nextQuery.once("value", nextSnapshot => {
nextSnapshot.forEach(nextChildSnapshot => {
res.json({
nextChildSnapshot
})
})
})
}
});
})
} catch (err) {
res.json({
status: "error",
message: err
})
}
});
});
It is incredibly uncommon to use both limitToFirst and limitToLast in a query. In fact, I'm surprised that this doesn't raise an error:
const firstnquery = uploadsRef.limitToFirst(limitToFirst);
const lastnquery = firstnquery.limitToLast(limitToLast);
Firebase queries are based on cursors. This means that to get the data for the next page, you must know the last item on the previous page. This is different from most databases, which work based on offsets. Firebase doesn't support offset based queries, so you'll need to know the value of createdBy and the key of the last item of the previous page.
With that, you can get the next page of items with:
admin.database().ref('uploads')
.orderByChild('createdBy')
.startAt(idOfLastItemOfPreviousPage, keyOfLastItemOfPreviousPage)
.limitToFist(pageSize + 1)
I highly recommend checking out some other questions on implementing pagination on the realtime database, as there are some good examples and explanations in there too.
So far I am building an app using express and firebase cloud functions. I am not able to create a nested json according to this db:
Here is the code:
exports.tstMenu = (req, res) => {
let shop = db.collection('shops').doc(req.params.name).collection('menus');
shop.get()
.then((data) => {
let menu = [];
data.forEach((doc) => {
let categories = getCategories(doc.id, shop);
menu.push({
menuID: doc.id,
name: doc.data().name,
position: doc.data().position,
categories: categories,
});
console.log(menu);
});
return res.json(menu);
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error: err.message});
});
}
function getCategories(id, db){
let shop = db.doc(id).collection('categories');
return shop.get()
.then((data) => {
let categs = [];
data.forEach((doc) => {
var menuElements = [];//getMenuElement(doc.id, shop);
categs.push({
catID: doc.id,
name: doc.data().name,
position: doc.data().position,
menuElements: menuElements,
});
});
return categs;
});
}
and the result of tstMenu is:
while the log is showing this:
Can anyone explain me how to fix it? I am quite sure that promises are not received when tstMenu reaches return res.json(menu);
Your problem lies within this line :
let categories = getCategories(doc.id, shop);
getCategories is an async method. It returns a promise so you can't use it directly as you do.
You either should do your assignment in a then callback or you should use async await.
exports.tstMenu = (req, res) => {
let shop = db.collection('shops').doc(req.params.name).collection('menus');
shop.get()
.then((data) => {
let menu = [];
const promises = data.docs.map((doc) => // change this from forEach to map
getCategories(doc.id, shop).then(categories =>{
menu.push({
menuID: doc.id,
name: doc.data().name,
position: doc.data().position,
categories: categories,
});
);
return Promise.all(promises).then(()=> res.json(menu)); // return res after all promises completed
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error: err.message});
});
}
Or
exports.tstMenu = async (req, res) => {
try {
let shop = db.collection('shops').doc(req.params.name).collection('menus');
const data = await shop.get()
let menu = [];
const promises = data.docs.map((doc) => // change this from forEach to map
getCategories(doc.id, shop).then(categories =>{
menu.push({
menuID: doc.id,
name: doc.data().name,
position: doc.data().position,
categories: categories,
});
);
await Promise.all(promises);
return res.json(menu)
} catch(err) {
console.error(err);
return res.status(500).json({ error: err.message});
}
}
I have found out that my and my colleagues lambda doesn't return data we anticipate.
After what I have already found, we used deprecated invokeAsync which returnes only statusCode.
I have upgraded aws-sdk to the newest version and change the invocation code to use invoke with
InvocationType: 'RequestResponse'
as that should give us the returned value.
Lambdas code itself looks good, its an async function without arguments.
Im not using the callback here (3rd argument of handler), as we are doing some async stuff and it would require a small refactor to not use async/await, also i have read that just returning value is ok as well.
Earlier lambda was returning array, but after some investigation and googling I have changed it to
return {
statusCode: 200,
body: JSON.stringify(result)
}
where result is the mentioned array, as this was recurring pattern in search results.
Overall result is in our service where we do the invocation, we get timeout, lambda logs that it is returning value, but then retries itself again and again.
Dumping code
invocation in our service:
const lambda = new AWS.Lambda({ httpOptions: { timeout: 600000 } })
const results = await new Promise((resolve, reject) => {
lambda.invoke(
{
FunctionName: `lambda-name`,
InvocationType: 'RequestResponse',
Payload: '""'
},
(err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
}
)
})
lambda handler
import { parallelLimit } from 'async'
const PARALLEL_FETCHERS_NUMBER = 2
export async function runTasksInParallel (
tasks,
limit,
) {
return new Promise(
(
resolve,
reject,
) => {
parallelLimit(tasks, limit, (error, results) => {
if (error === null) {
resolve(results)
} else {
reject(error)
}
})
},
)
}
async function connectToDB(logger) {
const MONGO_URL = process.env.MONGO_URL || 'mongodb://localhost:27017/'
let db
logger.debug('Connecting to mongo')
try {
db = await MongoClient.connect(
MONGO_URL,
{
sslValidate: false,
}
)
} catch (error) {
logger.error('Could not connect to Mongo', { error })
throw error
}
logger.debug('Connected')
return db
}
async function fetchAndStore(
list,
sources,
logger
) {
const results = []
const tasks = sources.map((source) => async (callback) => {
const { fetchAdapter, storageAdapter } = source
try {
const { entries, timestamp } = await fetchAdapter.fetchLatest(list)
await storageAdapter.storeMany(timestamp, entries)
results.push(['fetch.success', 1, [ `fetcher:${fetchAdapter.name}` ] ])
} catch (error) {
const errorMessage = `Failed to fetch and store from adapter ${fetchAdapter.name}`
const { message, name, stack } = error
logger.error(errorMessage, { error: { message, name, stack } })
results.push(['fetch.error', 1, [ `fetcher:${fetchAdapter.name}` ] ])
}
callback && callback(null)
})
await runTasksInParallel(tasks, PARALLEL_FETCHERS_NUMBER)
return results
}
export async function handle() {
const logger = createLambdaLogger() // just a wrapper for console.log to match interface in service
logger.debug('Starting fetching data')
const db: Db = await connectToDB(logger)
const primaryCollection = db.collection(PRIMARY_COLLECTION)
const itemsColletion = db.collection(ITEMS_COLLECTION)
const secondaryCollection = db.collection(SECONDARY_PRICING_COLLECTION)
const primaryStorageAdapter = new StorageAdapter(
Promise.resolve(primaryCollection),
logger
)
const itemsStorageAdapter = new ItemsStorageAdapter(
Promise.resolve(itemsColletion),
logger
)
const secondaryStorageAdapter = new StorageAdapter(
Promise.resolve(secondaryCollection),
logger
)
const primaryFetchAdapter = new PrimaryFetchAdapter(getFetcherCredentials('PRIMARY'), logger)
const secondaryFetchAdapter = new SecondaryFetchAdapter(getFetcherCredentials('SECONDARY'), logger)
const sources = [
{ fetchAdapter: primaryFetchAdapter, storageAdapter: primaryStorageAdapter },
{ fetchAdapter: secondaryFetchAdapter, storageAdapter: secondaryStorageAdapter },
]
try {
const list = await itemsStorageAdapter.getItems()
logger.debug(`Total items to fetch ${list.length}`)
const result = await fetchAndStore(list, sources, logger)
logger.debug('Returning: ', { result })
return {
statusCode: 200,
body: JSON.stringify(result)
}
} catch (error) {
const errorMessage = 'failed to do task'
const { message, name, stack } = error
logger.error(errorMessage, { error: { message, name, stack } })
return {
statusCode: 500,
body: JSON.stringify(new Error(errorMessage))
}
} finally {
await db.close()
}
}
Edit:
I have changed the way i'm invoking the lambda to this below. Basically I tried to listen for every possible event that can say me something. Result is that i get a error event, with response message being Converting circular structure to JSON. Thing is I don't really know about which structure this is, as the value I'm returning is a two elements array of 3 elements (strings and number) arrays.
const lambda = new AWS.Lambda({
httpOptions: { timeout: 660000 },
maxRetries: 0
})
const request = lambda.invoke(
{
FunctionName: `${config.get('env')}-credit-rubric-pricing-fetching-lambda`,
InvocationType: 'RequestResponse',
Payload: '""'
},
(err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
}
)
const results = await request
.on('send', () => {
logger.debug('Request sent to lambda')
})
.on('retry', response => {
logger.debug('Retrying.', { response })
})
.on('extractError', response => {
logger.debug('ExtractError', { response })
})
.on('extractData', response => {
logger.debug('ExtractData', { response })
})
.on('error', response => {
logger.debug('error', { response })
})
.on('succes', response => {
logger.debug('success', { response })
})
.on('complete', response => {
logger.debug('complete', { response })
})
.on('httpHeaders', (statusCode, headers, response, statusMessage) => {
logger.debug('httpHeaders', { statusCode, headers, response, statusMessage })
})
.on('httpData', (chunk, response) => {
logger.debug('httpData', { response, chunk })
})
.on('httpError', (error, response) => {
logger.debug('httpError', { response, error })
})
.on('httpDone', response => {
logger.debug('httpDone', { response })
})
.promise()