From ecaf152b4a6eb702375debaef0dddc2cca798116 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 4 Apr 2023 17:32:09 +0900
Subject: [PATCH] enhance(backend): improve cache

---
 .../{UserCacheService.ts => CacheService.ts}  | 17 +++-
 packages/backend/src/core/CoreModule.ts       | 12 +--
 .../backend/src/core/InstanceActorService.ts  | 12 +--
 .../backend/src/core/NoteCreateService.ts     |  6 +-
 .../backend/src/core/NotificationService.ts   | 18 ++--
 packages/backend/src/core/RelayService.ts     |  8 +-
 packages/backend/src/core/RoleService.ts      | 30 +++---
 .../core/activitypub/ApDbResolverService.ts   | 10 +-
 .../activitypub/models/ApPersonService.ts     | 14 +--
 .../entities/NotificationEntityService.ts     |  1 +
 .../src/core/entities/UserEntityService.ts    |  1 -
 packages/backend/src/misc/cache.ts            | 97 +++++++++++++++++--
 .../processors/DeliverProcessorService.ts     | 10 +-
 .../src/server/NodeinfoServerService.ts       |  8 +-
 .../src/server/api/AuthenticateService.ts     |  8 +-
 .../api/endpoints/i/regenerate-token.ts       |  2 +-
 .../src/server/api/endpoints/i/update.ts      |  8 +-
 .../src/server/api/endpoints/mute/create.ts   |  3 +
 .../api/endpoints/renote-mute/create.ts       |  2 -
 .../backend/src/server/api/stream/types.ts    |  2 +-
 packages/backend/test/unit/RoleService.ts     |  4 +-
 21 files changed, 184 insertions(+), 89 deletions(-)
 rename packages/backend/src/core/{UserCacheService.ts => CacheService.ts} (79%)

diff --git a/packages/backend/src/core/UserCacheService.ts b/packages/backend/src/core/CacheService.ts
similarity index 79%
rename from packages/backend/src/core/UserCacheService.ts
rename to packages/backend/src/core/CacheService.ts
index e452caf5d1..887baeb2c2 100644
--- a/packages/backend/src/core/UserCacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -1,7 +1,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import Redis from 'ioredis';
-import type { UsersRepository } from '@/models/index.js';
-import { MemoryKVCache } from '@/misc/cache.js';
+import type { UserProfile, UsersRepository } from '@/models/index.js';
+import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
 import type { LocalUser, User } from '@/models/entities/User.js';
 import { DI } from '@/di-symbols.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -10,13 +10,18 @@ import { StreamMessages } from '@/server/api/stream/types.js';
 import type { OnApplicationShutdown } from '@nestjs/common';
 
 @Injectable()
-export class UserCacheService implements OnApplicationShutdown {
+export class CacheService implements OnApplicationShutdown {
 	public userByIdCache: MemoryKVCache<User>;
 	public localUserByNativeTokenCache: MemoryKVCache<LocalUser | null>;
 	public localUserByIdCache: MemoryKVCache<LocalUser>;
 	public uriPersonCache: MemoryKVCache<User | null>;
+	public userProfileCache: RedisKVCache<UserProfile>;
+	public userMutingsCache: RedisKVCache<string[]>;
 
 	constructor(
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+
 		@Inject(DI.redisSubscriber)
 		private redisSubscriber: Redis.Redis,
 
@@ -31,6 +36,8 @@ export class UserCacheService implements OnApplicationShutdown {
 		this.localUserByNativeTokenCache = new MemoryKVCache<LocalUser | null>(Infinity);
 		this.localUserByIdCache = new MemoryKVCache<LocalUser>(Infinity);
 		this.uriPersonCache = new MemoryKVCache<User | null>(Infinity);
+		this.userProfileCache = new RedisKVCache<UserProfile>(this.redisClient, 'userProfile', 1000 * 60 * 60 * 24, 1000 * 60);
+		this.userMutingsCache = new RedisKVCache<string[]>(this.redisClient, 'userMutings', 1000 * 60 * 60 * 24, 1000 * 60);
 
 		this.redisSubscriber.on('message', this.onMessage);
 	}
@@ -52,7 +59,7 @@ export class UserCacheService implements OnApplicationShutdown {
 						}
 					}
 					if (this.userEntityService.isLocalUser(user)) {
-						this.localUserByNativeTokenCache.set(user.token, user);
+						this.localUserByNativeTokenCache.set(user.token!, user);
 						this.localUserByIdCache.set(user.id, user);
 					}
 					break;
@@ -77,7 +84,7 @@ export class UserCacheService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public findById(userId: User['id']) {
+	public findUserById(userId: User['id']) {
 		return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId }));
 	}
 
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index d67e80fc1d..5c867e6cfc 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -38,7 +38,7 @@ import { S3Service } from './S3Service.js';
 import { SignupService } from './SignupService.js';
 import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
 import { UserBlockingService } from './UserBlockingService.js';
