From 0556d403e6d303e7f46a8ab9cd52a567cfeee30b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 6 Oct 2025 11:41:57 -0400 Subject: [PATCH] move uriPersonCache to ApPersonService --- packages/backend/src/core/CacheService.ts | 33 ++------ .../src/core/RemoteUserResolveService.ts | 1 + .../activitypub/models/ApPersonService.ts | 80 ++++++++++++++----- .../DeleteAccountProcessorService.ts | 4 +- 4 files changed, 67 insertions(+), 51 deletions(-) diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts index 12fa4f501f..bf31c6c105 100644 --- a/packages/backend/src/core/CacheService.ts +++ b/packages/backend/src/core/CacheService.ts @@ -35,7 +35,6 @@ export interface FollowStats { export class CacheService implements OnApplicationShutdown { public readonly userByIdCache: ManagedQuantumKVCache; public readonly nativeTokenCache: ManagedQuantumKVCache; // Token -> UserId - public readonly uriPersonCache: ManagedQuantumKVCache; // URI -> UserId public readonly userByAcctCache: ManagedQuantumKVCache; // Acct -> UserId public readonly userProfileCache: ManagedQuantumKVCache; public readonly userMutingsCache: ManagedQuantumKVCache>; @@ -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 diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts index b65619064a..df2c307780 100644 --- a/packages/backend/src/core/RemoteUserResolveService.ts +++ b/packages/backend/src/core/RemoteUserResolveService.ts @@ -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}`); diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index e78f3c931e..62c2f3def2 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -63,9 +63,12 @@ type Field = Record<'name' | 'value', string>; @Injectable() export class ApPersonService implements OnModuleInit { + // Moved from CacheService + public readonly uriPersonCache: ManagedQuantumKVCache; + // Moved from ApDbResolverService - private publicKeyByKeyIdCache: ManagedQuantumKVCache; - private publicKeyByUserIdCache: ManagedQuantumKVCache; + private readonly publicKeyByKeyIdCache: ManagedQuantumKVCache; + private readonly publicKeyByUserIdCache: ManagedQuantumKVCache; 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('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('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 { - 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 { + 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>> { @@ -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 diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index cbc843e208..06a4b7ab7f 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -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({