normalize userFollowingsCache / userFollowersCache and add hibernatedUserCache to reduce the number of cache-clears and allow use of caching in many more places

This commit is contained in:
Hazelnoot 2025-06-08 19:52:59 -04:00
parent 372714c9b6
commit fa68751a19
28 changed files with 816 additions and 581 deletions

View file

@ -147,12 +147,7 @@ export class UserFollowingService implements OnModuleInit {
if (blocked) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked');
}
if (await this.followingsRepository.exists({
where: {
followerId: follower.id,
followeeId: followee.id,
},
})) {
if (await this.cacheService.isFollowing(follower, followee)) {
// すでにフォロー関係が存在している場合
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
// リモート → ローカル: acceptを送り返しておしまい
@ -180,24 +175,14 @@ export class UserFollowingService implements OnModuleInit {
let autoAccept = false;
// 鍵アカウントであっても、既にフォローされていた場合はスルー
const isFollowing = await this.followingsRepository.exists({
where: {
followerId: follower.id,
followeeId: followee.id,
},
});
const isFollowing = await this.cacheService.isFollowing(follower, followee);
if (isFollowing) {
autoAccept = true;
}
// フォローしているユーザーは自動承認オプション
if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
const isFollowed = await this.followingsRepository.exists({
where: {
followerId: followee.id,
followeeId: follower.id,
},
});
const isFollowed = await this.cacheService.isFollowing(followee, follower); // intentionally reversed parameters
if (isFollowed) autoAccept = true;
}
@ -206,12 +191,7 @@ export class UserFollowingService implements OnModuleInit {
if (followee.isLocked && !autoAccept) {
autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs(
follower,
(oldSrc, newSrc) => this.followingsRepository.exists({
where: {
followeeId: followee.id,
followerId: newSrc.id,
},
}),
(oldSrc, newSrc) => this.cacheService.isFollowing(newSrc, followee),
true,
));
}
@ -366,32 +346,29 @@ export class UserFollowingService implements OnModuleInit {
},
silent = false,
): Promise<void> {
const following = await this.followingsRepository.findOne({
relations: {
follower: true,
followee: true,
},
where: {
followerId: follower.id,
followeeId: followee.id,
},
});
const [
followerUser,
followeeUser,
following,
] = await Promise.all([
this.cacheService.findUserById(follower.id),
this.cacheService.findUserById(followee.id),
this.cacheService.userFollowingsCache.fetch(follower.id).then(fs => fs.get(followee.id)),
]);
if (following === null || !following.follower || !following.followee) {
if (following == null) {
this.logger.warn('フォロー解除がリクエストされましたがフォローしていませんでした');
return;
}
await this.followingsRepository.delete(following.id);
await this.internalEventService.emit('unfollow', { followerId: follower.id, followeeId: followee.id });
// Handled by CacheService
// this.cacheService.userFollowingsCache.refresh(follower.id);
this.decrementFollowing(following.follower, following.followee);
this.decrementFollowing(followerUser, followeeUser);
if (!silent && this.userEntityService.isLocalUser(follower)) {
// Publish unfollow event
this.userEntityService.pack(followee.id, follower, {
this.userEntityService.pack(followeeUser, follower, {
schema: 'UserDetailedNotMe',
}).then(async packed => {
this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
@ -416,8 +393,6 @@ export class UserFollowingService implements OnModuleInit {
follower: MiUser,
followee: MiUser,
): Promise<void> {
await this.internalEventService.emit('unfollow', { followerId: follower.id, followeeId: followee.id });
// Neither followee nor follower has moved.
if (!follower.movedToUri && !followee.movedToUri) {
//#region Decrement following / followers counts
@ -691,22 +666,22 @@ export class UserFollowingService implements OnModuleInit {
*/
@bindThis
private async removeFollow(followee: Both, follower: Both): Promise<void> {
const following = await this.followingsRepository.findOne({
relations: {
followee: true,
follower: true,
},
where: {
followeeId: followee.id,
followerId: follower.id,
},
});
const [
followerUser,
followeeUser,
following,
] = await Promise.all([
this.cacheService.findUserById(follower.id),
this.cacheService.findUserById(followee.id),
this.cacheService.userFollowingsCache.fetch(follower.id).then(fs => fs.get(followee.id)),
]);
if (!following || !following.followee || !following.follower) return;
if (!following) return;
await this.followingsRepository.delete(following.id);
await this.internalEventService.emit('unfollow', { followerId: follower.id, followeeId: followee.id });
this.decrementFollowing(following.follower, following.followee);
this.decrementFollowing(followerUser, followeeUser);
}
/**
@ -737,36 +712,26 @@ export class UserFollowingService implements OnModuleInit {
}
@bindThis
public getFollowees(userId: MiUser['id']) {
return this.followingsRepository.createQueryBuilder('following')
.select('following.followeeId')
.where('following.followerId = :followerId', { followerId: userId })
.getMany();
public async getFollowees(userId: MiUser['id']) {
const followings = await this.cacheService.userFollowingsCache.fetch(userId);
return Array.from(followings.values());
}
@bindThis
public isFollowing(followerId: MiUser['id'], followeeId: MiUser['id']) {
return this.followingsRepository.exists({
where: {
followerId,
followeeId,
},
});
public async isFollowing(followerId: MiUser['id'], followeeId: MiUser['id']) {
return this.cacheService.isFollowing(followerId, followeeId);
}
@bindThis
public async isMutual(aUserId: MiUser['id'], bUserId: MiUser['id']) {
const count = await this.followingsRepository.createQueryBuilder('following')
.where(new Brackets(qb => {
qb.where('following.followerId = :aUserId', { aUserId })
.andWhere('following.followeeId = :bUserId', { bUserId });
}))
.orWhere(new Brackets(qb => {
qb.where('following.followerId = :bUserId', { bUserId })
.andWhere('following.followeeId = :aUserId', { aUserId });
}))
.getCount();
const [
isFollowing,
isFollowed,
] = await Promise.all([
this.isFollowing(aUserId, bUserId),
this.isFollowing(bUserId, aUserId),
]);
return count === 2;
return isFollowing && isFollowed;
}
}