-import { UserCacheService } from './UserCacheService.js';
+import { CacheService } from './CacheService.js';
 import { UserFollowingService } from './UserFollowingService.js';
 import { UserKeypairStoreService } from './UserKeypairStoreService.js';
 import { UserListService } from './UserListService.js';
@@ -159,7 +159,7 @@ const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
 const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
 const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
 const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
-const $UserCacheService: Provider = { provide: 'UserCacheService', useExisting: UserCacheService };
+const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
 const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
 const $UserKeypairStoreService: Provider = { provide: 'UserKeypairStoreService', useExisting: UserKeypairStoreService };
 const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
@@ -282,7 +282,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		SignupService,
 		TwoFactorAuthenticationService,
 		UserBlockingService,
-		UserCacheService,
+		CacheService,
 		UserFollowingService,
 		UserKeypairStoreService,
 		UserListService,
@@ -399,7 +399,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$SignupService,
 		$TwoFactorAuthenticationService,
 		$UserBlockingService,
-		$UserCacheService,
+		$CacheService,
 		$UserFollowingService,
 		$UserKeypairStoreService,
 		$UserListService,
@@ -517,7 +517,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		SignupService,
 		TwoFactorAuthenticationService,
 		UserBlockingService,
-		UserCacheService,
+		CacheService,
 		UserFollowingService,
 		UserKeypairStoreService,
 		UserListService,
@@ -633,7 +633,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$SignupService,
 		$TwoFactorAuthenticationService,
 		$UserBlockingService,
-		$UserCacheService,
+		$CacheService,
 		$UserFollowingService,
 		$UserKeypairStoreService,
 		$UserListService,
diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts
index 049e27dec8..898fb4ce85 100644
--- a/packages/backend/src/core/InstanceActorService.ts
+++ b/packages/backend/src/core/InstanceActorService.ts
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { IsNull } from 'typeorm';
 import type { LocalUser } from '@/models/entities/User.js';
 import type { UsersRepository } from '@/models/index.js';
-import { MemoryKVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
 import { DI } from '@/di-symbols.js';
 import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
 import { bindThis } from '@/decorators.js';
@@ -11,7 +11,7 @@ const ACTOR_USERNAME = 'instance.actor' as const;
 
 @Injectable()
 export class InstanceActorService {
-	private cache: MemoryKVCache<LocalUser>;
+	private cache: MemoryCache<LocalUser>;
 
 	constructor(
 		@Inject(DI.usersRepository)
@@ -19,12 +19,12 @@ export class InstanceActorService {
 
 		private createSystemUserService: CreateSystemUserService,
 	) {
-		this.cache = new MemoryKVCache<LocalUser>(Infinity);
+		this.cache = new MemoryCache<LocalUser>(Infinity);
 	}
 
 	@bindThis
 	public async getInstanceActor(): Promise<LocalUser> {
-		const cached = this.cache.get(null);
+		const cached = this.cache.get();
 		if (cached) return cached;
 	
 		const user = await this.usersRepository.findOneBy({
@@ -33,11 +33,11 @@ export class InstanceActorService {
 		}) as LocalUser | undefined;
 	
 		if (user) {
-			this.cache.set(null, user);
+			this.cache.set(user);
 			return user;
 		} else {
 			const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser;
-			this.cache.set(null, created);
+			this.cache.set(created);
 			return created;
 		}
 	}
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 552f241044..83290b310e 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -20,7 +20,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js
 import { checkWordMute } from '@/misc/check-word-mute.js';
 import type { Channel } from '@/models/entities/Channel.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
-import { MemoryKVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
 import type { UserProfile } from '@/models/entities/UserProfile.js';
 import { RelayService } from '@/core/RelayService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
@@ -47,7 +47,7 @@ import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { RoleService } from '@/core/RoleService.js';
 import { MetaService } from '@/core/MetaService.js';
 
-const mutedWordsCache = new MemoryKVCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
+const mutedWordsCache = new MemoryCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -473,7 +473,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 		this.incNotesCountOfUser(user);
 
 		// Word mute
-		mutedWordsCache.fetch(null, () => this.userProfilesRepository.find({
+		mutedWordsCache.fetch(() => this.userProfilesRepository.find({
 			where: {
 				enableWordMute: true,
 			},
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index 2a4dbba6a4..9c179f9318 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -3,7 +3,7 @@ import Redis from 'ioredis';
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
 import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { MutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
+import type { MutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
 import type { User } from '@/models/entities/User.js';
 import type { Notification } from '@/models/entities/Notification.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -12,6 +12,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { PushNotificationService } from '@/core/PushNotificationService.js';
 import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
 import { IdService } from '@/core/IdService.js';
+import { CacheService } from '@/core/CacheService.js';
 
 @Injectable()
 export class NotificationService implements OnApplicationShutdown {
@@ -35,6 +36,7 @@ export class NotificationService implements OnApplicationShutdown {
 		private idService: IdService,
 		private globalEventService: GlobalEventService,
 		private pushNotificationService: PushNotificationService,
+		private cacheService: CacheService,
 	) {
 	}
 
@@ -49,7 +51,6 @@ export class NotificationService implements OnApplicationShutdown {
 			'+',
 			'-',
 			'COUNT', 1);
-		console.log('latestNotificationIdsRes', latestNotificationIdsRes);
 		const latestNotificationId = latestNotificationIdsRes[0]?.[0];
 
 		if (latestNotificationId == null) return;
@@ -72,9 +73,8 @@ export class NotificationService implements OnApplicationShutdown {
 		type: Notification['type'],
 		data: Partial<Notification>,
 	): Promise<Notification | null> {
-		// TODO: Cache
-		const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId });
-		const isMuted = profile?.mutingNotificationTypes.includes(type);
+		const profile = await this.cacheService.userProfileCache.fetch(notifieeId, () => this.userProfilesRepository.findOneByOrFail({ userId: notifieeId }));
+		const isMuted = profile.mutingNotificationTypes.includes(type);
 		if (isMuted) return null;
 
 		if (data.notifierId) {
@@ -82,12 +82,8 @@ export class NotificationService implements OnApplicationShutdown {
 				return null;
 			}
 
-			// TODO: cache
-			const mutings = await this.mutingsRepository.findOneBy({
-				muterId: notifieeId,
-				muteeId: data.notifierId,
-			});
-			if (mutings) {
+			const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId, () => this.mutingsRepository.findBy({ muterId: notifieeId }).then(xs => xs.map(x => x.muteeId)));
+			if (mutings.includes(data.notifierId)) {
 				return null;
 			}
 		}
diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts
index be5a4d4b02..4df7fb3bff 100644
--- a/packages/backend/src/core/RelayService.ts
+++ b/packages/backend/src/core/RelayService.ts
@@ -3,7 +3,7 @@ import { IsNull } from 'typeorm';
 import type { LocalUser, User } from '@/models/entities/User.js';
 import type { RelaysRepository, UsersRepository } from '@/models/index.js';
 import { IdService } from '@/core/IdService.js';
-import { MemoryKVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
 import type { Relay } from '@/models/entities/Relay.js';
 import { QueueService } from '@/core/QueueService.js';
 import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
@@ -16,7 +16,7 @@ const ACTOR_USERNAME = 'relay.actor' as const;
 
 @Injectable()
 export class RelayService {
-	private relaysCache: MemoryKVCache<Relay[]>;
+	private relaysCache: MemoryCache<Relay[]>;
 
 	constructor(
 		@Inject(DI.usersRepository)
@@ -30,7 +30,7 @@ export class RelayService {
 		private createSystemUserService: CreateSystemUserService,
 		private apRendererService: ApRendererService,
 	) {
-		this.relaysCache = new MemoryKVCache<Relay[]>(1000 * 60 * 10);
+		this.relaysCache = new MemoryCache<Relay[]>(1000 * 60 * 10);
 	}
 
 	@bindThis
@@ -109,7 +109,7 @@ export class RelayService {
 	public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise<void> {
 		if (activity == null) return;
 	
-		const relays = await this.relaysCache.fetch(null, () => this.relaysRepository.findBy({
+		const relays = await this.relaysCache.fetch(() => this.relaysRepository.findBy({
 			status: 'accepted',
 		}));
 		if (relays.length === 0) return;
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 678bcfc337..52e6292a1e 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -2,12 +2,12 @@ import { Inject, Injectable } from '@nestjs/common';
 import Redis from 'ioredis';
 import { In } from 'typeorm';
 import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
-import { MemoryKVCache } from '@/misc/cache.js';
+import { MemoryKVCache, MemoryCache } from '@/misc/cache.js';
 import type { User } from '@/models/entities/User.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
 import { MetaService } from '@/core/MetaService.js';
-import { UserCacheService } from '@/core/UserCacheService.js';
+import { CacheService } from '@/core/CacheService.js';
 import type { RoleCondFormulaValue } from '@/models/entities/Role.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { StreamMessages } from '@/server/api/stream/types.js';
@@ -57,7 +57,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
 
 @Injectable()
 export class RoleService implements OnApplicationShutdown {
-	private rolesCache: MemoryKVCache<Role[]>;
+	private rolesCache: MemoryCache<Role[]>;
 	private roleAssignmentByUserIdCache: MemoryKVCache<RoleAssignment[]>;
 
 	public static AlreadyAssignedError = class extends Error {};
@@ -77,14 +77,14 @@ export class RoleService implements OnApplicationShutdown {
 		private roleAssignmentsRepository: RoleAssignmentsRepository,
 
 		private metaService: MetaService,
-		private userCacheService: UserCacheService,
+		private cacheService: CacheService,
 		private userEntityService: UserEntityService,
 		private globalEventService: GlobalEventService,
 		private idService: IdService,
 	) {
 		//this.onMessage = this.onMessage.bind(this);
 
-		this.rolesCache = new MemoryKVCache<Role[]>(Infinity);
+		this.rolesCache = new MemoryCache<Role[]>(Infinity);
 		this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(Infinity);
 
 		this.redisSubscriber.on('message', this.onMessage);
@@ -98,7 +98,7 @@ export class RoleService implements OnApplicationShutdown {
 			const { type, body } = obj.message as StreamMessages['internal']['payload'];
 			switch (type) {
 				case 'roleCreated': {
-					const cached = this.rolesCache.get(null);
+					const cached = this.rolesCache.get();
 					if (cached) {
 						cached.push({
 							...body,
@@ -110,7 +110,7 @@ export class RoleService implements OnApplicationShutdown {
 					break;
 				}
 				case 'roleUpdated': {
-					const cached = this.rolesCache.get(null);
+					const cached = this.rolesCache.get();
 					if (cached) {
 						const i = cached.findIndex(x => x.id === body.id);
 						if (i > -1) {
@@ -125,9 +125,9 @@ export class RoleService implements OnApplicationShutdown {
 					break;
 				}
 				case 'roleDeleted': {
-					const cached = this.rolesCache.get(null);
+					const cached = this.rolesCache.get();
 					if (cached) {
-						this.rolesCache.set(null, cached.filter(x => x.id !== body.id));
+						this.rolesCache.set(cached.filter(x => x.id !== body.id));
 					}
 					break;
 				}
@@ -214,9 +214,9 @@ export class RoleService implements OnApplicationShutdown {
 		// 期限切れのロールを除外
 		assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
 		const assignedRoleIds = assigns.map(x => x.roleId);
-		const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+		const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
 		const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id));
-		const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null;
+		const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
 		const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
 		return [...assignedRoles, ...matchedCondRoles];
 	}
@@ -231,11 +231,11 @@ export class RoleService implements OnApplicationShutdown {
 		// 期限切れのロールを除外
 		assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
 		const assignedRoleIds = assigns.map(x => x.roleId);
-		const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+		const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
 		const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id));
 		const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional'));
 		if (badgeCondRoles.length > 0) {
-			const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null;
+			const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
 			const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, r.condFormula));
 			return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
 		} else {
@@ -301,7 +301,7 @@ export class RoleService implements OnApplicationShutdown {
 
 	@bindThis
 	public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
-		const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+		const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
 		const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator);
 		const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
 			roleId: In(moderatorRoles.map(r => r.id)),
@@ -321,7 +321,7 @@ export class RoleService implements OnApplicationShutdown {
 
 	@bindThis
 	public async getAdministratorIds(): Promise<User['id'][]> {
-		const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
+		const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
 		const administratorRoles = roles.filter(r => r.isAdministrator);
 		const assigns = administratorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
 			roleId: In(administratorRoles.map(r => r.id)),
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index dc0a865bbe..4b032be89a 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -5,7 +5,7 @@ import type { NotesRepository, UserPublickeysRepository, UsersRepository } from
 import type { Config } from '@/config.js';
 import { MemoryKVCache } from '@/misc/cache.js';
 import type { UserPublickey } from '@/models/entities/UserPublickey.js';
-import { UserCacheService } from '@/core/UserCacheService.js';
+import { CacheService } from '@/core/CacheService.js';
 import type { Note } from '@/models/entities/Note.js';
 import { bindThis } from '@/decorators.js';
 import { RemoteUser, User } from '@/models/entities/User.js';
@@ -47,7 +47,7 @@ export class ApDbResolverService {
 		@Inject(DI.userPublickeysRepository)
 		private userPublickeysRepository: UserPublickeysRepository,
 
-		private userCacheService: UserCacheService,
+		private cacheService: CacheService,
 		private apPersonService: ApPersonService,
 	) {
 		this.publicKeyCache = new MemoryKVCache<UserPublickey | null>(Infinity);
@@ -107,11 +107,11 @@ export class ApDbResolverService {
 		if (parsed.local) {
 			if (parsed.type !== 'users') return null;
 
-			return await this.userCacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
+			return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
 				id: parsed.id,
 			}).then(x => x ?? undefined)) ?? null;
 		} else {
-			return await this.userCacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
+			return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
 				uri: parsed.uri,
 			}));
 		}
@@ -138,7 +138,7 @@ export class ApDbResolverService {
 		if (key == null) return null;
 
 		return {
-			user: await this.userCacheService.findById(key.userId) as RemoteUser,
+			user: await this.cacheService.findUserById(key.userId) as RemoteUser,
 			key,
 		};
 	}
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 41f7eafa41..67e907c271 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -8,7 +8,7 @@ import type { Config } from '@/config.js';
 import type { RemoteUser } from '@/models/entities/User.js';
 import { User } from '@/models/entities/User.js';
 import { truncate } from '@/misc/truncate.js';
-import type { UserCacheService } from '@/core/UserCacheService.js';
+import type { CacheService } from '@/core/CacheService.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
 import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
 import type Logger from '@/logger.js';
@@ -54,7 +54,7 @@ export class ApPersonService implements OnModuleInit {
 	private metaService: MetaService;
 	private federatedInstanceService: FederatedInstanceService;
 	private fetchInstanceMetadataService: FetchInstanceMetadataService;
-	private userCacheService: UserCacheService;
+	private cacheService: CacheService;
 	private apResolverService: ApResolverService;
 	private apNoteService: ApNoteService;
 	private apImageService: ApImageService;
@@ -97,7 +97,7 @@ export class ApPersonService implements OnModuleInit {
 		//private metaService: MetaService,
 		//private federatedInstanceService: FederatedInstanceService,
 		//private fetchInstanceMetadataService: FetchInstanceMetadataService,
-		//private userCacheService: UserCacheService,
+		//private cacheService: CacheService,
 		//private apResolverService: ApResolverService,
 		//private apNoteService: ApNoteService,
 		//private apImageService: ApImageService,
@@ -118,7 +118,7 @@ export class ApPersonService implements OnModuleInit {
 		this.metaService = this.moduleRef.get('MetaService');
 		this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
 		this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
-		this.userCacheService = this.moduleRef.get('UserCacheService');
+		this.cacheService = this.moduleRef.get('CacheService');
 		this.apResolverService = this.moduleRef.get('ApResolverService');
 		this.apNoteService = this.moduleRef.get('ApNoteService');
 		this.apImageService = this.moduleRef.get('ApImageService');
@@ -207,14 +207,14 @@ export class ApPersonService implements OnModuleInit {
 	public async fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
 		if (typeof uri !== 'string') throw new Error('uri is not string');
 
-		const cached = this.userCacheService.uriPersonCache.get(uri);
+		const cached = this.cacheService.uriPersonCache.get(uri);
 		if (cached) return cached;
 
 		// URIがこのサーバーを指しているならデータベースからフェッチ
 		if (uri.startsWith(this.config.url + '/')) {
 			const id = uri.split('/').pop();
 			const u = await this.usersRepository.findOneBy({ id });
-			if (u) this.userCacheService.uriPersonCache.set(uri, u);
+			if (u) this.cacheService.uriPersonCache.set(uri, u);
 			return u;
 		}
 
@@ -222,7 +222,7 @@ export class ApPersonService implements OnModuleInit {
 		const exist = await this.usersRepository.findOneBy({ uri });
 
 		if (exist) {
-			this.userCacheService.uriPersonCache.set(uri, exist);
+			this.cacheService.uriPersonCache.set(uri, exist);
 			return exist;
 		}
 		//#endregion
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 7cffb8d568..6b9a9d3320 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -54,6 +54,7 @@ export class NotificationEntityService implements OnModuleInit {
 	public async pack(
 		src: Notification,
 		meId: User['id'],
+		// eslint-disable-next-line @typescript-eslint/ban-types
 		options: {
 			
 		},
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 71aa2ee6de..e8474c7e0e 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -255,7 +255,6 @@ export class UserEntityService implements OnModuleInit {
 			'+',
 			'-',
 			'COUNT', 1);
-		console.log('latestNotificationIdsRes', latestNotificationIdsRes);
 		const latestNotificationId = latestNotificationIdsRes[0]?.[0];
 
 		return latestNotificationId != null && (latestReadNotificationId == null || latestReadNotificationId < latestNotificationId);
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index a805d18421..870dfd237c 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -1,9 +1,94 @@
+import Redis from 'ioredis';
 import { bindThis } from '@/decorators.js';
 
+// redis通すとDateのインスタンスはstringに変換されるので
+type Serialized<T> = {
+	[K in keyof T]:
+		T[K] extends Date
+			? string
+			: T[K] extends (Date | null)
+				? (string | null)
+				: T[K] extends Record<string, any>
+					? Serialized<T[K]>
+					: T[K];
+};
+
+export class RedisKVCache<T> {
+	private redisClient: Redis.Redis;
+	private name: string;
+	private lifetime: number;
+	private memoryCache: MemoryKVCache<T>;
+
+	constructor(redisClient: RedisKVCache<never>['redisClient'], name: RedisKVCache<never>['name'], lifetime: RedisKVCache<never>['lifetime'], memoryCacheLifetime: number) {
+		this.redisClient = redisClient;
+		this.name = name;
+		this.lifetime = lifetime;
+		this.memoryCache = new MemoryKVCache(memoryCacheLifetime);
+	}
+
+	@bindThis
+	public async set(key: string, value: T): Promise<void> {
+		this.memoryCache.set(key, value);
+		if (this.lifetime === Infinity) {
+			await this.redisClient.set(
+				`kvcache:${this.name}:${key}`,
+				JSON.stringify(value),
+			);
+		} else {
+			await this.redisClient.set(
+				`kvcache:${this.name}:${key}`,
+				JSON.stringify(value),
+				'ex', Math.round(this.lifetime / 1000),
+			);
+		}
+	}
+
+	@bindThis
+	public async get(key: string): Promise<Serialized<T> | T | undefined> {
+		const memoryCached = this.memoryCache.get(key);
+		if (memoryCached !== undefined) return memoryCached;
+
+		const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`);
+		if (cached == null) return undefined;
+		return JSON.parse(cached);
+	}
+
+	@bindThis
+	public async delete(key: string): Promise<void> {
+		this.memoryCache.delete(key);
+		await this.redisClient.del(`kvcache:${this.name}:${key}`);
+	}
+
+	/**
+ * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
+ * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
+ */
+	@bindThis
+	public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: Serialized<T> | T) => boolean): Promise<Serialized<T> | T> {
+		const cachedValue = await this.get(key);
+		if (cachedValue !== undefined) {
+			if (validator) {
+				if (validator(cachedValue)) {
+					// Cache HIT
+					return cachedValue;
+				}
+			} else {
+				// Cache HIT
+				return cachedValue;
+			}
+		}
+
+		// Cache MISS
+		const value = await fetcher();
+		this.set(key, value);
+		return value;
+	}
+}
+
 // TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
 
 export class MemoryKVCache<T> {
-	public cache: Map<string | null, { date: number; value: T; }>;
+	public cache: Map<string, { date: number; value: T; }>;
 	private lifetime: number;
 
 	constructor(lifetime: MemoryKVCache<never>['lifetime']) {
@@ -12,7 +97,7 @@ export class MemoryKVCache<T> {
 	}
 
 	@bindThis
-	public set(key: string | null, value: T): void {
+	public set(key: string, value: T): void {
 		this.cache.set(key, {
 			date: Date.now(),
 			value,
@@ -20,7 +105,7 @@ export class MemoryKVCache<T> {
 	}
 
 	@bindThis
-	public get(key: string | null): T | undefined {
+	public get(key: string): T | undefined {
 		const cached = this.cache.get(key);
 		if (cached == null) return undefined;
 		if ((Date.now() - cached.date) > this.lifetime) {
@@ -31,7 +116,7 @@ export class MemoryKVCache<T> {
 	}
 
 	@bindThis
-	public delete(key: string | null) {
+	public delete(key: string) {
 		this.cache.delete(key);
 	}
 
@@ -40,7 +125,7 @@ export class MemoryKVCache<T> {
 	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
 	 */
 	@bindThis
-	public async fetch(key: string | null, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
+	public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
 		const cachedValue = this.get(key);
 		if (cachedValue !== undefined) {
 			if (validator) {
@@ -65,7 +150,7 @@ export class MemoryKVCache<T> {
 	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
 	 */
 	@bindThis
-	public async fetchMaybe(key: string | null, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
+	public async fetchMaybe(key: string, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
 		const cachedValue = this.get(key);
 		if (cachedValue !== undefined) {
 			if (validator) {
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index 71865c778a..a9af22ad09 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -7,7 +7,7 @@ import { MetaService } from '@/core/MetaService.js';
 import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
-import { MemoryKVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
 import type { Instance } from '@/models/entities/Instance.js';
 import InstanceChart from '@/core/chart/charts/instance.js';
 import ApRequestChart from '@/core/chart/charts/ap-request.js';
@@ -22,7 +22,7 @@ import type { DeliverJobData } from '../types.js';
 @Injectable()
 export class DeliverProcessorService {
 	private logger: Logger;
-	private suspendedHostsCache: MemoryKVCache<Instance[]>;
+	private suspendedHostsCache: MemoryCache<Instance[]>;
 	private latest: string | null;
 
 	constructor(
@@ -46,7 +46,7 @@ export class DeliverProcessorService {
 		private queueLoggerService: QueueLoggerService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
-		this.suspendedHostsCache = new MemoryKVCache<Instance[]>(1000 * 60 * 60);
+		this.suspendedHostsCache = new MemoryCache<Instance[]>(1000 * 60 * 60);
 	}
 
 	@bindThis
@@ -60,14 +60,14 @@ export class DeliverProcessorService {
 		}
 
 		// isSuspendedなら中断
-		let suspendedHosts = this.suspendedHostsCache.get(null);
+		let suspendedHosts = this.suspendedHostsCache.get();
 		if (suspendedHosts == null) {
 			suspendedHosts = await this.instancesRepository.find({
 				where: {
 					isSuspended: true,
 				},
 			});
-			this.suspendedHostsCache.set(null, suspendedHosts);
+			this.suspendedHostsCache.set(suspendedHosts);
 		}
 		if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) {
 			return 'skip (suspended)';
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index 3387bd53aa..66c1faaac2 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -4,7 +4,7 @@ import type { NotesRepository, UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import { MetaService } from '@/core/MetaService.js';
 import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
-import { MemoryKVCache } from '@/misc/cache.js';
+import { MemoryCache } from '@/misc/cache.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
 import NotesChart from '@/core/chart/charts/notes.js';
@@ -118,17 +118,17 @@ export class NodeinfoServerService {
 			};
 		};
 
-		const cache = new MemoryKVCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
+		const cache = new MemoryCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
 
 		fastify.get(nodeinfo2_1path, async (request, reply) => {
-			const base = await cache.fetch(null, () => nodeinfo2());
+			const base = await cache.fetch(() => nodeinfo2());
 
 			reply.header('Cache-Control', 'public, max-age=600');
 			return { version: '2.1', ...base };
 		});
 
 		fastify.get(nodeinfo2_0path, async (request, reply) => {
-			const base = await cache.fetch(null, () => nodeinfo2());
+			const base = await cache.fetch(() => nodeinfo2());
 
 			delete (base as any).software.repository;
 
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index cd6bce9ef9..6548c475b2 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -5,7 +5,7 @@ import type { LocalUser } from '@/models/entities/User.js';
 import type { AccessToken } from '@/models/entities/AccessToken.js';
 import { MemoryKVCache } from '@/misc/cache.js';
 import type { App } from '@/models/entities/App.js';
-import { UserCacheService } from '@/core/UserCacheService.js';
+import { CacheService } from '@/core/CacheService.js';
 import isNativeToken from '@/misc/is-native-token.js';
 import { bindThis } from '@/decorators.js';
 
@@ -30,7 +30,7 @@ export class AuthenticateService {
 		@Inject(DI.appsRepository)
 		private appsRepository: AppsRepository,
 
-		private userCacheService: UserCacheService,
+		private cacheService: CacheService,
 	) {
 		this.appCache = new MemoryKVCache<App>(Infinity);
 	}
@@ -42,7 +42,7 @@ export class AuthenticateService {
 		}
 	
 		if (isNativeToken(token)) {
-			const user = await this.userCacheService.localUserByNativeTokenCache.fetch(token,
+			const user = await this.cacheService.localUserByNativeTokenCache.fetch(token,
 				() => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>);
 	
 			if (user == null) {
@@ -67,7 +67,7 @@ export class AuthenticateService {
 				lastUsedAt: new Date(),
 			});
 	
-			const user = await this.userCacheService.localUserByIdCache.fetch(accessToken.userId,
+			const user = await this.cacheService.localUserByIdCache.fetch(accessToken.userId,
 				() => this.usersRepository.findOneBy({
 					id: accessToken.userId,
 				}) as Promise<LocalUser>);
diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
index f942f43cc8..786e64374c 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id });
-			const oldToken = freshUser.token;
+			const oldToken = freshUser.token!;
 
 			const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
 
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index b1eaab3908..46b16e9dce 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -18,6 +18,7 @@ import { AccountUpdateService } from '@/core/AccountUpdateService.js';
 import { HashtagService } from '@/core/HashtagService.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
+import { CacheService } from '@/core/CacheService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -152,6 +153,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private accountUpdateService: AccountUpdateService,
 		private hashtagService: HashtagService,
 		private roleService: RoleService,
+		private cacheService: CacheService,
 	) {
 		super(meta, paramDef, async (ps, _user, token) => {
 			const user = await this.usersRepository.findOneByOrFail({ id: _user.id });
@@ -276,9 +278,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				includeSecrets: isSecure,
 			});
 
+			const updatedProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+
+			this.cacheService.userProfileCache.set(user.id, updatedProfile);
+
 			// Publish meUpdated event
 			this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj);
-			this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneByOrFail({ userId: user.id }));
+			this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', updatedProfile);
 
 			// 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
 			if (user.isLocked && ps.isLocked === false) {
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 9099eea52e..fd062e1cab 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -7,6 +7,7 @@ import type { Muting } from '@/models/entities/Muting.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
 import { GetterService } from '@/server/api/GetterService.js';
+import { CacheService } from '@/core/CacheService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -65,6 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private globalEventService: GlobalEventService,
 		private getterService: GetterService,
 		private idService: IdService,
+		private cacheService: CacheService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const muter = me;
@@ -103,6 +105,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				muteeId: mutee.id,
 			} as Muting);
 
+			this.cacheService.userMutingsCache.delete(muter.id);
 			this.globalEventService.publishUserEvent(me.id, 'mute', mutee);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
index 051a005b67..b285269617 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
@@ -92,8 +92,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				muterId: muter.id,
 				muteeId: mutee.id,
 			} as RenoteMuting);
-
-		// publishUserEvent(user.id, 'mute', mutee);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index b8f50e0546..1e6e51e76d 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -19,7 +19,7 @@ import type { EventEmitter } from 'events';
 //#region Stream type-body definitions
 export interface InternalStreamTypes {
 	userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; };
-	userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; };
+	userTokenRegenerated: { id: User['id']; oldToken: string; newToken: string; };
 	remoteUserUpdated: { id: User['id']; };
 	follow: { followerId: User['id']; followeeId: User['id']; };
 	unfollow: { followerId: User['id']; followeeId: User['id']; };
diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts
index 6fe04274e6..907f1f2edc 100644
--- a/packages/backend/test/unit/RoleService.ts
+++ b/packages/backend/test/unit/RoleService.ts
@@ -11,7 +11,7 @@ import type { Role, RolesRepository, RoleAssignmentsRepository, UsersRepository,
 import { DI } from '@/di-symbols.js';
 import { MetaService } from '@/core/MetaService.js';
 import { genAid } from '@/misc/id/aid.js';
-import { UserCacheService } from '@/core/UserCacheService.js';
+import { CacheService } from '@/core/CacheService.js';
 import { IdService } from '@/core/IdService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { sleep } from '../utils.js';
@@ -65,7 +65,7 @@ describe('RoleService', () => {
 			],
 			providers: [
 				RoleService,
-				UserCacheService,
+				CacheService,
 				IdService,
 				GlobalEventService,
 			],