diff --git a/src/database/mongo.js b/src/database/mongo.js index 310867cef5..4e2ba46a66 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -86,6 +86,9 @@ mongoModule.createIndices = async function () { const collection = mongoModule.client.collection('objects'); await collection.createIndex({ _key: 1, score: -1 }, { background: true }); await collection.createIndex({ _key: 1, value: -1 }, { background: true, unique: true, sparse: true }); + await collection.createIndex( + { members: 1, _key: 1}, { background: true, partialFilterExpression: { members: { $exists: true } } } + ); await collection.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0, background: true }); winston.info('[database] Checking database indices done!'); }; diff --git a/src/database/mongo/sets.js b/src/database/mongo/sets.js index 784b6acd70..66b4baebab 100644 --- a/src/database/mongo/sets.js +++ b/src/database/mongo/sets.js @@ -115,7 +115,7 @@ module.exports = function (module) { await module.client.collection('objects').bulkWrite([ { updateMany: { filter: { _key: filterKey }, update: update } }, - { deleteMany: { filter: { _key: filterKey, members: { $size: 0 } } } }, + { deleteMany: { filter: { _key: filterKey, members: [] } } }, ], { ordered: true }); } @@ -128,8 +128,9 @@ module.exports = function (module) { const item = await module.client.collection('objects').findOne({ _key: key, members: value, }, { - projection: { _id: 0, members: 0 }, + projection: { _id: 0, _key: 1}, }); + return item !== null && item !== undefined; }; @@ -137,15 +138,31 @@ module.exports = function (module) { if (!key || !Array.isArray(values) || !values.length) { return []; } - values = values.map(v => helpers.valueToString(v)); + const stringValues = values.map(v => helpers.valueToString(v)); - const result = await module.client.collection('objects').findOne({ - _key: key, - }, { - projection: { _id: 0, _key: 0 }, - }); - const membersSet = new Set(result && Array.isArray(result.members) ? result.members : []); - return values.map(v => membersSet.has(v)); + const pipeline = [ + { + $match: { + _key: key, + members: { $in: stringValues, $exists: true }, // Trigger the partial index + }, + }, + { + $project: { + _id: 0, + results: { + $map: { + input: stringValues, + as: 'val', + in: { $in: ['$$val', '$members'] }, + }, + }, + }, + }, + ]; + + const [doc] = await module.client.collection('objects').aggregate(pipeline).toArray(); + return doc ? doc.results : values.map(() => false); }; module.isMemberOfSets = async function (sets, value) { @@ -157,7 +174,7 @@ module.exports = function (module) { const result = await module.client.collection('objects').find({ _key: { $in: sets }, members: value, }, { - projection: { _id: 0, members: 0 }, + projection: { _id: 0, _key: 1 }, }).toArray(); const map = {}; @@ -176,7 +193,7 @@ module.exports = function (module) { const data = await module.client.collection('objects').findOne({ _key: key, }, { - projection: { _id: 0, _key: 0 }, + projection: { _id: 0, members: 1 }, }); return data ? data.members : []; }; diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index eceea5e342..c6f1ee90ce 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -393,13 +393,13 @@ module.exports = function (module) { return []; } const arrayOfKeys = keys.length > 1; - const projection = { _id: 0, value: 1 }; + const projection = arrayOfKeys ? + { _id: 0, _key: 1, value: 1} : + { _id: 0, value: 1 }; if (withScores) { projection.score = 1; } - if (arrayOfKeys) { - projection._key = 1; - } + const data = await module.client.collection('objects').find({ _key: arrayOfKeys ? { $in: keys } : keys[0], }, { projection: projection }) diff --git a/src/groups/invite.js b/src/groups/invite.js index 747111d156..e5b7c7a1ca 100644 --- a/src/groups/invite.js +++ b/src/groups/invite.js @@ -54,9 +54,15 @@ module.exports = function (Groups) { if (!Array.isArray(groupNames)) { groupNames = [groupNames]; } - const sets = []; - groupNames.forEach(groupName => sets.push(`group:${groupName}:pending`, `group:${groupName}:invited`)); - await db.setsRemove(sets, uid); + const sets = [ + ...groupNames.map(g => `group:${g}:pending`), + ...groupNames.map(g => `group:${g}:invited`), + ]; + const isMembers = await db.isMemberOfSets(sets, uid); + const toRemoveSets = sets.filter((set, index) => isMembers[index]); + if (toRemoveSets.length) { + await db.setsRemove(toRemoveSets, uid); + } }; Groups.invite = async function (groupName, uids) { diff --git a/src/upgrades/4.10.0/mongodb-set-members-index.js b/src/upgrades/4.10.0/mongodb-set-members-index.js new file mode 100644 index 0000000000..f7a2284eb7 --- /dev/null +++ b/src/upgrades/4.10.0/mongodb-set-members-index.js @@ -0,0 +1,22 @@ +'use strict'; + + +const db = module.parent.require('./database'); + +module.exports = { + name: 'Add a partial index on set members', + timestamp: Date.UTC(2026, 2, 11), + method: async function () { + const nconf = require.main.require('nconf'); + const isMongo = nconf.get('database') === 'mongo'; + if (!isMongo) { + return; + } + + await db.client.collection('objects').createIndex( + { members: 1, _key: 1 }, + { background: true, partialFilterExpression: { members: { $exists: true } } }, + ); + }, +}; +