fix: promises in groups.leave

speed up user.delete

user.delete calls `groups.leaveAllGroups` which calls rejectMembership with 500+ groups. This function then tries to remove the user from `group:<group>:pending` and `group:<group>:invited` sets so a total for 1k sets. You can't be invited or request membership to privilege groups so filter the groups before sending to rejectMembership

clearGroupTitleIfSet function tries to remove the group title from the user. It was only skipping privilege groups and registered-users, but unverified-users & verified users weren't added to the check

Messaging.leaveRooms, make a single call to isUserInRoom and passing an array of roomIds

In user.delete, check utils.isNumber(uid) once.

Call deleteVotes/deleteChats/revokeAllSessions in Promise.all

If user is local dont call activitypub.actors.remove(), this saves a db call to `await db.isSortedSetMember('usersRemote:lastCrawled', id);`
This commit is contained in:
Barış Soner Uşaklı
2026-03-09 13:29:12 -04:00
parent 457f6cf385
commit 8ec1ccccfe
3 changed files with 47 additions and 30 deletions

View File

@@ -43,20 +43,20 @@ module.exports = function (Groups) {
const promises = []; const promises = [];
if (emptyPrivilegeGroups.length) { if (emptyPrivilegeGroups.length) {
promises.push(Groups.destroy, emptyPrivilegeGroups); promises.push(Groups.destroy(emptyPrivilegeGroups));
} }
if (visibleGroups.length) { if (visibleGroups.length) {
promises.push( promises.push(
db.sortedSetAdd, db.sortedSetAdd(
'groups:visible:memberCount', 'groups:visible:memberCount',
visibleGroups.map(groupData => groupData.memberCount), visibleGroups.map(groupData => groupData.memberCount),
visibleGroups.map(groupData => groupData.name) visibleGroups.map(groupData => groupData.name)
)
); );
} }
await Promise.all(promises);
await Promise.all([ await Promise.all([
...promises,
clearGroupTitleIfSet(groupsToLeave, uid), clearGroupTitleIfSet(groupsToLeave, uid),
leavePublicRooms(groupsToLeave, uid), leavePublicRooms(groupsToLeave, uid),
]); ]);
@@ -82,7 +82,12 @@ module.exports = function (Groups) {
} }
async function clearGroupTitleIfSet(groupNames, uid) { async function clearGroupTitleIfSet(groupNames, uid) {
groupNames = groupNames.filter(groupName => groupName !== 'registered-users' && !Groups.isPrivilegeGroup(groupName)); groupNames = groupNames.filter(
groupName => groupName !== 'registered-users' &&
groupName !== 'unverified-users' &&
groupName !== 'verified-users' &&
!Groups.isPrivilegeGroup(groupName)
);
if (!groupNames.length) { if (!groupNames.length) {
return; return;
} }
@@ -103,7 +108,9 @@ module.exports = function (Groups) {
const groups = await db.getSortedSetRange('groups:createtime', 0, -1); const groups = await db.getSortedSetRange('groups:createtime', 0, -1);
await Promise.all([ await Promise.all([
Groups.leave(groups, uid), Groups.leave(groups, uid),
Groups.rejectMembership(groups, uid), Groups.rejectMembership(
groups.filter(g => !Groups.isPrivilegeGroup(g)), uid
),
]); ]);
}; };

View File

@@ -361,9 +361,11 @@ module.exports = function (Messaging) {
}; };
Messaging.leaveRooms = async (uid, roomIds) => { Messaging.leaveRooms = async (uid, roomIds) => {
const isInRoom = await Promise.all(roomIds.map(roomId => Messaging.isUserInRoom(uid, roomId))); const isInRoom = await Messaging.isUserInRoom(uid, roomIds);
roomIds = roomIds.filter((roomId, index) => isInRoom[index]); roomIds = roomIds.filter((roomId, index) => isInRoom[index]);
if (!roomIds.length) {
return;
}
const roomKeys = [ const roomKeys = [
...roomIds.map(roomId => `chat:room:${roomId}:uids`), ...roomIds.map(roomId => `chat:room:${roomId}:uids`),
...roomIds.map(roomId => `chat:room:${roomId}:owners`), ...roomIds.map(roomId => `chat:room:${roomId}:owners`),

View File

@@ -33,10 +33,15 @@ module.exports = function (User) {
throw new Error('[[error:already-deleting]]'); throw new Error('[[error:already-deleting]]');
} }
deletesInProgress[uid] = 'user.delete'; deletesInProgress[uid] = 'user.delete';
await deletePosts(callerUid, uid); async function deletePostsTopics() {
await deleteTopics(callerUid, uid); await deletePosts(callerUid, uid);
await deleteUploads(callerUid, uid); await deleteTopics(callerUid, uid);
await deleteQueued(uid); }
await Promise.all([
deletePostsTopics(),
deleteUploads(callerUid, uid),
deleteQueued(uid),
]);
delete deletesInProgress[uid]; delete deletesInProgress[uid];
}; };
@@ -90,9 +95,9 @@ module.exports = function (User) {
throw new Error('[[error:already-deleting]]'); throw new Error('[[error:already-deleting]]');
} }
deletesInProgress[uid] = 'user.deleteAccount'; deletesInProgress[uid] = 'user.deleteAccount';
const isLocal = utils.isNumber(uid);
await removeFromSortedSets(uid); await removeFromSortedSets(uid);
const userData = await db.getObject(utils.isNumber(uid) ? `user:${uid}` : `userRemote:${uid}`); const userData = await db.getObject(isLocal ? `user:${uid}` : `userRemote:${uid}`);
if (!userData || !userData.username) { if (!userData || !userData.username) {
delete deletesInProgress[uid]; delete deletesInProgress[uid];
@@ -100,9 +105,11 @@ module.exports = function (User) {
} }
await plugins.hooks.fire('static:user.delete', { uid: uid, userData: userData }); await plugins.hooks.fire('static:user.delete', { uid: uid, userData: userData });
await deleteVotes(uid); await Promise.all([
await deleteChats(uid); deleteVotes(uid),
await User.auth.revokeAllSessions(uid); deleteChats(uid),
User.auth.revokeAllSessions(uid),
]);
const keys = [ const keys = [
`uid:${uid}:notifications:read`, `uid:${uid}:notifications:read`,
@@ -143,7 +150,7 @@ module.exports = function (User) {
await Promise.all([ await Promise.all([
db.sortedSetRemoveBulk(bulkRemove), db.sortedSetRemoveBulk(bulkRemove),
utils.isNumber(uid) ? db.decrObjectField('global', 'userCount') : null, isLocal ? db.decrObjectField('global', 'userCount') : null,
db.deleteAll(keys), db.deleteAll(keys),
db.setRemove('invitation:uids', uid), db.setRemove('invitation:uids', uid),
deleteUserIps(uid), deleteUserIps(uid),
@@ -156,13 +163,13 @@ module.exports = function (User) {
flags.resolveFlag('user', uid, uid), flags.resolveFlag('user', uid, uid),
User.reset.cleanByUid(uid), User.reset.cleanByUid(uid),
User.email.expireValidation(uid), User.email.expireValidation(uid),
activitypub.actors.remove(uid), !isLocal ? activitypub.actors.remove(uid) : null,
]); ]);
await db.deleteAll([ await db.deleteAll([
`followers:${uid}`, `following:${uid}`, `followers:${uid}`, `following:${uid}`,
`uid:${uid}:followed_tags`, `uid:${uid}:followed_tids`, `uid:${uid}:followed_tags`, `uid:${uid}:followed_tids`,
`uid:${uid}:ignored_tids`, `uid:${uid}:ignored_tids`,
`${utils.isNumber(uid) ? 'user' : 'userRemote'}:${uid}`, `${isLocal ? 'user' : 'userRemote'}:${uid}`,
]); ]);
delete deletesInProgress[uid]; delete deletesInProgress[uid];
return userData; return userData;
@@ -184,11 +191,10 @@ module.exports = function (User) {
} }
async function deleteVotes(uid) { async function deleteVotes(uid) {
const [upvotedPids, downvotedPids] = await Promise.all([ const upvoteDownvotePids = await db.getSortedSetRange([
db.getSortedSetRange(`uid:${uid}:upvote`, 0, -1), `uid:${uid}:upvote`, `uid:${uid}:downvote`,
db.getSortedSetRange(`uid:${uid}:downvote`, 0, -1), ], 0, -1);
]); const pids = _.uniq(upvoteDownvotePids).filter(Boolean);
const pids = _.uniq(upvotedPids.concat(downvotedPids).filter(Boolean));
await async.eachSeries(pids, async (pid) => { await async.eachSeries(pids, async (pid) => {
await posts.unvote(pid, uid); await posts.unvote(pid, uid);
}); });
@@ -203,8 +209,10 @@ module.exports = function (User) {
async function deleteUserIps(uid) { async function deleteUserIps(uid) {
const ips = await db.getSortedSetRange(`uid:${uid}:ip`, 0, -1); const ips = await db.getSortedSetRange(`uid:${uid}:ip`, 0, -1);
await db.sortedSetsRemove(ips.map(ip => `ip:${ip}:uid`), uid); await Promise.all([
await db.delete(`uid:${uid}:ip`); db.sortedSetsRemove(ips.map(ip => `ip:${ip}:uid`), uid),
db.delete(`uid:${uid}:ip`),
]);
} }
async function deleteUserFromFollowers(uid) { async function deleteUserFromFollowers(uid) {