diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index 444bb7bcb0..73b3b3ceea 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -185,7 +185,11 @@ export class CacheService implements OnApplicationShutdown { this.userByIdCache = this.cacheManagementService.createQuantumKVCache('userById', { lifetime: 1000 * 60 * 5, // 5m fetcher: async (userId) => await this.usersRepository.findOneByOrFail({ id: userId }), - bulkFetcher: async (userIds) => await this.usersRepository.findBy({ id: In(userIds) }).then(us => us.map(u => [u.id, u])), + optionalFetcher: async (userId) => await this.usersRepository.findOneBy({ id: userId }), + bulkFetcher: async (userIds) => { + const users = await this.usersRepository.findBy({ id: In(userIds) }); + return users.map(user => [user.id, user]); + }, }); this.nativeTokenCache = this.cacheManagementService.createQuantumKVCache('localUserByNativeToken', { @@ -198,6 +202,14 @@ export class CacheService implements OnApplicationShutdown { .getOneOrFail() as { id: string }; return id; }, + optionalFetcher: async (token) => { + const result = await this.usersRepository + .createQueryBuilder('user') + .select('user.id') + .where({ token }) + .getOne() as { id: string } | null; + return result?.id; + }, bulkFetcher: async (tokens) => { const users = await this.usersRepository .createQueryBuilder('user') @@ -205,7 +217,7 @@ export class CacheService implements OnApplicationShutdown { .addSelect('user.token') .where({ token: In(tokens) }) .getMany() as { id: string, token: string }[]; - return users.map(u => [u.token, u.id]); + return users.map(user => [user.token, user.id]); }, }); @@ -223,224 +235,314 @@ export class CacheService implements OnApplicationShutdown { .getOneOrFail(); return id; }, - // No bulk fetcher for this + optionalFetcher: async (acct) => { + const parsed = Acct.parse(acct); + const res = await this.usersRepository + .createQueryBuilder('user') + .select('user.id') + .where({ + usernameLower: parsed.username.toLowerCase(), + host: parsed.host ?? IsNull(), + }) + .getOne(); + return res?.id; + }, + // no bulkFetcher possible }); this.userProfileCache = this.cacheManagementService.createQuantumKVCache('userProfile', { lifetime: 1000 * 60 * 30, // 30m - fetcher: (key) => this.userProfilesRepository.findOneBy({ userId: key }), - bulkFetcher: userIds => this.userProfilesRepository.findBy({ userId: In(userIds) }).then(ps => ps.map(p => [p.userId, p])), + fetcher: async userId => await this.userProfilesRepository.findOneByOrFail({ userId }), + optionalFetcher: async userId => await this.userProfilesRepository.findOneBy({ userId }), + bulkFetcher: async userIds => { + const profiles = await this.userProfilesRepository.findBy({ userId: In(userIds) }); + return profiles.map(profile => [profile.userId, profile]); + }, }); this.userMutingsCache = this.cacheManagementService.createQuantumKVCache>('userMutings', { lifetime: 1000 * 60 * 30, // 3m (workaround for mute expiration) - fetcher: (key) => this.mutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))), - bulkFetcher: muterIds => this.mutingsRepository - .createQueryBuilder('muting') - .select('"muting"."muterId"', 'muterId') - .addSelect('array_agg("muting"."muteeId")', 'muteeIds') - .where({ muterId: In(muterIds) }) - .andWhere(new Brackets(qb => qb - .orWhere({ expiresAt: IsNull() }) - .orWhere({ expiresAt: MoreThan(this.timeService.date) }))) - .groupBy('muting.muterId') - .getRawMany<{ muterId: string, muteeIds: string[] }>() - .then(ms => ms.map(m => [m.muterId, new Set(m.muteeIds)])), + fetcher: async muterId => { + const mutings = await this.mutingsRepository.find({ where: { muterId: muterId }, select: ['muteeId'] }); + return new Set(mutings.map(muting => muting.muteeId)); + }, + // no optionalFetcher needed + bulkFetcher: async muterIds => { + const mutings = await this.mutingsRepository + .createQueryBuilder('muting') + .select('"muting"."muterId"', 'muterId') + .addSelect('array_agg("muting"."muteeId")', 'muteeIds') + .where({ muterId: In(muterIds) }) + .andWhere(new Brackets(qb => qb + .orWhere({ expiresAt: IsNull() }) + .orWhere({ expiresAt: MoreThan(this.timeService.date) }))) + .groupBy('muting.muterId') + .getRawMany<{ muterId: string, muteeIds: string[] }>(); + return mutings.map(muting => [muting.muterId, new Set(muting.muteeIds)]); + }, }); this.userMutedCache = this.cacheManagementService.createQuantumKVCache>('userMuted', { lifetime: 1000 * 60 * 30, // 3m (workaround for mute expiration) - fetcher: (key) => this.mutingsRepository.find({ where: { muteeId: key }, select: ['muterId'] }).then(xs => new Set(xs.map(x => x.muterId))), - bulkFetcher: muteeIds => this.mutingsRepository - .createQueryBuilder('muting') - .select('"muting"."muteeId"', 'muteeId') - .addSelect('array_agg("muting"."muterId")', 'muterIds') - .where({ muteeId: In(muteeIds) }) - .andWhere(new Brackets(qb => qb - .orWhere({ expiresAt: IsNull() }) - .orWhere({ expiresAt: MoreThan(this.timeService.date) }))) - .groupBy('muting.muteeId') - .getRawMany<{ muteeId: string, muterIds: string[] }>() - .then(ms => ms.map(m => [m.muteeId, new Set(m.muterIds)])), + fetcher: async muteeId => { + const mutings = await this.mutingsRepository.find({ where: { muteeId }, select: ['muterId'] }); + return new Set(mutings.map(muting => muting.muterId)); + }, + // no optionalFetcher needed + bulkFetcher: async muteeIds => { + const mutings = await this.mutingsRepository + .createQueryBuilder('muting') + .select('"muting"."muteeId"', 'muteeId') + .addSelect('array_agg("muting"."muterId")', 'muterIds') + .where({ muteeId: In(muteeIds) }) + .andWhere(new Brackets(qb => qb + .orWhere({ expiresAt: IsNull() }) + .orWhere({ expiresAt: MoreThan(this.timeService.date) }))) + .groupBy('muting.muteeId') + .getRawMany<{ muteeId: string, muterIds: string[] }>(); + return mutings.map(muting => [muting.muteeId, new Set(muting.muterIds)]); + }, }); this.userBlockingCache = this.cacheManagementService.createQuantumKVCache>('userBlocking', { lifetime: 1000 * 60 * 30, // 30m - fetcher: (key) => this.blockingsRepository.find({ where: { blockerId: key }, select: ['blockeeId'] }).then(xs => new Set(xs.map(x => x.blockeeId))), - bulkFetcher: blockerIds => this.blockingsRepository - .createQueryBuilder('blocking') - .select('"blocking"."blockerId"', 'blockerId') - .addSelect('array_agg("blocking"."blockeeId")', 'blockeeIds') - .where({ blockerId: In(blockerIds) }) - .groupBy('blocking.blockerId') - .getRawMany<{ blockerId: string, blockeeIds: string[] }>() - .then(ms => ms.map(m => [m.blockerId, new Set(m.blockeeIds)])), + fetcher: async blockerId => { + const blockings = await this.blockingsRepository.find({ where: { blockerId }, select: ['blockeeId'] }); + return new Set(blockings.map(blocking => blocking.blockeeId)); + }, + // no optionalFetcher needed + bulkFetcher: async blockerIds => { + const blockings = await this.blockingsRepository + .createQueryBuilder('blocking') + .select('"blocking"."blockerId"', 'blockerId') + .addSelect('array_agg("blocking"."blockeeId")', 'blockeeIds') + .where({ blockerId: In(blockerIds) }) + .groupBy('blocking.blockerId') + .getRawMany<{ blockerId: string, blockeeIds: string[] }>(); + return blockings.map(blocking => [blocking.blockerId, new Set(blocking.blockeeIds)]); + }, }); this.userBlockedCache = this.cacheManagementService.createQuantumKVCache>('userBlocked', { lifetime: 1000 * 60 * 30, // 30m - fetcher: (key) => this.blockingsRepository.find({ where: { blockeeId: key }, select: ['blockerId'] }).then(xs => new Set(xs.map(x => x.blockerId))), - bulkFetcher: blockeeIds => this.blockingsRepository - .createQueryBuilder('blocking') - .select('"blocking"."blockeeId"', 'blockeeId') - .addSelect('array_agg("blocking"."blockerId")', 'blockerIds') - .where({ blockeeId: In(blockeeIds) }) - .groupBy('blocking.blockeeId') - .getRawMany<{ blockeeId: string, blockerIds: string[] }>() - .then(ms => ms.map(m => [m.blockeeId, new Set(m.blockerIds)])), + fetcher: async blockeeId => { + const blockings = await this.blockingsRepository.find({ where: { blockeeId: blockeeId }, select: ['blockerId'] }); + return new Set(blockings.map(blocking => blocking.blockerId)); + }, + // no optionalFetcher needed + bulkFetcher: async blockeeIds => { + const blockings = await this.blockingsRepository + .createQueryBuilder('blocking') + .select('"blocking"."blockeeId"', 'blockeeId') + .addSelect('array_agg("blocking"."blockerId")', 'blockerIds') + .where({ blockeeId: In(blockeeIds) }) + .groupBy('blocking.blockeeId') + .getRawMany<{ blockeeId: string, blockerIds: string[] }>(); + return blockings.map(blocking => [blocking.blockeeId, new Set(blocking.blockerIds)]); + }, }); this.userListMembershipsCache = this.cacheManagementService.createQuantumKVCache>('userListMemberships', { lifetime: 1000 * 60 * 30, // 30m - fetcher: async userId => await this.userListMembershipsRepository.findBy({ userId }).then(ms => new Map(ms.map(m => [m.userListId, m]))), - bulkFetcher: async userIds => await this.userListMembershipsRepository - .findBy({ userId: In(userIds) }) - .then(ms => ms - .reduce((groups, m) => { - let listsForUser = groups.get(m.userId); - if (!listsForUser) { - listsForUser = new Map(); - groups.set(m.userId, listsForUser); - } - listsForUser.set(m.userListId, m); - return groups; - }, new Map>)), + fetcher: async userId => { + const memberships = await this.userListMembershipsRepository.findBy({ userId }); + return new Map(memberships.map(membership => [membership.userListId, membership])); + }, + // no optionalFetcher needed + bulkFetcher: async userIds => { + const groups = new Map>; + + const memberships = await this.userListMembershipsRepository.findBy({ userId: In(userIds) }); + for (const membership of memberships) { + let listsForUser = groups.get(membership.userId); + if (!listsForUser) { + listsForUser = new Map(); + groups.set(membership.userId, listsForUser); + } + listsForUser.set(membership.userListId, membership); + } + + return groups; + }, }); this.listUserMembershipsCache = this.cacheManagementService.createQuantumKVCache>('listUserMemberships', { lifetime: 1000 * 60 * 30, // 30m - fetcher: async userListId => await this.userListMembershipsRepository.findBy({ userListId }).then(ms => new Map(ms.map(m => [m.userId, m]))), - bulkFetcher: async userListIds => await this.userListMembershipsRepository - .findBy({ userListId: In(userListIds) }) - .then(ms => ms - .reduce((groups, m) => { - let usersForList = groups.get(m.userListId); - if (!usersForList) { - usersForList = new Map(); - groups.set(m.userListId, usersForList); - } - usersForList.set(m.userId, m); - return groups; - }, new Map>)), + fetcher: async userListId => { + const memberships = await this.userListMembershipsRepository.findBy({ userListId }); + return new Map(memberships.map(membership => [membership.userId, membership])); + }, + // no optionalFetcher needed + bulkFetcher: async userListIds => { + const memberships = await this.userListMembershipsRepository.findBy({ userListId: In(userListIds) }); + const groups = new Map>(); + for (const membership of memberships) { + let usersForList = groups.get(membership.userListId); + if (!usersForList) { + usersForList = new Map(); + groups.set(membership.userListId, usersForList); + } + usersForList.set(membership.userId, membership); + } + return groups; + }, }); this.userListFavoritesCache = cacheManagementService.createQuantumKVCache>('userListFavorites', { lifetime: 1000 * 60 * 30, // 30m - fetcher: async userId => await this.userListFavoritesRepository.findBy({ userId }).then(fs => new Set(fs.map(f => f.userListId))), - bulkFetcher: async userIds => await this.userListFavoritesRepository - .createQueryBuilder('favorite') - .select('"favorite"."userId"', 'userId') - .addSelect('array_agg("favorite"."userListId")', 'userListIds') - .where({ userId: In(userIds) }) - .groupBy('favorite.userId') - .getRawMany<{ userId: string, userListIds: string[] }>() - .then(fs => fs.map(f => [f.userId, new Set(f.userListIds)])), + fetcher: async userId => { + const favorites = await this.userListFavoritesRepository.find({ where: { userId }, select: ['userListId'] }); + return new Set(favorites.map(favorites => favorites.userListId)); + }, + // no optionalFetcher needed + bulkFetcher: async userIds => { + const favorites = await this.userListFavoritesRepository + .createQueryBuilder('favorite') + .select('"favorite"."userId"', 'userId') + .addSelect('array_agg("favorite"."userListId")', 'userListIds') + .where({ userId: In(userIds) }) + .groupBy('favorite.userId') + .getRawMany<{ userId: string, userListIds: string[] }>(); + return favorites.map(favorite => [favorite.userId, new Set(favorite.userListIds)]); + }, }); this.listUserFavoritesCache = cacheManagementService.createQuantumKVCache>('listUserFavorites', { lifetime: 1000 * 60 * 30, // 30m - fetcher: async userListId => await this.userListFavoritesRepository.findBy({ userListId }).then(fs => new Set(fs.map(f => f.userId))), - bulkFetcher: async userListIds => await this.userListFavoritesRepository - .createQueryBuilder('favorite') - .select('"favorite"."userListId"', 'userListId') - .addSelect('array_agg("favorite"."userId")', 'userIds') - .where({ userListId: In(userListIds) }) - .groupBy('favorite.userListId') - .getRawMany<{ userListId: string, userIds: string[] }>() - .then(fs => fs.map(f => [f.userListId, new Set(f.userIds)])), + fetcher: async userListId => { + const favorites = await this.userListFavoritesRepository.find({ where: { userListId }, select: ['userId'] }); + return new Set(favorites.map(favorite => favorite.userId)); + }, + // no optionalFetcher needed + bulkFetcher: async userListIds => { + const favorites = await this.userListFavoritesRepository + .createQueryBuilder('favorite') + .select('"favorite"."userListId"', 'userListId') + .addSelect('array_agg("favorite"."userId")', 'userIds') + .where({ userListId: In(userListIds) }) + .groupBy('favorite.userListId') + .getRawMany<{ userListId: string, userIds: string[] }>(); + return favorites.map(favorite => [favorite.userListId, new Set(favorite.userIds)]); + }, }); this.renoteMutingsCache = this.cacheManagementService.createQuantumKVCache>('renoteMutings', { lifetime: 1000 * 60 * 30, // 30m - fetcher: (key) => this.renoteMutingsRepository.find({ where: { muterId: key }, select: ['muteeId'] }).then(xs => new Set(xs.map(x => x.muteeId))), - bulkFetcher: muterIds => this.renoteMutingsRepository - .createQueryBuilder('muting') - .select('"muting"."muterId"', 'muterId') - .addSelect('array_agg("muting"."muteeId")', 'muteeIds') - .where({ muterId: In(muterIds) }) - .groupBy('muting.muterId') - .getRawMany<{ muterId: string, muteeIds: string[] }>() - .then(ms => ms.map(m => [m.muterId, new Set(m.muteeIds)])), + fetcher: async muterId => { + const mutings = await this.renoteMutingsRepository.find({ where: { muterId: muterId }, select: ['muteeId'] }); + return new Set(mutings.map(muting => muting.muteeId)); + }, + // no optionalFetcher needed + bulkFetcher: async muterIds => { + const mutings = await this.renoteMutingsRepository + .createQueryBuilder('muting') + .select('"muting"."muterId"', 'muterId') + .addSelect('array_agg("muting"."muteeId")', 'muteeIds') + .where({ muterId: In(muterIds) }) + .groupBy('muting.muterId') + .getRawMany<{ muterId: string, muteeIds: string[] }>(); + return mutings.map(muting => [muting.muterId, new Set(muting.muteeIds)]); + }, }); this.threadMutingsCache = this.cacheManagementService.createQuantumKVCache>('threadMutings', { lifetime: 1000 * 60 * 30, // 30m - fetcher: muterId => this.noteThreadMutingsRepository - .find({ where: { userId: muterId, isPostMute: false }, select: { threadId: true } }) - .then(ms => new Set(ms.map(m => m.threadId))), - bulkFetcher: muterIds => this.noteThreadMutingsRepository - .createQueryBuilder('muting') - .select('"muting"."userId"', 'userId') - .addSelect('array_agg("muting"."threadId")', 'threadIds') - .groupBy('"muting"."userId"') - .where({ userId: In(muterIds), isPostMute: false }) - .getRawMany<{ userId: string, threadIds: string[] }>() - .then(ms => ms.map(m => [m.userId, new Set(m.threadIds)])), + fetcher: async muterId => { + const mutings = await this.noteThreadMutingsRepository.find({ where: { userId: muterId, isPostMute: false }, select: { threadId: true } }); + return new Set(mutings.map(muting => muting.threadId)); + }, + // no optionalFetcher needed + bulkFetcher: async muterIds => { + const mutings = await this.noteThreadMutingsRepository + .createQueryBuilder('muting') + .select('"muting"."userId"', 'userId') + .addSelect('array_agg("muting"."threadId")', 'threadIds') + .groupBy('"muting"."userId"') + .where({ userId: In(muterIds), isPostMute: false }) + .getRawMany<{ userId: string, threadIds: string[] }>(); + return mutings.map(muting => [muting.userId, new Set(muting.threadIds)]); + }, }); this.noteMutingsCache = this.cacheManagementService.createQuantumKVCache>('noteMutings', { lifetime: 1000 * 60 * 30, // 30m - fetcher: muterId => this.noteThreadMutingsRepository - .find({ where: { userId: muterId, isPostMute: true }, select: { threadId: true } }) - .then(ms => new Set(ms.map(m => m.threadId))), - bulkFetcher: muterIds => this.noteThreadMutingsRepository - .createQueryBuilder('muting') - .select('"muting"."userId"', 'userId') - .addSelect('array_agg("muting"."threadId")', 'threadIds') - .groupBy('"muting"."userId"') - .where({ userId: In(muterIds), isPostMute: true }) - .getRawMany<{ userId: string, threadIds: string[] }>() - .then(ms => ms.map(m => [m.userId, new Set(m.threadIds)])), + fetcher: async muterId => { + const mutings = await this.noteThreadMutingsRepository.find({ where: { userId: muterId, isPostMute: true }, select: { threadId: true } }); + return new Set(mutings.map(mutings => mutings.threadId)); + }, + // no optionalFetcher needed + bulkFetcher: async muterIds => { + const mutings = await this.noteThreadMutingsRepository + .createQueryBuilder('muting') + .select('"muting"."userId"', 'userId') + .addSelect('array_agg("muting"."threadId")', 'threadIds') + .groupBy('"muting"."userId"') + .where({ userId: In(muterIds), isPostMute: true }) + .getRawMany<{ userId: string, threadIds: string[] }>(); + return mutings.map(muting => [muting.userId, new Set(muting.threadIds)]); + }, }); this.userFollowingsCache = this.cacheManagementService.createQuantumKVCache>>('userFollowings', { lifetime: 1000 * 60 * 30, // 30m - fetcher: (key) => this.followingsRepository.findBy({ followerId: key }).then(xs => new Map(xs.map(f => [f.followeeId, f]))), - bulkFetcher: followerIds => this.followingsRepository - .findBy({ followerId: In(followerIds) }) - .then(fs => fs - .reduce((groups, f) => { - let group = groups.get(f.followerId); - if (!group) { - group = new Map(); - groups.set(f.followerId, group); - } - group.set(f.followeeId, f); - return groups; - }, new Map>>)), + fetcher: async followerId => { + const followings = await this.followingsRepository.findBy({ followerId: followerId }); + return new Map(followings.map(following => [following.followeeId, following])); + }, + // no optionalFetcher needed + bulkFetcher: async followerIds => { + const groups = new Map>>(); + + const followings = await this.followingsRepository.findBy({ followerId: In(followerIds) }); + for (const following of followings) { + let group = groups.get(following.followerId); + if (!group) { + group = new Map(); + groups.set(following.followerId, group); + } + group.set(following.followeeId, following); + } + + return groups; + }, }); this.userFollowersCache = this.cacheManagementService.createQuantumKVCache>>('userFollowers', { lifetime: 1000 * 60 * 30, // 30m - fetcher: followeeId => this.followingsRepository.findBy({ followeeId: followeeId }).then(xs => new Map(xs.map(x => [x.followerId, x]))), - bulkFetcher: followeeIds => this.followingsRepository - .findBy({ followeeId: In(followeeIds) }) - .then(fs => fs - .reduce((groups, f) => { - let group = groups.get(f.followeeId); - if (!group) { - group = new Map(); - groups.set(f.followeeId, group); - } - group.set(f.followerId, f); - return groups; - }, new Map>>)), + fetcher: async followeeId => { + const followings = await this.followingsRepository.findBy({ followeeId: followeeId }); + return new Map(followings.map(following => [following.followerId, following])); + }, + // no optionalFetcher needed + bulkFetcher: async followeeIds => { + const groups = new Map>>(); + + const followings = await this.followingsRepository.findBy({ followeeId: In(followeeIds) }); + for (const following of followings) { + let group = groups.get(following.followeeId); + if (!group) { + group = new Map(); + groups.set(following.followeeId, group); + } + group.set(following.followerId, following); + } + + return groups; + }, }); this.hibernatedUserCache = this.cacheManagementService.createQuantumKVCache('hibernatedUsers', { lifetime: 1000 * 60 * 30, // 30m fetcher: async userId => { - const result = await this.usersRepository.findOne({ - where: { id: userId }, - select: { isHibernated: true }, - }); + const { isHibernated } = await this.usersRepository.findOneOrFail({ where: { id: userId }, select: { isHibernated: true } }); + return isHibernated; + }, + optionalFetcher: async userId => { + const result = await this.usersRepository.findOne({ where: { id: userId }, select: { isHibernated: true } }); return result?.isHibernated; }, bulkFetcher: async userIds => { - const results = await this.usersRepository.find({ - where: { id: In(userIds) }, - select: { id: true, isHibernated: true }, - }); + const results = await this.usersRepository.find({ where: { id: In(userIds) }, select: { id: true, isHibernated: true } }); return results.map(({ id, isHibernated }) => [id, isHibernated]); }, onChanged: async userIds => { @@ -467,8 +569,8 @@ export class CacheService implements OnApplicationShutdown { for (const { id, isHibernated } of hibernations) { const users = userObjects.get(id); if (users) { - for (const u of users) { - u.isHibernated = isHibernated; + for (const user of users) { + user.isHibernated = isHibernated; } } } @@ -480,11 +582,21 @@ export class CacheService implements OnApplicationShutdown { this.userFollowingChannelsCache = this.cacheManagementService.createQuantumKVCache>('userFollowingChannels', { lifetime: 1000 * 60 * 30, // 30m - fetcher: (key) => this.channelFollowingsRepository.find({ - where: { followerId: key }, - select: ['followeeId'], - }).then(xs => new Set(xs.map(x => x.followeeId))), - // TODO bulk fetcher + fetcher: async (followerId) => { + const followings = await this.channelFollowingsRepository.find({ where: { followerId: followerId }, select: ['followeeId'] }); + return new Set(followings.map(following => following.followeeId)); + }, + // no optionalFetcher needed + bulkFetcher: async followerIds => { + const followings = await this.channelFollowingsRepository + .createQueryBuilder('following') + .select('"following"."followerId"', 'followerId') + .addSelect('array_agg("following"."followeeId")', 'followeeIds') + .where({ followerId: In(followerIds) }) + .groupBy('following.followerId') + .getRawMany<{ followerId: string, followeeIds: string[] }>(); + return followings.map(following => [following.followerId, new Set(following.followeeIds)]); + }, }); this.internalEventService.on('usersUpdated', this.onUserEvent); @@ -758,7 +870,7 @@ export class CacheService implements OnApplicationShutdown { @bindThis public async getHibernatedFollowers(followeeId: string): Promise { const followers = await this.getFollowersWithHibernation(followeeId); - return followers.filter(f => f.isFollowerHibernated); + return followers.filter(follower => follower.isFollowerHibernated); } /** @@ -767,7 +879,7 @@ export class CacheService implements OnApplicationShutdown { @bindThis public async getNonHibernatedFollowers(followeeId: string): Promise { const followers = await this.getFollowersWithHibernation(followeeId); - return followers.filter(f => !f.isFollowerHibernated); + return followers.filter(follower => !follower.isFollowerHibernated); } /** @@ -777,14 +889,14 @@ export class CacheService implements OnApplicationShutdown { @bindThis public async getFollowersWithHibernation(followeeId: string): Promise { const followers = await this.userFollowersCache.fetch(followeeId); - const hibernations = await this.hibernatedUserCache.fetchMany(followers.keys()).then(fs => fs.reduce((map, f) => { - map.set(f[0], f[1]); - return map; - }, new Map)); - return Array.from(followers.values()).map(following => ({ - ...following, - isFollowerHibernated: hibernations.get(following.followerId) ?? false, - })); + const hibernations = new Map(await this.hibernatedUserCache.fetchMany(followers.keys())); + return followers + .values() + .map(following => ({ + ...following, + isFollowerHibernated: hibernations.get(following.followerId) ?? false, + })) + .toArray(); } /** @@ -793,7 +905,7 @@ export class CacheService implements OnApplicationShutdown { @bindThis public async refreshFollowRelationsFor(userId: string): Promise { const followings = await this.userFollowingsCache.refresh(userId); - const followees = Array.from(followings.values()).map(f => f.followeeId); + const followees = followings.values().map(following => following.followeeId); await this.userFollowersCache.deleteMany(followees); } diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index 51a48aeec4..dff6560378 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -115,13 +115,21 @@ export class CustomEmojiService { this.emojisByIdCache = cacheManagementService.createQuantumKVCache('emojisById', { lifetime: 1000 * 60 * 60, // 1h - fetcher: async (id) => await this.emojisRepository.findOneBy({ id }), - bulkFetcher: async (ids) => await this.emojisRepository.findBy({ id: In(ids) }).then(es => es.map(e => [e.id, e])), + fetcher: async (id) => await this.emojisRepository.findOneByOrFail({ id }), + optionalFetcher: async (id) => await this.emojisRepository.findOneBy({ id }), + bulkFetcher: async (ids) => { + const emojis = await this.emojisRepository.findBy({ id: In(ids) }); + return emojis.map(emoji => [emoji.id, emoji]); + }, }); this.emojisByKeyCache = cacheManagementService.createQuantumKVCache('emojisByKey', { lifetime: 1000 * 60 * 60, // 1h fetcher: async (key) => { + const { host, name } = decodeEmojiKey(key); + return await this.emojisRepository.findOneByOrFail({ host: host ?? IsNull(), name }); + }, + optionalFetcher: async (key) => { const { host, name } = decodeEmojiKey(key); return await this.emojisRepository.findOneBy({ host: host ?? IsNull(), name }); }, diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 6cdb52299d..3e80ac2877 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -63,10 +63,11 @@ export class FederatedInstanceService implements OnApplicationShutdown { } return instance; }, + // optionalFetcher not needed bulkFetcher: async keys => { const hosts = keys.map(key => this.utilityService.toPuny(key)); const instances = await this.instancesRepository.findBy({ host: In(hosts) }); - return instances.map(i => [i.host, i]); + return instances.map(instance => [instance.host, instance]); }, }); diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts index f0c533d4da..2697812f73 100644 --- a/packages/backend/src/core/PushNotificationService.ts +++ b/packages/backend/src/core/PushNotificationService.ts @@ -70,7 +70,9 @@ export class PushNotificationService { ) { this.subscriptionsCache = cacheManagementService.createQuantumKVCache('userSwSubscriptions', { lifetime: 1000 * 60 * 60 * 1, // 1h - fetcher: (key) => this.swSubscriptionsRepository.findBy({ userId: key }), + fetcher: async userId => await this.swSubscriptionsRepository.findBy({ userId }), + // optionalFetcher not needed + // bulkFetcher not needed }); } diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts index 750906891a..2a2f1aeab7 100644 --- a/packages/backend/src/core/UserKeypairService.ts +++ b/packages/backend/src/core/UserKeypairService.ts @@ -32,8 +32,12 @@ export class UserKeypairService implements OnApplicationShutdown { ) { this.userKeypairCache = cacheManagementService.createQuantumKVCache('userKeypair', { lifetime: 1000 * 60 * 60, // 1h - fetcher: async userId => await this.userKeypairsRepository.findOneBy({ userId }), - bulkFetcher: async userIds => await this.userKeypairsRepository.findBy({ userId: In(userIds) }).then(ks => ks.map(k => [k.userId, k])), + fetcher: async userId => await this.userKeypairsRepository.findOneByOrFail({ userId }), + optionalFetcher: async userId => await this.userKeypairsRepository.findOneBy({ userId }), + bulkFetcher: async userIds => { + const keypairs = await this.userKeypairsRepository.findBy({ userId: In(userIds) }); + return keypairs.map(keypair => [keypair.userId, keypair]); + }, }); this.internalEventService.on('userChangeDeletedState', this.onUserDeleted); diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts index 29ea6fdc4d..01b8173c59 100644 --- a/packages/backend/src/core/UserListService.ts +++ b/packages/backend/src/core/UserListService.ts @@ -61,8 +61,12 @@ export class UserListService implements OnModuleInit { ) { this.userListsCache = cacheManagementService.createQuantumKVCache('userLists', { lifetime: 1000 * 60 * 30, // 30m - fetcher: async id => await this.userListsRepository.findOneBy({ id }), - bulkFetcher: async ids => await this.userListsRepository.findBy({ id: In(ids) }).then(ls => ls.map(l => [l.id, l])), + fetcher: async id => await this.userListsRepository.findOneByOrFail({ id }), + optionalFetcher: async id => await this.userListsRepository.findOneBy({ id }), + bulkFetcher: async ids => { + const lists = await this.userListsRepository.findBy({ id: In(ids) }); + return lists.map(list => [list.id, list]); + }, }); } diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index cf2c302af1..25cd0ce8c0 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -136,6 +136,14 @@ export class ApPersonService implements OnModuleInit { .getOneOrFail() as { id: string }; return id; }, + optionalFetcher: async (uri) => { + const res = await this.usersRepository + .createQueryBuilder('user') + .select('user.id') + .where({ uri }) + .getOne() as { id: string } | null; + return res?.id; + }, bulkFetcher: async (uris) => { const users = await this.usersRepository .createQueryBuilder('user') @@ -143,20 +151,28 @@ export class ApPersonService implements OnModuleInit { .addSelect('user.uri') .where({ uri: In(uris) }) .getMany() as { id: string, uri: string }[]; - return users.map(u => [u.uri, u.id]); + return users.map(user => [user.uri, user.id]); }, }); this.publicKeyByKeyIdCache = this.cacheManagementService.createQuantumKVCache('publicKeyByKeyId', { lifetime: 1000 * 60 * 60 * 12, // 12h - fetcher: async (keyId) => await this.userPublickeysRepository.findOneBy({ keyId }), - bulkFetcher: async (keyIds) => await this.userPublickeysRepository.findBy({ keyId: In(keyIds) }).then(ks => ks.map(k => [k.keyId, k])), + fetcher: async (keyId) => await this.userPublickeysRepository.findOneByOrFail({ keyId }), + optionalFetcher: async (keyId) => await this.userPublickeysRepository.findOneBy({ keyId }), + bulkFetcher: async (keyIds) => { + const publicKeys = await this.userPublickeysRepository.findBy({ keyId: In(keyIds) }); + return publicKeys.map(k => [k.keyId, k]); + }, }); this.publicKeyByUserIdCache = this.cacheManagementService.createQuantumKVCache('publicKeyByUserId', { lifetime: 1000 * 60 * 60 * 12, // 12h - fetcher: async (userId) => await this.userPublickeysRepository.findOneBy({ userId }), - bulkFetcher: async (userIds) => await this.userPublickeysRepository.findBy({ userId: In(userIds) }).then(ks => ks.map(k => [k.userId, k])), + fetcher: async (userId) => await this.userPublickeysRepository.findOneByOrFail({ userId }), + optionalFetcher: async (userId) => await this.userPublickeysRepository.findOneBy({ userId }), + bulkFetcher: async (userIds) => { + const publicKeys = await this.userPublickeysRepository.findBy({ userId: In(userIds) }); + return publicKeys.map(k => [k.userId, k]); + }, }); }