move uriPersonCache to ApPersonService

This commit is contained in:
Hazelnoot 2025-10-06 11:41:57 -04:00
parent 0047396cf2
commit 0556d403e6
4 changed files with 67 additions and 51 deletions

View file

@ -35,7 +35,6 @@ export interface FollowStats {
export class CacheService implements OnApplicationShutdown {
public readonly userByIdCache: ManagedQuantumKVCache<MiUser>;
public readonly nativeTokenCache: ManagedQuantumKVCache<string>; // Token -> UserId
public readonly uriPersonCache: ManagedQuantumKVCache<string>; // URI -> UserId
public readonly userByAcctCache: ManagedQuantumKVCache<string>; // Acct -> UserId
public readonly userProfileCache: ManagedQuantumKVCache<MiUserProfile>;
public readonly userMutingsCache: ManagedQuantumKVCache<Set<string>>;
@ -119,12 +118,12 @@ export class CacheService implements OnApplicationShutdown {
this.nativeTokenCache = this.cacheManagementService.createQuantumKVCache('localUserByNativeToken', {
lifetime: 1000 * 60 * 5, // 5m
fetcher: async (token) => {
const user = await this.usersRepository
const { id } = await this.usersRepository
.createQueryBuilder('user')
.select('user.id')
.where({ token })
.getOne() as { id: string } | null;
return user?.id ?? null;
.getOneOrFail() as { id: string };
return id;
},
bulkFetcher: async (tokens) => {
const users = await this.usersRepository
@ -133,29 +132,7 @@ export class CacheService implements OnApplicationShutdown {
.addSelect('user.token')
.where({ token: In(tokens) })
.getMany() as { id: string, token: string }[];
const userMap = new Map(users.map(u => [u.token, u.id]));
return tokens.map(token => [token, userMap.get(token) ?? null]);
},
});
this.uriPersonCache = this.cacheManagementService.createQuantumKVCache('uriPerson', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: async (uri) => {
const user = await this.usersRepository
.createQueryBuilder('user')
.select('user.id')
.where({ uri })
.getOneOrFail() as { id: string };
return user.id;
},
bulkFetcher: async (uris) => {
const users = await this.usersRepository
.createQueryBuilder('user')
.select('user.id')
.addSelect('user.uri')
.where({ uri: In(uris) })
.getMany() as { id: string, uri: string }[];
return users.map(u => [u.uri, u.id]);
return users.map(u => [u.token, u.id]);
},
});
@ -636,7 +613,7 @@ export class CacheService implements OnApplicationShutdown {
@bindThis
public findOptionalUserById(userId: MiUser['id']) {
return this.userByIdCache.fetchMaybe(userId, async () => await this.usersRepository.findOneBy({ id: userId }) ?? undefined);
return this.userByIdCache.fetchMaybe(userId);
}
@bindThis

View file

@ -107,6 +107,7 @@ export class RemoteUserResolveService {
}, {
uri: self.href,
});
await this.apPersonService.uriPersonCache.delete(user.uri); // Unmap the old URI
}
this.logger.info(`Corrected URI for ${acctLower} from ${user.uri} to ${self.href}`);

View file

@ -63,9 +63,12 @@ type Field = Record<'name' | 'value', string>;
@Injectable()
export class ApPersonService implements OnModuleInit {
// Moved from CacheService
public readonly uriPersonCache: ManagedQuantumKVCache<string>;
// Moved from ApDbResolverService
private publicKeyByKeyIdCache: ManagedQuantumKVCache<MiUserPublickey>;
private publicKeyByUserIdCache: ManagedQuantumKVCache<MiUserPublickey>;
private readonly publicKeyByKeyIdCache: ManagedQuantumKVCache<MiUserPublickey>;
private readonly publicKeyByUserIdCache: ManagedQuantumKVCache<MiUserPublickey>;
private driveFileEntityService: DriveFileEntityService;
private globalEventService: GlobalEventService;
@ -122,11 +125,34 @@ export class ApPersonService implements OnModuleInit {
apLoggerService: ApLoggerService,
) {
this.logger = apLoggerService.logger;
this.uriPersonCache = this.cacheManagementService.createQuantumKVCache('uriPerson', {
lifetime: 1000 * 60 * 30, // 30m
fetcher: async (uri) => {
const { id } = await this.usersRepository
.createQueryBuilder('user')
.select('user.id')
.where({ uri })
.getOneOrFail() as { id: string };
return id;
},
bulkFetcher: async (uris) => {
const users = await this.usersRepository
.createQueryBuilder('user')
.select('user.id')
.addSelect('user.uri')
.where({ uri: In(uris) })
.getMany() as { id: string, uri: string }[];
return users.map(u => [u.uri, u.id]);
},
});
this.publicKeyByKeyIdCache = this.cacheManagementService.createQuantumKVCache<MiUserPublickey>('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])),
});
this.publicKeyByUserIdCache = this.cacheManagementService.createQuantumKVCache<MiUserPublickey>('publicKeyByUserId', {
lifetime: 1000 * 60 * 60 * 12, // 12h
fetcher: async (userId) => await this.userPublickeysRepository.findOneBy({ userId }),
@ -251,28 +277,38 @@ export class ApPersonService implements OnModuleInit {
* Misskeyに対象のPersonが登録されていればそれを返しnullを返します
*/
@bindThis
public async fetchPerson(uri: string): Promise<MiLocalUser | MiRemoteUser | null> {
const cached = this.cacheService.uriPersonCache.get(uri) as MiLocalUser | MiRemoteUser | null | undefined;
if (cached) return cached;
public async fetchPerson(uri: string, opts?: { withDeleted?: boolean, withSuspended?: boolean }): Promise<MiLocalUser | MiRemoteUser | null> {
const _opts = {
withDeleted: opts?.withDeleted ?? false,
withSuspended: opts?.withSuspended ?? true,
};
// URIがこのサーバーを指しているならデータベースからフェッチ
if (uri.startsWith(`${this.config.url}/`)) {
const id = uri.split('/').pop();
const u = await this.usersRepository.findOneBy({ id }) as MiLocalUser | null;
if (u) this.cacheService.uriPersonCache.set(uri, u);
return u;
let userId;
// Resolve URI -> User ID
const parsed = this.utilityService.parseUri(uri);
if (parsed.local) {
userId = parsed.type === 'users' ? parsed.id : null;
} else {
userId = await this.uriPersonCache.fetch(uri).catch(() => null);
}
//#region このサーバーに既に登録されていたらそれを返す
const exist = await this.usersRepository.findOneBy({ uri }) as MiLocalUser | MiRemoteUser | null;
if (exist) {
this.cacheService.uriPersonCache.set(uri, exist);
return exist;
// No match
if (!userId) {
return null;
}
//#endregion
return null;
const user = await this.cacheService.findUserById(userId)
.catch(() => null) as MiLocalUser | MiRemoteUser | null;
if (user?.isDeleted && !_opts.withDeleted) {
return null;
}
if (user?.isSuspended && !_opts.withSuspended) {
return null;
}
return user;
}
private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any, bgimg: any): Promise<Partial<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'backgroundId' | 'avatarUrl' | 'bannerUrl' | 'backgroundUrl' | 'avatarBlurhash' | 'bannerBlurhash' | 'backgroundBlurhash'>>> {
@ -508,7 +544,7 @@ export class ApPersonService implements OnModuleInit {
if (user == null) throw new Error(`failed to create user - user is null: ${uri}`);
// Register to the cache
await this.cacheService.uriPersonCache.set(user.uri, user.id);
await this.uriPersonCache.set(user.uri, user.id);
// Register public key to the cache.
if (publicKey) {
@ -541,7 +577,7 @@ export class ApPersonService implements OnModuleInit {
user = { ...user, ...updates };
// Register to the cache
await this.cacheService.uriPersonCache.set(user.uri, user.id);
await this.uriPersonCache.set(user.uri, user.id);
} catch (err) {
// Permanent error implies hidden or inaccessible, which is a normal thing.
if (isRetryableError(err)) {
@ -809,7 +845,7 @@ export class ApPersonService implements OnModuleInit {
}
//#region このサーバーに既に登録されていたらそれを返す
const exist = await this.fetchPerson(uri);
const exist = await this.fetchPerson(uri, { withDeleted: true });
if (exist) return exist;
//#endregion

View file

@ -20,6 +20,7 @@ import { ReactionService } from '@/core/ReactionService.js';
import { QueueService } from '@/core/QueueService.js';
import { CacheService } from '@/core/CacheService.js';
import { QueueLoggerService } from '@/queue/QueueLoggerService.js';
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
import * as Acct from '@/misc/acct.js';
import type * as Bull from 'bullmq';
import type { DbUserDeleteJobData } from '../types.js';
@ -97,6 +98,7 @@ export class DeleteAccountProcessorService {
private reactionService: ReactionService,
private readonly apLogService: ApLogService,
private readonly cacheService: CacheService,
private readonly apPersonService: ApPersonService,
) {
this.logger = this.queueLoggerService.logger.createSubLogger('delete-account');
}
@ -160,7 +162,7 @@ export class DeleteAccountProcessorService {
await this.cacheService.nativeTokenCache.delete(user.token);
}
if (user.uri) {
await this.cacheService.uriPersonCache.delete(user.uri);
await this.apPersonService.uriPersonCache.delete(user.uri);
}
await this.followingsRepository.delete({