Last active
January 21, 2021 20:33
-
-
Save AbdelrahmanHafez/15e577d0e4c75e1fe4d4e2320ab10cd2 to your computer and use it in GitHub Desktop.
Comparing bulkWrite performance with all `updateOne`s vs using multiple `updateMany` grouped by the same `update` object.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://stackoverflow.com/questions/65831219/mongodb-bulkwrite-multiple-updateone-vs-updatemany?noredirect=1#comment116399692_65831219 | |
'use strict'; | |
const mongoose = require('mongoose'); | |
const { Schema } = mongoose; | |
const DOCUMENTS_COUNT = 1_000_000; | |
const UPDATE_MANY_OPERATIONS_COUNT = 100; | |
const MINIMUM_BALANCE = 0; | |
const MAXIMUM_BALANCE = 100; | |
const SAMPLES_COUNT = 10; | |
const bankAccountSchema = new Schema({ | |
balance: { type: Number } | |
}); | |
const BankAccount = mongoose.model('BankAccount', bankAccountSchema); | |
mainRunner().catch(console.error); | |
async function mainRunner () { | |
for (let i = 0; i < SAMPLES_COUNT; i++) { | |
await runOneCycle(buildUpdateManyWriteOperations).catch(console.error); | |
await runOneCycle(buildUpdateOneWriteOperations).catch(console.error); | |
console.log('-'.repeat(100)); | |
} | |
process.exit(0); | |
} | |
/** | |
* | |
* @param {buildUpdateManyWriteOperations|buildUpdateOneWriteOperations} buildBulkWrite | |
*/ | |
async function runOneCycle (buildBulkWrite) { | |
await mongoose.connect('mongodb://localhost:27017/test', { | |
useNewUrlParser: true, | |
useUnifiedTopology: true | |
}); | |
await mongoose.connection.dropDatabase(); | |
const { accounts } = await createAccounts({ accountsCount: DOCUMENTS_COUNT }); | |
const { writeOperations } = buildBulkWrite({ accounts }); | |
const writeStartedAt = Date.now(); | |
await BankAccount.bulkWrite(writeOperations); | |
const writeEndedAt = Date.now(); | |
console.log(`Write operations took ${(writeEndedAt - writeStartedAt) / 1000} seconds with \`${buildBulkWrite.name}\`.`); | |
} | |
async function createAccounts ({ accountsCount }) { | |
const rawAccounts = Array.from({ length: accountsCount }, () => ({ balance: getRandomInteger(MINIMUM_BALANCE, MAXIMUM_BALANCE) })); | |
const accounts = await BankAccount.insertMany(rawAccounts); | |
return { accounts }; | |
} | |
function buildUpdateOneWriteOperations ({ accounts }) { | |
const writeOperations = shuffleArray(accounts).map((account) => ({ | |
updateOne: { | |
filter: { _id: account._id }, | |
update: { balance: getRandomInteger(MINIMUM_BALANCE, MAXIMUM_BALANCE) } | |
} | |
})); | |
return { writeOperations }; | |
} | |
function buildUpdateManyWriteOperations ({ accounts }) { | |
shuffleArray(accounts); | |
const accountsChunks = chunkArray(accounts, DOCUMENTS_COUNT / UPDATE_MANY_OPERATIONS_COUNT); | |
const writeOperations = accountsChunks.map((accountsChunk) => ({ | |
updateMany: { | |
filter: { _id: { $in: accountsChunk.map(account => account._id) } }, | |
update: { balance: getRandomInteger(MINIMUM_BALANCE, MAXIMUM_BALANCE) } | |
} | |
})); | |
return { writeOperations }; | |
} | |
function getRandomInteger (min = 0, max = 1) { | |
min = Math.ceil(min); | |
max = Math.floor(max); | |
return min + Math.floor(Math.random() * (max - min + 1)); | |
} | |
function shuffleArray (array) { | |
let currentIndex = array.length; | |
let temporaryValue; | |
let randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
function chunkArray (array, sizeOfTheChunkedArray) { | |
const chunked = []; | |
for (const element of array) { | |
const last = chunked[chunked.length - 1]; | |
if (!last || last.length === sizeOfTheChunkedArray) { | |
chunked.push([element]); | |
} else { | |
last.push(element); | |
} | |
} | |
return chunked; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment