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 = [];
if (emptyPrivilegeGroups.length) {
promises.push(Groups.destroy, emptyPrivilegeGroups);
promises.push(Groups.destroy(emptyPrivilegeGroups));
}
if (visibleGroups.length) {
promises.push(
db.sortedSetAdd,
'groups:visible:memberCount',
visibleGroups.map(groupData => groupData.memberCount),
visibleGroups.map(groupData => groupData.name)
db.sortedSetAdd(
'groups:visible:memberCount',
visibleGroups.map(groupData => groupData.memberCount),
visibleGroups.map(groupData => groupData.name)
)
);
}
await Promise.all(promises);
await Promise.all([
...promises,
clearGroupTitleIfSet(groupsToLeave, uid),
leavePublicRooms(groupsToLeave, uid),
]);
@@ -82,7 +82,12 @@ module.exports = function (Groups) {
}
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) {
return;
}
@@ -103,7 +108,9 @@ module.exports = function (Groups) {
const groups = await db.getSortedSetRange('groups:createtime', 0, -1);
await Promise.all([
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) => {
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]);
if (!roomIds.length) {
return;
}
const roomKeys = [
...roomIds.map(roomId => `chat:room:${roomId}:uids`),
...roomIds.map(roomId => `chat:room:${roomId}:owners`),

View File

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