From 676acb7e8c72dbaa228f9ecf377548389f1ac87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 11 Feb 2025 10:39:24 -0500 Subject: [PATCH] perf: closes #13145, reduce calls in actors.prune instead of deleting 18k users everyday delete max 500 every 30mins like notifications.prune refactor getLocalFollowCounts so it works with an array of actors dont make dbcalls for uids that dont exist --- src/activitypub/actors.js | 52 +++++++++++++++++++++++---------------- src/activitypub/index.js | 2 +- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index 2208f03de7..939309c8dc 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -230,19 +230,28 @@ Actors.getLocalFollowers = async (id) => { return response; }; -Actors.getLocalFollowCounts = async (actor) => { - let followers = 0; // x local followers - let following = 0; // following x local users - if (!activitypub.helpers.isUri(actor)) { - return { followers, following }; +Actors.getLocalFollowCounts = async (actors) => { + if (!Array.isArray(actors)) { + actors = [actors]; } - [followers, following] = await Promise.all([ - db.sortedSetCard(`followersRemote:${actor}`), - db.sortedSetCard(`followingRemote:${actor}`), + const validActors = actors.filter(actor => activitypub.helpers.isUri(actor)); + const followerKeys = validActors.map(actor => `followersRemote:${actor}`); + const followingKeys = validActors.map(actor => `followingRemote:${actor}`); + + const [followersCounts, followingCounts] = await Promise.all([ + db.sortedSetsCard(followerKeys), + db.sortedSetsCard(followingKeys), ]); - return { followers, following }; + const results = actors.map((actor, index) => { + if (!validActors.includes(actor)) { + return { followers: 0, following: 0 }; + } + return { followers: followersCounts[index], following: followingCounts[index] }; + }); + + return Array.isArray(actors) ? results : results[0]; }; Actors.remove = async (id) => { @@ -288,7 +297,7 @@ Actors.prune = async () => { const days = parseInt(meta.config.activitypubUserPruneDays, 10); const timestamp = Date.now() - (1000 * 60 * 60 * 24 * days); - const uids = await db.getSortedSetRangeByScore('usersRemote:lastCrawled', 0, -1, '-inf', timestamp); + const uids = await db.getSortedSetRangeByScore('usersRemote:lastCrawled', 0, 500, '-inf', timestamp); if (!uids.length) { winston.info('[actors/prune] No remote users to prune, all done.'); return; @@ -299,18 +308,19 @@ Actors.prune = async () => { await batch.processArray(uids, async (uids) => { const exists = await db.exists(uids.map(uid => `userRemote:${uid}`)); - const [postCounts, roomCounts] = await Promise.all([ - db.sortedSetsCard(uids.map(uid => `uid:${uid}:posts`)), - db.sortedSetsCard(uids.map(uid => `uid:${uid}:chat:rooms`)), - ]); - await Promise.all(uids.map(async (uid, idx) => { - if (!exists[idx]) { - // id in zset but not asserted, handle and return early - await db.sortedSetRemove('usersRemote:lastCrawled', uid); - return; - } - const { followers, following } = await Actors.getLocalFollowCounts(uid); + const uidsThatExist = uids.filter((uid, idx) => exists[idx]); + const uidsThatDontExist = uids.filter((uid, idx) => !exists[idx]); + await db.sortedSetRemove('usersRemote:lastCrawled', uidsThatDontExist); + + const [postCounts, roomCounts, followCounts] = await Promise.all([ + db.sortedSetsCard(uidsThatExist.map(uid => `uid:${uid}:posts`)), + db.sortedSetsCard(uidsThatExist.map(uid => `uid:${uid}:chat:rooms`)), + Actors.getLocalFollowCounts(uidsThatExist), + ]); + + await Promise.all(uidsThatExist.map(async (uid, idx) => { + const { followers, following } = followCounts[idx]; const postCount = postCounts[idx]; const roomCount = roomCounts[idx]; if ([postCount, roomCount, followers, following].every(metric => metric < 1)) { diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 501ac3402a..08a33ecdf1 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -68,7 +68,7 @@ ActivityPub.startJobs = () => { } }, null, true, null, null, false); // change last argument to true for debugging - new CronJob('0 1 * * *', async () => { + new CronJob('*/30 * * * *', async () => { try { await ActivityPub.actors.prune(); } catch (err) {