diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index e9966b7785..9ce5c3e8b0 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -1,5 +1,5 @@
 export class Cache<T> {
-	private cache: Map<string | null, { date: number; value: T; }>;
+	public cache: Map<string | null, { date: number; value: T; }>;
 	private lifetime: number;
 
 	constructor(lifetime: Cache<never>['lifetime']) {
diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts
index 9d5db10eb3..c76824c977 100644
--- a/packages/backend/src/models/entities/user.ts
+++ b/packages/backend/src/models/entities/user.ts
@@ -234,3 +234,9 @@ export interface ILocalUser extends User {
 export interface IRemoteUser extends User {
 	host: string;
 }
+
+export type CacheableLocalUser = ILocalUser;
+
+export type CacheableRemoteUser = IRemoteUser;
+
+export type CacheableUser = CacheableLocalUser | CacheableRemoteUser;
diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts
index 1b3f94b700..4fbfdb234f 100644
--- a/packages/backend/src/queue/processors/inbox.ts
+++ b/packages/backend/src/queue/processors/inbox.ts
@@ -15,6 +15,8 @@ import DbResolver from '@/remote/activitypub/db-resolver.js';
 import { resolvePerson } from '@/remote/activitypub/models/person.js';
 import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js';
 import { StatusError } from '@/misc/fetch.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
+import { UserPublickey } from '@/models/entities/user-publickey.js';
 
 const logger = new Logger('inbox');
 
@@ -42,11 +44,13 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
 		return `Old keyId is no longer supported. ${keyIdLower}`;
 	}
 
-	// TDOO: キャッシュ
 	const dbResolver = new DbResolver();
 
 	// HTTP-Signature keyIdを元にDBから取得
-	let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
+	let authUser: {
+		user: CacheableRemoteUser;
+		key: UserPublickey | null;
+	} | null = await dbResolver.getAuthUserFromKeyId(signature.keyId);
 
 	// keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得
 	if (authUser == null) {
diff --git a/packages/backend/src/remote/activitypub/audience.ts b/packages/backend/src/remote/activitypub/audience.ts
index ba69b11e85..846ccf9c00 100644
--- a/packages/backend/src/remote/activitypub/audience.ts
+++ b/packages/backend/src/remote/activitypub/audience.ts
@@ -3,26 +3,26 @@ import Resolver from './resolver.js';
 import { resolvePerson } from './models/person.js';
 import { unique, concat } from '@/prelude/array.js';
 import promiseLimit from 'promise-limit';
-import { User, IRemoteUser } from '@/models/entities/user.js';
+import { User, CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js';
 
 type Visibility = 'public' | 'home' | 'followers' | 'specified';
 
 type AudienceInfo = {
 	visibility: Visibility,
-	mentionedUsers: User[],
-	visibleUsers: User[],
+	mentionedUsers: CacheableUser[],
+	visibleUsers: CacheableUser[],
 };
 
-export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
+export async function parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
 	const toGroups = groupingAudience(getApIds(to), actor);
 	const ccGroups = groupingAudience(getApIds(cc), actor);
 
 	const others = unique(concat([toGroups.other, ccGroups.other]));
 
-	const limit = promiseLimit<User | null>(2);
+	const limit = promiseLimit<CacheableUser | null>(2);
 	const mentionedUsers = (await Promise.all(
 		others.map(id => limit(() => resolvePerson(id, resolver).catch(() => null)))
-	)).filter((x): x is User => x != null);
+	)).filter((x): x is CacheableUser => x != null);
 
 	if (toGroups.public.length > 0) {
 		return {
@@ -55,7 +55,7 @@ export async function parseAudience(actor: IRemoteUser, to?: ApObject, cc?: ApOb
 	};
 }
 
-function groupingAudience(ids: string[], actor: IRemoteUser) {
+function groupingAudience(ids: string[], actor: CacheableRemoteUser) {
 	const groups = {
 		public: [] as string[],
 		followers: [] as string[],
@@ -85,7 +85,7 @@ function isPublic(id: string) {
 	].includes(id);
 }
 
-function isFollowers(id: string, actor: IRemoteUser) {
+function isFollowers(id: string, actor: CacheableRemoteUser) {
 	return (
 		id === (actor.followersUri || `${actor.uri}/followers`)
 	);
diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts
index 3f16c5f56d..3e7d2655ff 100644
--- a/packages/backend/src/remote/activitypub/db-resolver.ts
+++ b/packages/backend/src/remote/activitypub/db-resolver.ts
@@ -1,12 +1,17 @@
+import escapeRegexp from 'escape-regexp';
 import config from '@/config/index.js';
 import { Note } from '@/models/entities/note.js';
-import { User, IRemoteUser } from '@/models/entities/user.js';
+import { User, IRemoteUser, CacheableRemoteUser } from '@/models/entities/user.js';
 import { UserPublickey } from '@/models/entities/user-publickey.js';
 import { MessagingMessage } from '@/models/entities/messaging-message.js';
 import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js';
 import { IObject, getApId } from './type.js';
 import { resolvePerson } from './models/person.js';
-import escapeRegexp from 'escape-regexp';
+import { Cache } from '@/misc/cache.js';
+import { userByIdCache } from '@/services/user-cache.js';
+
+const publicKeyCache = new Cache<UserPublickey | null>(Infinity);
+const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
 
 export default class DbResolver {
 	constructor() {
@@ -75,17 +80,24 @@ export default class DbResolver {
 	/**
 	 * AP KeyId => Misskey User and Key
 	 */
-	public async getAuthUserFromKeyId(keyId: string): Promise<AuthUser | null> {
-		const key = await UserPublickeys.findOne({
-			keyId,
-		}, {
-			relations: ['user'],
-		});
+	public async getAuthUserFromKeyId(keyId: string): Promise<{
+		user: CacheableRemoteUser;
+		key: UserPublickey;
+	} | null> {
+		const key = await publicKeyCache.fetch(keyId, async () => {
+			const key = await UserPublickeys.findOne({
+				keyId,
+			});
+	
+			if (key == null) return null;
+
+			return key;
+		}, key => key != null);
 
 		if (key == null) return null;
 
 		return {
-			user: key.user as IRemoteUser,
+			user: await userByIdCache.fetch(key.userId, () => Users.findOneOrFail(key.userId)) as CacheableRemoteUser,
 			key,
 		};
 	}
@@ -93,12 +105,15 @@ export default class DbResolver {
 	/**
 	 * AP Actor id => Misskey User and Key
 	 */
-	public async getAuthUserFromApId(uri: string): Promise<AuthUser | null> {
-		const user = await resolvePerson(uri) as IRemoteUser;
+	public async getAuthUserFromApId(uri: string): Promise<{
+		user: CacheableRemoteUser;
+		key: UserPublickey | null;
+	} | null> {
+		const user = await resolvePerson(uri) as CacheableRemoteUser;
 
 		if (user == null) return null;
 
-		const key = await UserPublickeys.findOne(user.id);
+		const key = await publicKeyByUserIdCache.fetch(user.id, () => UserPublickeys.findOne(user.id).then(x => x || null), v => v != null); // TODO: typeorm 3.0 にしたら.then(x => x || null)は消せる
 
 		return {
 			user,
@@ -125,11 +140,6 @@ export default class DbResolver {
 	}
 }
 
-export type AuthUser = {
-	user: IRemoteUser;
-	key?: UserPublickey;
-};
-
 type UriParseResult = {
 	/** id in DB (local object only) */
 	id?: string;
diff --git a/packages/backend/src/remote/activitypub/deliver-manager.ts b/packages/backend/src/remote/activitypub/deliver-manager.ts
index 9c4e3418ff..6cbd57af5d 100644
--- a/packages/backend/src/remote/activitypub/deliver-manager.ts
+++ b/packages/backend/src/remote/activitypub/deliver-manager.ts
@@ -112,7 +112,7 @@ export default class DeliverManager {
  * @param activity Activity
  * @param from Followee
  */
-export async function deliverToFollowers(actor: ILocalUser, activity: any) {
+export async function deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) {
 	const manager = new DeliverManager(actor, activity);
 	manager.addFollowersRecipe();
 	await manager.execute();
@@ -123,7 +123,7 @@ export async function deliverToFollowers(actor: ILocalUser, activity: any) {
  * @param activity Activity
  * @param to Target user
  */
-export async function deliverToUser(actor: ILocalUser, activity: any, to: IRemoteUser) {
+export async function deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) {
 	const manager = new DeliverManager(actor, activity);
 	manager.addDirectRecipe(to);
 	await manager.execute();
diff --git a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts
index 393516addf..4350ef1333 100644
--- a/packages/backend/src/remote/activitypub/kernel/accept/follow.ts
+++ b/packages/backend/src/remote/activitypub/kernel/accept/follow.ts
@@ -1,10 +1,10 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import accept from '@/services/following/requests/accept.js';
 import { IFollow } from '../../type.js';
 import DbResolver from '../../db-resolver.js';
 import { relayAccepted } from '@/services/relay.js';
 
-export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
 	// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
 
 	const dbResolver = new DbResolver();
diff --git a/packages/backend/src/remote/activitypub/kernel/accept/index.ts b/packages/backend/src/remote/activitypub/kernel/accept/index.ts
index 354bd4f6e1..78ef75ade3 100644
--- a/packages/backend/src/remote/activitypub/kernel/accept/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/accept/index.ts
@@ -1,12 +1,12 @@
 import Resolver from '../../resolver.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import acceptFollow from './follow.js';
 import { IAccept, isFollow, getApType } from '../../type.js';
 import { apLogger } from '../../logger.js';
 
 const logger = apLogger;
 
-export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<string> => {
 	const uri = activity.id || activity;
 
 	logger.info(`Accept: ${uri}`);
diff --git a/packages/backend/src/remote/activitypub/kernel/add/index.ts b/packages/backend/src/remote/activitypub/kernel/add/index.ts
index 9a2fac1e74..c813414f93 100644
--- a/packages/backend/src/remote/activitypub/kernel/add/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/add/index.ts
@@ -1,9 +1,9 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { IAdd } from '../../type.js';
 import { resolveNote } from '../../models/note.js';
 import { addPinned } from '@/services/i/pin.js';
 
-export default async (actor: IRemoteUser, activity: IAdd): Promise<void> => {
+export default async (actor: CacheableRemoteUser, activity: IAdd): Promise<void> => {
 	if ('actor' in activity && actor.uri !== activity.actor) {
 		throw new Error('invalid actor');
 	}
diff --git a/packages/backend/src/remote/activitypub/kernel/announce/index.ts b/packages/backend/src/remote/activitypub/kernel/announce/index.ts
index 7e2e73bdd5..ae7e507c99 100644
--- a/packages/backend/src/remote/activitypub/kernel/announce/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/announce/index.ts
@@ -1,12 +1,12 @@
 import Resolver from '../../resolver.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import announceNote from './note.js';
 import { IAnnounce, getApId } from '../../type.js';
 import { apLogger } from '../../logger.js';
 
 const logger = apLogger;
 
-export default async (actor: IRemoteUser, activity: IAnnounce): Promise<void> => {
+export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise<void> => {
 	const uri = getApId(activity);
 
 	logger.info(`Announce: ${uri}`);
diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts
index f6068fac79..680749f4d8 100644
--- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts
+++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts
@@ -1,6 +1,6 @@
 import Resolver from '../../resolver.js';
 import post from '@/services/note/create.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { IAnnounce, getApId } from '../../type.js';
 import { fetchNote, resolveNote } from '../../models/note.js';
 import { apLogger } from '../../logger.js';
@@ -15,10 +15,9 @@ const logger = apLogger;
 /**
  * アナウンスアクティビティを捌きます
  */
-export default async function(resolver: Resolver, actor: IRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
+export default async function(resolver: Resolver, actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
 	const uri = getApId(activity);
 
-	// アナウンサーが凍結されていたらスキップ
 	if (actor.isSuspended) {
 		return;
 	}
diff --git a/packages/backend/src/remote/activitypub/kernel/block/index.ts b/packages/backend/src/remote/activitypub/kernel/block/index.ts
index 9e4f1b316e..69037fec17 100644
--- a/packages/backend/src/remote/activitypub/kernel/block/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/block/index.ts
@@ -1,9 +1,10 @@
 import { IBlock } from '../../type.js';
 import block from '@/services/blocking/create.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import DbResolver from '../../db-resolver.js';
+import { Users } from '@/models/index.js';
 
-export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<string> => {
 	// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
 
 	const dbResolver = new DbResolver();
@@ -17,6 +18,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<string> =>
 		return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`;
 	}
 
-	await block(actor, blockee);
+	await block(await Users.findOneOrFail(actor.id), blockee);
 	return `ok`;
 };
diff --git a/packages/backend/src/remote/activitypub/kernel/create/index.ts b/packages/backend/src/remote/activitypub/kernel/create/index.ts
index 1187b95ac6..c253f9f667 100644
--- a/packages/backend/src/remote/activitypub/kernel/create/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/create/index.ts
@@ -1,5 +1,5 @@
 import Resolver from '../../resolver.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import createNote from './note.js';
 import { ICreate, getApId, isPost, getApType } from '../../type.js';
 import { apLogger } from '../../logger.js';
@@ -7,7 +7,7 @@ import { toArray, concat, unique } from '@/prelude/array.js';
 
 const logger = apLogger;
 
-export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
+export default async (actor: CacheableRemoteUser, activity: ICreate): Promise<void> => {
 	const uri = getApId(activity);
 
 	logger.info(`Create: ${uri}`);
diff --git a/packages/backend/src/remote/activitypub/kernel/create/note.ts b/packages/backend/src/remote/activitypub/kernel/create/note.ts
index b5c47990aa..f8dabe06e2 100644
--- a/packages/backend/src/remote/activitypub/kernel/create/note.ts
+++ b/packages/backend/src/remote/activitypub/kernel/create/note.ts
@@ -1,5 +1,5 @@
 import Resolver from '../../resolver.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { createNote, fetchNote } from '../../models/note.js';
 import { getApId, IObject, ICreate } from '../../type.js';
 import { getApLock } from '@/misc/app-lock.js';
@@ -9,7 +9,7 @@ import { StatusError } from '@/misc/fetch.js';
 /**
  * 投稿作成アクティビティを捌きます
  */
-export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
+export default async function(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
 	const uri = getApId(note);
 
 	if (typeof note === 'object') {
diff --git a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts
index 2f75841e52..53dabb1191 100644
--- a/packages/backend/src/remote/activitypub/kernel/delete/actor.ts
+++ b/packages/backend/src/remote/activitypub/kernel/delete/actor.ts
@@ -1,18 +1,19 @@
 import { apLogger } from '../../logger.js';
 import { createDeleteAccountJob } from '@/queue/index.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { Users } from '@/models/index.js';
 
 const logger = apLogger;
 
-export async function deleteActor(actor: IRemoteUser, uri: string): Promise<string> {
+export async function deleteActor(actor: CacheableRemoteUser, uri: string): Promise<string> {
 	logger.info(`Deleting the Actor: ${uri}`);
 
 	if (actor.uri !== uri) {
 		return `skip: delete actor ${actor.uri} !== ${uri}`;
 	}
 
-	if (actor.isDeleted) {
+	const user = await Users.findOneOrFail(actor.id);
+	if (user.isDeleted) {
 		logger.info(`skip: already deleted`);
 	}
 
diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts
index b6d5e96d03..4c06a9de0b 100644
--- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts
@@ -1,5 +1,5 @@
 import deleteNote from './note.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { IDelete, getApId, isTombstone, IObject, validPost, validActor } from '../../type.js';
 import { toSingle } from '@/prelude/array.js';
 import { deleteActor } from './actor.js';
@@ -7,7 +7,7 @@ import { deleteActor } from './actor.js';
 /**
  * 削除アクティビティを捌きます
  */
-export default async (actor: IRemoteUser, activity: IDelete): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IDelete): Promise<string> => {
 	if ('actor' in activity && actor.uri !== activity.actor) {
 		throw new Error('invalid actor');
 	}
diff --git a/packages/backend/src/remote/activitypub/kernel/delete/note.ts b/packages/backend/src/remote/activitypub/kernel/delete/note.ts
index ad5e1a2edc..1f44c35562 100644
--- a/packages/backend/src/remote/activitypub/kernel/delete/note.ts
+++ b/packages/backend/src/remote/activitypub/kernel/delete/note.ts
@@ -1,4 +1,4 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import deleteNode from '@/services/note/delete.js';
 import { apLogger } from '../../logger.js';
 import DbResolver from '../../db-resolver.js';
@@ -7,7 +7,7 @@ import { deleteMessage } from '@/services/messages/delete.js';
 
 const logger = apLogger;
 
-export default async function(actor: IRemoteUser, uri: string): Promise<string> {
+export default async function(actor: CacheableRemoteUser, uri: string): Promise<string> {
 	logger.info(`Deleting the Note: ${uri}`);
 
 	const unlock = await getApLock(uri);
diff --git a/packages/backend/src/remote/activitypub/kernel/flag/index.ts b/packages/backend/src/remote/activitypub/kernel/flag/index.ts
index e80e632786..45c0a6c711 100644
--- a/packages/backend/src/remote/activitypub/kernel/flag/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/flag/index.ts
@@ -1,11 +1,11 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import config from '@/config/index.js';
 import { IFlag, getApIds } from '../../type.js';
 import { AbuseUserReports, Users } from '@/models/index.js';
 import { In } from 'typeorm';
 import { genId } from '@/misc/gen-id.js';
 
-export default async (actor: IRemoteUser, activity: IFlag): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IFlag): Promise<string> => {
 	// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
 	// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
 	const uris = getApIds(activity.object);
diff --git a/packages/backend/src/remote/activitypub/kernel/follow.ts b/packages/backend/src/remote/activitypub/kernel/follow.ts
index 49c1a7ee01..a9e92fa229 100644
--- a/packages/backend/src/remote/activitypub/kernel/follow.ts
+++ b/packages/backend/src/remote/activitypub/kernel/follow.ts
@@ -1,9 +1,9 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import follow from '@/services/following/create.js';
 import { IFollow } from '../type.js';
 import DbResolver from '../db-resolver.js';
 
-export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
 	const dbResolver = new DbResolver();
 	const followee = await dbResolver.getUserFromApId(activity.object);
 
diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts
index 6aea8e57cf..254a121605 100644
--- a/packages/backend/src/remote/activitypub/kernel/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/index.ts
@@ -1,5 +1,5 @@
 import { IObject, isCreate, isDelete, isUpdate, isRead, isFollow, isAccept, isReject, isAdd, isRemove, isAnnounce, isLike, isUndo, isBlock, isCollectionOrOrderedCollection, isCollection, isFlag } from '../type.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import create from './create/index.js';
 import performDeleteActivity from './delete/index.js';
 import performUpdateActivity from './update/index.js';
@@ -17,8 +17,9 @@ import flag from './flag/index.js';
 import { apLogger } from '../logger.js';
 import Resolver from '../resolver.js';
 import { toArray } from '@/prelude/array.js';
+import { Users } from '@/models/index.js';
 
-export async function performActivity(actor: IRemoteUser, activity: IObject) {
+export async function performActivity(actor: CacheableRemoteUser, activity: IObject) {
 	if (isCollectionOrOrderedCollection(activity)) {
 		const resolver = new Resolver();
 		for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
@@ -36,7 +37,7 @@ export async function performActivity(actor: IRemoteUser, activity: IObject) {
 	}
 }
 
-async function performOneActivity(actor: IRemoteUser, activity: IObject): Promise<void> {
+async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise<void> {
 	if (actor.isSuspended) return;
 
 	if (isCreate(activity)) {
diff --git a/packages/backend/src/remote/activitypub/kernel/like.ts b/packages/backend/src/remote/activitypub/kernel/like.ts
index 715cc379b9..2b65ff7383 100644
--- a/packages/backend/src/remote/activitypub/kernel/like.ts
+++ b/packages/backend/src/remote/activitypub/kernel/like.ts
@@ -1,9 +1,9 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { ILike, getApId } from '../type.js';
 import create from '@/services/note/reaction/create.js';
 import { fetchNote, extractEmojis } from '../models/note.js';
 
-export default async (actor: IRemoteUser, activity: ILike) => {
+export default async (actor: CacheableRemoteUser, activity: ILike) => {
 	const targetUri = getApId(activity.object);
 
 	const note = await fetchNote(targetUri);
diff --git a/packages/backend/src/remote/activitypub/kernel/read.ts b/packages/backend/src/remote/activitypub/kernel/read.ts
index 93cc36ec46..333466e22f 100644
--- a/packages/backend/src/remote/activitypub/kernel/read.ts
+++ b/packages/backend/src/remote/activitypub/kernel/read.ts
@@ -1,10 +1,10 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { IRead, getApId } from '../type.js';
 import { isSelfHost, extractDbHost } from '@/misc/convert-host.js';
 import { MessagingMessages } from '@/models/index.js';
 import { readUserMessagingMessage } from '../../../server/api/common/read-messaging-message.js';
 
-export const performReadActivity = async (actor: IRemoteUser, activity: IRead): Promise<string> => {
+export const performReadActivity = async (actor: CacheableRemoteUser, activity: IRead): Promise<string> => {
 	const id = await getApId(activity.object);
 
 	if (!isSelfHost(extractDbHost(id))) {
diff --git a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts
index 72751e83c0..824ac69d70 100644
--- a/packages/backend/src/remote/activitypub/kernel/reject/follow.ts
+++ b/packages/backend/src/remote/activitypub/kernel/reject/follow.ts
@@ -1,11 +1,11 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { remoteReject } from '@/services/following/reject.js';
 import { IFollow } from '../../type.js';
 import DbResolver from '../../db-resolver.js';
 import { relayRejected } from '@/services/relay.js';
 import { Users } from '@/models/index.js';
 
-export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
 	// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
 
 	const dbResolver = new DbResolver();
diff --git a/packages/backend/src/remote/activitypub/kernel/reject/index.ts b/packages/backend/src/remote/activitypub/kernel/reject/index.ts
index ed86a4aa2f..00f08842f4 100644
--- a/packages/backend/src/remote/activitypub/kernel/reject/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/reject/index.ts
@@ -1,12 +1,12 @@
 import Resolver from '../../resolver.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import rejectFollow from './follow.js';
 import { IReject, isFollow, getApType } from '../../type.js';
 import { apLogger } from '../../logger.js';
 
 const logger = apLogger;
 
-export default async (actor: IRemoteUser, activity: IReject): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IReject): Promise<string> => {
 	const uri = activity.id || activity;
 
 	logger.info(`Reject: ${uri}`);
diff --git a/packages/backend/src/remote/activitypub/kernel/remove/index.ts b/packages/backend/src/remote/activitypub/kernel/remove/index.ts
index 7d7b3386c0..11a994a83b 100644
--- a/packages/backend/src/remote/activitypub/kernel/remove/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/remove/index.ts
@@ -1,9 +1,9 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { IRemove } from '../../type.js';
 import { resolveNote } from '../../models/note.js';
 import { removePinned } from '@/services/i/pin.js';
 
-export default async (actor: IRemoteUser, activity: IRemove): Promise<void> => {
+export default async (actor: CacheableRemoteUser, activity: IRemove): Promise<void> => {
 	if ('actor' in activity && actor.uri !== activity.actor) {
 		throw new Error('invalid actor');
 	}
diff --git a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts
index 2383eea5bd..e2f77ca792 100644
--- a/packages/backend/src/remote/activitypub/kernel/undo/accept.ts
+++ b/packages/backend/src/remote/activitypub/kernel/undo/accept.ts
@@ -1,11 +1,11 @@
 import unfollow from '@/services/following/delete.js';
 import cancelRequest from '@/services/following/requests/cancel.js';
 import {IAccept} from '../../type.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { Followings } from '@/models/index.js';
 import DbResolver from '../../db-resolver.js';
 
-export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<string> => {
 	const dbResolver = new DbResolver();
 
 	const follower = await dbResolver.getUserFromApId(activity.object);
diff --git a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts
index 822c1e4948..f31aca1d8d 100644
--- a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts
+++ b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts
@@ -1,9 +1,9 @@
 import { Notes } from '@/models/index.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { IAnnounce, getApId } from '../../type.js';
 import deleteNote from '@/services/note/delete.js';
 
-export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<string> => {
+export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnounce): Promise<string> => {
 	const uri = getApId(activity);
 
 	const note = await Notes.findOne({
diff --git a/packages/backend/src/remote/activitypub/kernel/undo/block.ts b/packages/backend/src/remote/activitypub/kernel/undo/block.ts
index 844b067e2b..a4bb5e604f 100644
--- a/packages/backend/src/remote/activitypub/kernel/undo/block.ts
+++ b/packages/backend/src/remote/activitypub/kernel/undo/block.ts
@@ -1,9 +1,10 @@
 import { IBlock } from '../../type.js';
 import unblock from '@/services/blocking/delete.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import DbResolver from '../../db-resolver.js';
+import { Users } from '@/models/index.js';
 
-export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IBlock): Promise<string> => {
 	const dbResolver = new DbResolver();
 	const blockee = await dbResolver.getUserFromApId(activity.object);
 
@@ -15,6 +16,6 @@ export default async (actor: IRemoteUser, activity: IBlock): Promise<string> =>
 		return `skip: ブロック解除しようとしているユーザーはローカルユーザーではありません`;
 	}
 
-	await unblock(actor, blockee);
+	await unblock(await Users.findOneOrFail(actor.id), blockee);
 	return `ok`;
 };
diff --git a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts
index 6715adcf76..f501cc8cc6 100644
--- a/packages/backend/src/remote/activitypub/kernel/undo/follow.ts
+++ b/packages/backend/src/remote/activitypub/kernel/undo/follow.ts
@@ -1,11 +1,11 @@
 import unfollow from '@/services/following/delete.js';
 import cancelRequest from '@/services/following/requests/cancel.js';
 import { IFollow } from '../../type.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { FollowRequests, Followings } from '@/models/index.js';
 import DbResolver from '../../db-resolver.js';
 
-export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IFollow): Promise<string> => {
 	const dbResolver = new DbResolver();
 
 	const followee = await dbResolver.getUserFromApId(activity.object);
diff --git a/packages/backend/src/remote/activitypub/kernel/undo/index.ts b/packages/backend/src/remote/activitypub/kernel/undo/index.ts
index 05937c6855..27d433eb33 100644
--- a/packages/backend/src/remote/activitypub/kernel/undo/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/undo/index.ts
@@ -1,5 +1,5 @@
-import { IRemoteUser } from '@/models/entities/user.js';
-import {IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept} from '../../type.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
+import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType, isAccept } from '../../type.js';
 import unfollow from './follow.js';
 import unblock from './block.js';
 import undoLike from './like.js';
@@ -10,7 +10,7 @@ import { apLogger } from '../../logger.js';
 
 const logger = apLogger;
 
-export default async (actor: IRemoteUser, activity: IUndo): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IUndo): Promise<string> => {
 	if ('actor' in activity && actor.uri !== activity.actor) {
 		throw new Error('invalid actor');
 	}
diff --git a/packages/backend/src/remote/activitypub/kernel/undo/like.ts b/packages/backend/src/remote/activitypub/kernel/undo/like.ts
index 08ac630351..01aeba1fb7 100644
--- a/packages/backend/src/remote/activitypub/kernel/undo/like.ts
+++ b/packages/backend/src/remote/activitypub/kernel/undo/like.ts
@@ -1,4 +1,4 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { ILike, getApId } from '../../type.js';
 import deleteReaction from '@/services/note/reaction/delete.js';
 import { fetchNote } from '../../models/note.js';
@@ -6,7 +6,7 @@ import { fetchNote } from '../../models/note.js';
 /**
  * Process Undo.Like activity
  */
-export default async (actor: IRemoteUser, activity: ILike) => {
+export default async (actor: CacheableRemoteUser, activity: ILike) => {
 	const targetUri = getApId(activity.object);
 
 	const note = await fetchNote(targetUri);
diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts
index 7888c698e3..9e8a81bb39 100644
--- a/packages/backend/src/remote/activitypub/kernel/update/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts
@@ -1,4 +1,4 @@
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { getApType, IUpdate, isActor } from '../../type.js';
 import { apLogger } from '../../logger.js';
 import { updateQuestion } from '../../models/question.js';
@@ -8,7 +8,7 @@ import { updatePerson } from '../../models/person.js';
 /**
  * Updateアクティビティを捌きます
  */
-export default async (actor: IRemoteUser, activity: IUpdate): Promise<string> => {
+export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<string> => {
 	if ('actor' in activity && actor.uri !== activity.actor) {
 		return `skip: invalid actor`;
 	}
diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts
index b5e9181d30..33316dcd98 100644
--- a/packages/backend/src/remote/activitypub/models/image.ts
+++ b/packages/backend/src/remote/activitypub/models/image.ts
@@ -1,10 +1,10 @@
 import { uploadFromUrl } from '@/services/drive/upload-from-url.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js';
 import Resolver from '../resolver.js';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import { apLogger } from '../logger.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
-import { DriveFiles } from '@/models/index.js';
+import { DriveFiles, Users } from '@/models/index.js';
 import { truncate } from '@/misc/truncate.js';
 import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
 
@@ -13,7 +13,7 @@ const logger = apLogger;
 /**
  * Imageを作成します。
  */
-export async function createImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
+export async function createImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
 	// 投稿者が凍結されていたらスキップ
 	if (actor.isSuspended) {
 		throw new Error('actor has been suspended');
@@ -60,7 +60,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
  * Misskeyに対象のImageが登録されていればそれを返し、そうでなければ
  * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
  */
-export async function resolveImage(actor: IRemoteUser, value: any): Promise<DriveFile> {
+export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
 	// TODO
 
 	// リモートサーバーからフェッチしてきて登録
diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts
index c5b0ea53ce..a160092969 100644
--- a/packages/backend/src/remote/activitypub/models/mention.ts
+++ b/packages/backend/src/remote/activitypub/models/mention.ts
@@ -3,17 +3,17 @@ import { IObject, isMention, IApMention } from '../type.js';
 import { resolvePerson } from './person.js';
 import promiseLimit from 'promise-limit';
 import Resolver from '../resolver.js';
-import { User } from '@/models/entities/user.js';
+import { CacheableUser, User } from '@/models/entities/user.js';
 
 export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
 	const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
 
 	const resolver = new Resolver();
 
-	const limit = promiseLimit<User | null>(2);
+	const limit = promiseLimit<CacheableUser | null>(2);
 	const mentionedUsers = (await Promise.all(
 		hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null)))
-	)).filter((x): x is User => x != null);
+	)).filter((x): x is CacheableUser => x != null);
 
 	return mentionedUsers;
 }
diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts
index 0bcc89fbbb..bdcec6b257 100644
--- a/packages/backend/src/remote/activitypub/models/note.ts
+++ b/packages/backend/src/remote/activitypub/models/note.ts
@@ -5,7 +5,7 @@ import Resolver from '../resolver.js';
 import post from '@/services/note/create.js';
 import { resolvePerson, updatePerson } from './person.js';
 import { resolveImage } from './image.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js';
 import { htmlToMfm } from '../misc/html-to-mfm.js';
 import { extractApHashtags } from './tag.js';
 import { unique, toArray, toSingle } from '@/prelude/array.js';
@@ -15,7 +15,7 @@ import { apLogger } from '../logger.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
 import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
 import { extractDbHost, toPuny } from '@/misc/convert-host.js';
-import { Emojis, Polls, MessagingMessages } from '@/models/index.js';
+import { Emojis, Polls, MessagingMessages, Users } from '@/models/index.js';
 import { Note } from '@/models/entities/note.js';
 import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js';
 import { Emoji } from '@/models/entities/emoji.js';
@@ -90,7 +90,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
 	logger.info(`Creating the Note: ${note.id}`);
 
 	// 投稿者をフェッチ
-	const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as IRemoteUser;
+	const actor = await resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser;
 
 	// 投稿者が凍結されていたらスキップ
 	if (actor.isSuspended) {
@@ -230,11 +230,6 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
 
 	const poll = await extractPollFromQuestion(note, resolver).catch(() => undefined);
 
-	// ユーザーの情報が古かったらついでに更新しておく
-	if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
-		if (actor.uri) updatePerson(actor.uri);
-	}
-
 	if (isTalk) {
 		for (const recipient of visibleUsers) {
 			await createMessage(actor, recipient, undefined, text || undefined, (files && files.length > 0) ? files[0] : null, object.id);
diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index 659d3ac9a2..de64a4305b 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -15,7 +15,7 @@ import { apLogger } from '../logger.js';
 import { Note } from '@/models/entities/note.js';
 import { updateUsertags } from '@/services/update-hashtag.js';
 import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js';
-import { User, IRemoteUser } from '@/models/entities/user.js';
+import { User, IRemoteUser, CacheableUser } from '@/models/entities/user.js';
 import { Emoji } from '@/models/entities/emoji.js';
 import { UserNotePining } from '@/models/entities/user-note-pining.js';
 import { genId } from '@/misc/gen-id.js';
@@ -30,6 +30,8 @@ import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
 import { truncate } from '@/misc/truncate.js';
 import { StatusError } from '@/misc/fetch.js';
+import { uriPersonCache } from '@/services/user-cache.js';
+import { publishInternalEvent } from '@/services/stream.js';
 
 const logger = apLogger;
 
@@ -91,19 +93,25 @@ function validateActor(x: IObject, uri: string): IActor {
  *
  * Misskeyに対象のPersonが登録されていればそれを返します。
  */
-export async function fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
+export async function fetchPerson(uri: string, resolver?: Resolver): Promise<CacheableUser | null> {
 	if (typeof uri !== 'string') throw new Error('uri is not string');
 
+	const cached = uriPersonCache.get(uri);
+	if (cached) return cached;
+
 	// URIがこのサーバーを指しているならデータベースからフェッチ
 	if (uri.startsWith(config.url + '/')) {
 		const id = uri.split('/').pop();
-		return await Users.findOne(id).then(x => x || null);
+		const u = await Users.findOne(id).then(x => x || null); // TODO: typeorm 3.0 にしたら .then(x => x || null) を消す
+		if (u) uriPersonCache.set(uri, u);
+		return u;
 	}
 
 	//#region このサーバーに既に登録されていたらそれを返す
 	const exist = await Users.findOne({ uri });
 
 	if (exist) {
+		uriPersonCache.set(uri, exist);
 		return exist;
 	}
 	//#endregion
@@ -352,6 +360,8 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
 		location: person['vcard:Address'] || null,
 	});
 
+	publishInternalEvent('remoteUserUpdated', { id: exist.id });
+
 	// ハッシュタグ更新
 	updateUsertags(exist, tags);
 
@@ -371,7 +381,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
  * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ
  * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
  */
-export async function resolvePerson(uri: string, resolver?: Resolver): Promise<User> {
+export async function resolvePerson(uri: string, resolver?: Resolver): Promise<CacheableUser> {
 	if (typeof uri !== 'string') throw new Error('uri is not string');
 
 	//#region このサーバーに既に登録されていたらそれを返す
diff --git a/packages/backend/src/remote/activitypub/perform.ts b/packages/backend/src/remote/activitypub/perform.ts
index 3e18815586..a3c10ba945 100644
--- a/packages/backend/src/remote/activitypub/perform.ts
+++ b/packages/backend/src/remote/activitypub/perform.ts
@@ -1,7 +1,17 @@
 import { IObject } from './type.js';
-import { IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { performActivity } from './kernel/index.js';
+import { updatePerson } from './models/person.js';
 
-export default async (actor: IRemoteUser, activity: IObject): Promise<void> => {
+export default async (actor: CacheableRemoteUser, activity: IObject): Promise<void> => {
 	await performActivity(actor, activity);
+
+	// ついでにリモートユーザーの情報が古かったら更新しておく
+	if (actor.uri) {
+		if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
+			setImmediate(() => {
+				updatePerson(actor.uri!);
+			});
+		}
+	}
 };
diff --git a/packages/backend/src/server/activitypub/cache.ts b/packages/backend/src/server/activitypub/cache.ts
deleted file mode 100644
index eb20d00787..0000000000
--- a/packages/backend/src/server/activitypub/cache.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { Cache } from "@/misc/cache.js";
-import { User } from "@/models/entities/user.js";
-
-export const userCache = new Cache<User | null>(1000 * 60 * 30);
diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts
index fdae9dd928..f2bdb48bf0 100644
--- a/packages/backend/src/server/activitypub/followers.ts
+++ b/packages/backend/src/server/activitypub/followers.ts
@@ -10,7 +10,6 @@ import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
 import { setResponseType } from '../activitypub.js';
 import { Users, Followings, UserProfiles } from '@/models/index.js';
 import { LessThan } from 'typeorm';
-import { userCache } from './cache.js';
 
 export default async (ctx: Router.RouterContext) => {
 	const userId = ctx.params.user;
@@ -28,11 +27,10 @@ export default async (ctx: Router.RouterContext) => {
 		return;
 	}
 
-	// TODO: typeorm 3.0にしたら .then(x => x || null) は消せる
-	const user = await userCache.fetch(userId, () => Users.findOne({
+	const user = await Users.findOne({
 		id: userId,
 		host: null,
-	}).then(x => x || null));
+	});
 
 	if (user == null) {
 		ctx.status = 404;
diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts
index eb1b7a9d8c..16b2b051d3 100644
--- a/packages/backend/src/server/activitypub/following.ts
+++ b/packages/backend/src/server/activitypub/following.ts
@@ -11,7 +11,6 @@ import { setResponseType } from '../activitypub.js';
 import { Users, Followings, UserProfiles } from '@/models/index.js';
 import { LessThan, FindConditions } from 'typeorm';
 import { Following } from '@/models/entities/following.js';
-import { userCache } from './cache.js';
 
 export default async (ctx: Router.RouterContext) => {
 	const userId = ctx.params.user;
@@ -29,11 +28,10 @@ export default async (ctx: Router.RouterContext) => {
 		return;
 	}
 
-	// TODO: typeorm 3.0にしたら .then(x => x || null) は消せる
-	const user = await userCache.fetch(userId, () => Users.findOne({
+	const user = await Users.findOne({
 		id: userId,
 		host: null,
-	}).then(x => x || null));
+	});
 
 	if (user == null) {
 		ctx.status = 404;
diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts
index db2a18efcd..450fdea963 100644
--- a/packages/backend/src/server/activitypub/outbox.ts
+++ b/packages/backend/src/server/activitypub/outbox.ts
@@ -15,7 +15,6 @@ import { Users, Notes } from '@/models/index.js';
 import { makePaginationQuery } from '../api/common/make-pagination-query.js';
 import { Brackets } from 'typeorm';
 import { Note } from '@/models/entities/note.js';
-import { userCache } from './cache.js';
 
 export default async (ctx: Router.RouterContext) => {
 	const userId = ctx.params.user;
@@ -36,11 +35,10 @@ export default async (ctx: Router.RouterContext) => {
 		return;
 	}
 
-	// TODO: typeorm 3.0にしたら .then(x => x || null) は消せる
-	const user = await userCache.fetch(userId, () => Users.findOne({
+	const user = await Users.findOne({
 		id: userId,
 		host: null,
-	}).then(x => x || null));
+	});
 
 	if (user == null) {
 		ctx.status = 404;
diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts
index 7fdf14666e..8fb397ca5f 100644
--- a/packages/backend/src/server/api/authenticate.ts
+++ b/packages/backend/src/server/api/authenticate.ts
@@ -1,7 +1,12 @@
 import isNativeToken from './common/is-native-token.js';
-import { User } from '@/models/entities/user.js';
+import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js';
 import { Users, AccessTokens, Apps } from '@/models/index.js';
 import { AccessToken } from '@/models/entities/access-token.js';
+import { Cache } from '@/misc/cache.js';
+import { App } from '@/models/entities/app.js';
+import { localUserByIdCache, localUserByNativeTokenCache } from '@/services/user-cache.js';
+
+const appCache = new Cache<App>(Infinity);
 
 export class AuthenticationError extends Error {
 	constructor(message: string) {
@@ -10,15 +15,15 @@ export class AuthenticationError extends Error {
 	}
 }
 
-export default async (token: string | null): Promise<[User | null | undefined, AccessToken | null | undefined]> => {
+export default async (token: string | null): Promise<[CacheableLocalUser | null | undefined, AccessToken | null | undefined]> => {
 	if (token == null) {
 		return [null, null];
 	}
 
 	if (isNativeToken(token)) {
-		// Fetch user
-		const user = await Users
-			.findOne({ token });
+		// TODO: typeorm 3.0にしたら .then(x => x || null) は消せる
+		const user = await localUserByNativeTokenCache.fetch(token,
+			() => Users.findOne({ token }).then(x => x || null) as Promise<ILocalUser | null>);
 
 		if (user == null) {
 			throw new AuthenticationError('user not found');
@@ -42,14 +47,14 @@ export default async (token: string | null): Promise<[User | null | undefined, A
 			lastUsedAt: new Date(),
 		});
 
-		const user = await Users
-			.findOne({
+		const user = await localUserByIdCache.fetch(accessToken.userId,
+			() => Users.findOne({
 				id: accessToken.userId, // findOne(accessToken.userId) のように書かないのは後方互換性のため
-			});
+			}) as Promise<ILocalUser>);
 
 		if (accessToken.appId) {
-			const app = await Apps
-				.findOneOrFail(accessToken.appId);
+			const app = await appCache.fetch(accessToken.appId,
+				() => Apps.findOneOrFail(accessToken.appId!));
 
 			return [user, {
 				id: accessToken.id,
diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts
index 5c5ef66019..9a85e4565b 100644
--- a/packages/backend/src/server/api/call.ts
+++ b/packages/backend/src/server/api/call.ts
@@ -1,7 +1,7 @@
 import Koa from 'koa';
 import { performance } from 'perf_hooks';
 import { limiter } from './limiter.js';
-import { User } from '@/models/entities/user.js';
+import { CacheableLocalUser, User } from '@/models/entities/user.js';
 import endpoints, { IEndpoint } from './endpoints.js';
 import { ApiError } from './error.js';
 import { apiLogger } from './logger.js';
@@ -13,7 +13,7 @@ const accessDenied = {
 	id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e',
 };
 
-export default async (endpoint: string, user: User | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => {
+export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => {
 	const isSecure = user != null && token == null;
 
 	const ep = endpoints.find(e => e.name === endpoint);
diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts
index 4e6d041a29..1529894341 100644
--- a/packages/backend/src/server/api/define.ts
+++ b/packages/backend/src/server/api/define.ts
@@ -1,30 +1,16 @@
 import * as fs from 'node:fs';
 import Ajv from 'ajv';
-import { ILocalUser } from '@/models/entities/user.js';
+import { CacheableLocalUser, ILocalUser } from '@/models/entities/user.js';
 import { IEndpointMeta } from './endpoints.js';
 import { ApiError } from './error.js';
 import { Schema, SchemaType } from '@/misc/schema.js';
 import { AccessToken } from '@/models/entities/access-token.js';
 
-type SimpleUserInfo = {
-	id: ILocalUser['id'];
-	createdAt: ILocalUser['createdAt'];
-	host: ILocalUser['host'];
-	username: ILocalUser['username'];
-	uri: ILocalUser['uri'];
-	inbox: ILocalUser['inbox'];
-	sharedInbox: ILocalUser['sharedInbox'];
-	isAdmin: ILocalUser['isAdmin'];
-	isModerator: ILocalUser['isModerator'];
-	isSilenced: ILocalUser['isSilenced'];
-	showTimelineReplies: ILocalUser['showTimelineReplies'];
-};
-
 export type Response = Record<string, any> | void;
 
 // TODO: paramsの型をT['params']のスキーマ定義から推論する
 type executor<T extends IEndpointMeta, Ps extends Schema> =
-	(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any, cleanup?: () => any) =>
+	(params: SchemaType<Ps>, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any, cleanup?: () => any) =>
 		Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
 
 const ajv = new Ajv({
@@ -34,11 +20,11 @@ const ajv = new Ajv({
 ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/);
 
 export default function <T extends IEndpointMeta, Ps extends Schema>(meta: T, paramDef: Ps, cb: executor<T, Ps>)
-		: (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => Promise<any> {
+		: (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => Promise<any> {
 
 	const validate = ajv.compile(paramDef);
 
-	return (params: any, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any) => {
+	return (params: any, user: T['requireCredential'] extends true ? CacheableLocalUser : CacheableLocalUser | null, token: AccessToken | null, file?: any) => {
 		function cleanup() {
 			fs.unlink(file.path, () => {});
 		}
diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts
index 4206e3a3c2..60bf0ff09c 100644
--- a/packages/backend/src/server/api/endpoints/admin/moderators/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/moderators/add.ts
@@ -1,5 +1,6 @@
 import define from '../../../define.js';
 import { Users } from '@/models/index.js';
+import { publishInternalEvent } from '@/services/stream.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -31,4 +32,6 @@ export default define(meta, paramDef, async (ps) => {
 	await Users.update(user.id, {
 		isModerator: true,
 	});
+
+	publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: true });
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts
index 143119bfe4..cf9d7c14a8 100644
--- a/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts
+++ b/packages/backend/src/server/api/endpoints/admin/moderators/remove.ts
@@ -27,4 +27,6 @@ export default define(meta, paramDef, async (ps) => {
 	await Users.update(user.id, {
 		isModerator: false,
 	});
+
+	publishInternalEvent('userChangeModeratorState', { id: user.id, isModerator: false });
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index a435dcc288..564b8a0261 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -29,7 +29,8 @@ export default define(meta, paramDef, async (ps, me) => {
 		throw new Error('user not found');
 	}
 
-	if ((me.isModerator && !me.isAdmin) && user.isAdmin) {
+	const _me = await Users.findOneOrFail(me.id);
+	if ((_me.isModerator && !_me.isAdmin) && user.isAdmin) {
 		throw new Error('cannot show info of admin');
 	}
 
diff --git a/packages/backend/src/server/api/endpoints/admin/silence-user.ts b/packages/backend/src/server/api/endpoints/admin/silence-user.ts
index 4a74c3fb00..4cbed1cf9c 100644
--- a/packages/backend/src/server/api/endpoints/admin/silence-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/silence-user.ts
@@ -1,6 +1,7 @@
 import define from '../../define.js';
 import { Users } from '@/models/index.js';
 import { insertModerationLog } from '@/services/insert-moderation-log.js';
+import { publishInternalEvent } from '@/services/stream.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -33,6 +34,8 @@ export default define(meta, paramDef, async (ps, me) => {
 		isSilenced: true,
 	});
 
+	publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: true });
+
 	insertModerationLog(me, 'silence', {
 		targetId: user.id,
 	});
diff --git a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts
index 4e6366aa18..6c66288117 100644
--- a/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/unsilence-user.ts
@@ -1,6 +1,7 @@
 import define from '../../define.js';
 import { Users } from '@/models/index.js';
 import { insertModerationLog } from '@/services/insert-moderation-log.js';
+import { publishInternalEvent } from '@/services/stream.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -29,6 +30,8 @@ export default define(meta, paramDef, async (ps, me) => {
 		isSilenced: false,
 	});
 
+	publishInternalEvent('userChangeSilencedState', { id: user.id, isSilenced: false });
+
 	insertModerationLog(me, 'unsilence', {
 		targetId: user.id,
 	});
diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
index 5f565a63fb..eac8eabfc0 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
@@ -2,7 +2,7 @@ import { deleteFile } from '@/services/drive/delete-file.js';
 import { publishDriveStream } from '@/services/stream.js';
 import define from '../../../define.js';
 import { ApiError } from '../../../error.js';
-import { DriveFiles } from '@/models/index.js';
+import { DriveFiles, Users } from '@/models/index.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -42,7 +42,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		throw new ApiError(meta.errors.noSuchFile);
 	}
 
-	if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) {
+	if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) {
 		throw new ApiError(meta.errors.accessDenied);
 	}
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts
index 181365c7e6..16b313cabf 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts
@@ -1,7 +1,7 @@
 import define from '../../../define.js';
 import { ApiError } from '../../../error.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
-import { DriveFiles } from '@/models/index.js';
+import { DriveFiles, Users } from '@/models/index.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -70,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		throw new ApiError(meta.errors.noSuchFile);
 	}
 
-	if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) {
+	if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) {
 		throw new ApiError(meta.errors.accessDenied);
 	}
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index ab8e4aeeb2..30d7847b5b 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -1,7 +1,7 @@
 import { publishDriveStream } from '@/services/stream.js';
 import define from '../../../define.js';
 import { ApiError } from '../../../error.js';
-import { DriveFiles, DriveFolders } from '@/models/index.js';
+import { DriveFiles, DriveFolders, Users } from '@/models/index.js';
 import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits.js';
 
 export const meta = {
@@ -64,7 +64,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		throw new ApiError(meta.errors.noSuchFile);
 	}
 
-	if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) {
+	if ((!user.isAdmin && !user.isModerator) && (file.userId !== user.id)) {
 		throw new ApiError(meta.errors.accessDenied);
 	}
 
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 771c98b212..ae23d2482e 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -1,5 +1,5 @@
 import bcrypt from 'bcryptjs';
-import { publishMainStream, publishUserEvent } from '@/services/stream.js';
+import { publishInternalEvent, publishMainStream, publishUserEvent } from '@/services/stream.js';
 import generateUserToken from '../../common/generate-native-user-token.js';
 import define from '../../define.js';
 import { Users, UserProfiles } from '@/models/index.js';
@@ -20,6 +20,9 @@ export const paramDef = {
 
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
+	const freshUser = await Users.findOneOrFail(user.id);
+	const oldToken = freshUser.token;
+
 	const profile = await UserProfiles.findOneOrFail(user.id);
 
 	// Compare password
@@ -29,14 +32,14 @@ export default define(meta, paramDef, async (ps, user) => {
 		throw new Error('incorrect password');
 	}
 
-	// Generate secret
-	const secret = generateUserToken();
+	const newToken = generateUserToken();
 
 	await Users.update(user.id, {
-		token: secret,
+		token: newToken,
 	});
 
 	// Publish event
+	publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken });
 	publishMainStream(user.id, 'myTokenRegenerated');
 
 	// Terminate streaming
diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts
index 22ff2275ca..a1ab06d461 100644
--- a/packages/backend/src/server/api/endpoints/notes/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/delete.ts
@@ -48,7 +48,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		throw e;
 	});
 
-	if (!user.isAdmin && !user.isModerator && (note.userId !== user.id)) {
+	if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) {
 		throw new ApiError(meta.errors.accessDenied);
 	}
 
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index 26aaa0919c..09a8194665 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -2,7 +2,7 @@ import define from '../../define.js';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import { ApiError } from '../../error.js';
 import { makePaginationQuery } from '../../common/make-pagination-query.js';
-import { Notes } from '@/models/index.js';
+import { Notes, Users } from '@/models/index.js';
 import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
 import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js';
 import { activeUsersChart } from '@/services/chart/index.js';
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index 9bcb64b656..7c9c122963 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -2,7 +2,7 @@ import define from '../../define.js';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import { ApiError } from '../../error.js';
 import { makePaginationQuery } from '../../common/make-pagination-query.js';
-import { Followings, Notes } from '@/models/index.js';
+import { Followings, Notes, Users } from '@/models/index.js';
 import { Brackets } from 'typeorm';
 import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
 import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
@@ -56,7 +56,7 @@ export const paramDef = {
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
 	const m = await fetchMeta();
-	if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
+	if (m.disableLocalTimeline && (!user.isAdmin && !user.isModerator)) {
 		throw new ApiError(meta.errors.stlDisabled);
 	}
 
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index 12fc88b1fd..bb0bbe2a20 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -1,7 +1,7 @@
 import define from '../../define.js';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import { ApiError } from '../../error.js';
-import { Notes } from '@/models/index.js';
+import { Notes, Users } from '@/models/index.js';
 import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
 import { makePaginationQuery } from '../../common/make-pagination-query.js';
 import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index 70db12fb14..3555424fa6 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -1,5 +1,4 @@
 import define from '../../define.js';
-import { getNote } from '../../common/getters.js';
 import { ApiError } from '../../error.js';
 import { NoteReactions } from '@/models/index.js';
 import { DeepPartial } from 'typeorm';
diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts
index 7e6b93b39f..e74db8466e 100644
--- a/packages/backend/src/server/api/limiter.ts
+++ b/packages/backend/src/server/api/limiter.ts
@@ -2,12 +2,12 @@ import Limiter from 'ratelimiter';
 import { redisClient } from '../../db/redis.js';
 import { IEndpoint } from './endpoints.js';
 import * as Acct from '@/misc/acct.js';
-import { User } from '@/models/entities/user.js';
+import { CacheableLocalUser, User } from '@/models/entities/user.js';
 import Logger from '@/services/logger.js';
 
 const logger = new Logger('limiter');
 
-export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: User) => new Promise<void>((ok, reject) => {
+export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: CacheableLocalUser) => new Promise<void>((ok, reject) => {
 	const limitation = endpoint.meta.limit;
 
 	const key = Object.prototype.hasOwnProperty.call(limitation, 'key')
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index 90cf59038d..bea863eb7c 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -18,6 +18,11 @@ import { Packed } from '@/misc/schema.js';
 
 //#region Stream type-body definitions
 export interface InternalStreamTypes {
+	userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; };
+	userChangeSilencedState: { id: User['id']; isSilenced: User['isSilenced']; };
+	userChangeModeratorState: { id: User['id']; isModerator: User['isModerator']; };
+	userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; };
+	remoteUserUpdated: { id: User['id']; };
 	antennaCreated: Antenna;
 	antennaDeleted: Antenna;
 	antennaUpdated: Antenna;
diff --git a/packages/backend/src/services/following/reject.ts b/packages/backend/src/services/following/reject.ts
index 3b0cb2ba88..a108d1c1c5 100644
--- a/packages/backend/src/services/following/reject.ts
+++ b/packages/backend/src/services/following/reject.ts
@@ -7,8 +7,17 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
 import { Users, FollowRequests, Followings } from '@/models/index.js';
 import { decrementFollowing } from './delete.js';
 
-type Local = ILocalUser | { id: User['id']; host: User['host']; uri: User['host'] };
-type Remote = IRemoteUser;
+type Local = ILocalUser | {
+	id: ILocalUser['id'];
+	host: ILocalUser['host'];
+	uri: ILocalUser['uri']
+};
+type Remote = IRemoteUser | {
+	id: IRemoteUser['id'];
+	host: IRemoteUser['host'];
+	uri: IRemoteUser['uri'];
+	inbox: IRemoteUser['inbox'];
+};
 type Both = Local | Remote;
 
 /**
diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts
index c3908b2552..17479a85f9 100644
--- a/packages/backend/src/services/messages/create.ts
+++ b/packages/backend/src/services/messages/create.ts
@@ -1,4 +1,4 @@
-import { User } from '@/models/entities/user.js';
+import { CacheableUser, User } from '@/models/entities/user.js';
 import { UserGroup } from '@/models/entities/user-group.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
 import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/index.js';
@@ -13,7 +13,7 @@ import renderCreate from '@/remote/activitypub/renderer/create.js';
 import { renderActivity } from '@/remote/activitypub/renderer/index.js';
 import { deliver } from '@/queue/index.js';
 
-export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) {
+export async function createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) {
 	const message = {
 		id: genId(),
 		createdAt: new Date(),
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index b295534cd2..f4b0d52045 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -38,8 +38,6 @@ import { endedPollNotificationQueue } from '@/queue/queues.js';
 import { Cache } from '@/misc/cache.js';
 import { UserProfile } from '@/models/entities/user-profile.js';
 
-const usersCache = new Cache<MinimumUser>(Infinity);
-
 const mutedWordsCache = new Cache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@@ -212,7 +210,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
 	tags = tags.filter(tag => Array.from(tag || '').length <= 128).splice(0, 32);
 
 	if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) {
-		mentionedUsers.push(await usersCache.fetch(data.reply.userId, () => Users.findOneOrFail(data.reply!.userId)));
+		mentionedUsers.push(await Users.findOneOrFail(data.reply!.userId));
 	}
 
 	if (data.visibility === 'specified') {
@@ -225,7 +223,7 @@ export default async (user: { id: User['id']; username: User['username']; host:
 		}
 
 		if (data.reply && !data.visibleUsers.some(x => x.id === data.reply!.userId)) {
-			data.visibleUsers.push(await usersCache.fetch(data.reply.userId, () => Users.findOneOrFail(data.reply!.userId)));
+			data.visibleUsers.push(await Users.findOneOrFail(data.reply!.userId));
 		}
 	}
 
diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts
index 356dc39727..1caac2b88f 100644
--- a/packages/backend/src/services/note/delete.ts
+++ b/packages/backend/src/services/note/delete.ts
@@ -20,7 +20,7 @@ import { Brackets, In } from 'typeorm';
  * @param user 投稿者
  * @param note 投稿
  */
-export default async function(user: User, note: Note, quiet = false) {
+export default async function(user: { id: User['id']; uri: User['uri']; host: User['host']; }, note: Note, quiet = false) {
 	const deletedAt = new Date();
 
 	// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
@@ -131,7 +131,7 @@ async function getMentionedRemoteUsers(note: Note) {
 	}) as IRemoteUser[];
 }
 
-async function deliverToConcerned(user: ILocalUser, note: Note, content: any) {
+async function deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) {
 	deliverToFollowers(user, content);
 	deliverToRelays(user, content);
 	const remoteUsers = await getMentionedRemoteUsers(note);
diff --git a/packages/backend/src/services/note/polls/vote.ts b/packages/backend/src/services/note/polls/vote.ts
index 9b83b1953f..c758e38572 100644
--- a/packages/backend/src/services/note/polls/vote.ts
+++ b/packages/backend/src/services/note/polls/vote.ts
@@ -1,12 +1,12 @@
 import { publishNoteStream } from '@/services/stream.js';
-import { User } from '@/models/entities/user.js';
+import { CacheableUser, User } from '@/models/entities/user.js';
 import { Note } from '@/models/entities/note.js';
 import { PollVotes, NoteWatchings, Polls, Blockings } from '@/models/index.js';
 import { Not } from 'typeorm';
 import { genId } from '@/misc/gen-id.js';
 import { createNotification } from '../../create-notification.js';
 
-export default async function(user: User, note: Note, choice: number) {
+export default async function(user: CacheableUser, note: Note, choice: number) {
 	const poll = await Polls.findOne(note.id);
 
 	if (poll == null) throw new Error('poll not found');
diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts
index 6f0da503fc..ef04e1edf4 100644
--- a/packages/backend/src/services/relay.ts
+++ b/packages/backend/src/services/relay.ts
@@ -6,9 +6,13 @@ import { deliver } from '@/queue/index.js';
 import { ILocalUser, User } from '@/models/entities/user.js';
 import { Users, Relays } from '@/models/index.js';
 import { genId } from '@/misc/gen-id.js';
+import { Cache } from '@/misc/cache.js';
+import { Relay } from '@/models/entities/relay.js';
 
 const ACTOR_USERNAME = 'relay.actor' as const;
 
+const relaysCache = new Cache<Relay[]>(1000 * 60 * 10);
+
 export async function getRelayActor(): Promise<ILocalUser> {
 	const user = await Users.findOne({
 		host: null,
@@ -78,9 +82,9 @@ export async function relayRejected(id: string) {
 export async function deliverToRelays(user: { id: User['id']; host: null; }, activity: any) {
 	if (activity == null) return;
 
-	const relays = await Relays.find({
+	const relays = await relaysCache.fetch(null, () => Relays.find({
 		status: 'accepted',
-	});
+	}));
 	if (relays.length === 0) return;
 
 	const copy = JSON.parse(JSON.stringify(activity));
diff --git a/packages/backend/src/services/suspend-user.ts b/packages/backend/src/services/suspend-user.ts
index 033311a3cc..57999763cc 100644
--- a/packages/backend/src/services/suspend-user.ts
+++ b/packages/backend/src/services/suspend-user.ts
@@ -5,8 +5,11 @@ import config from '@/config/index.js';
 import { User } from '@/models/entities/user.js';
 import { Users, Followings } from '@/models/index.js';
 import { Not, IsNull } from 'typeorm';
+import { publishInternalEvent } from './stream';
 
 export async function doPostSuspend(user: { id: User['id']; host: User['host'] }) {
+	publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: true });
+
 	if (Users.isLocalUser(user)) {
 		// 知り得る全SharedInboxにDelete配信
 		const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user));
diff --git a/packages/backend/src/services/unsuspend-user.ts b/packages/backend/src/services/unsuspend-user.ts
index 3be081d0ed..b1f0284ac3 100644
--- a/packages/backend/src/services/unsuspend-user.ts
+++ b/packages/backend/src/services/unsuspend-user.ts
@@ -6,8 +6,11 @@ import config from '@/config/index.js';
 import { User } from '@/models/entities/user.js';
 import { Users, Followings } from '@/models/index.js';
 import { Not, IsNull } from 'typeorm';
+import { publishInternalEvent } from './stream';
 
 export async function doPostUnsuspend(user: User) {
+	publishInternalEvent('userChangeSuspendedState', { id: user.id, isSuspended: false });
+
 	if (Users.isLocalUser(user)) {
 		// 知り得る全SharedInboxにUndo Delete配信
 		const content = renderActivity(renderUndo(renderDelete(`${config.url}/users/${user.id}`, user), user));
diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts
new file mode 100644
index 0000000000..4cf3526b70
--- /dev/null
+++ b/packages/backend/src/services/user-cache.ts
@@ -0,0 +1,44 @@
+import { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/models/entities/user.js';
+import { Users } from '@/models/index.js';
+import { Cache } from '@/misc/cache.js';
+import { subsdcriber } from '@/db/redis.js';
+
+export const userByIdCache = new Cache<CacheableUser>(Infinity);
+export const localUserByNativeTokenCache = new Cache<CacheableLocalUser | null>(Infinity);
+export const localUserByIdCache = new Cache<CacheableLocalUser>(Infinity);
+export const uriPersonCache = new Cache<CacheableUser | null>(Infinity);
+
+subsdcriber.on('message', async (_, data) => {
+	const obj = JSON.parse(data);
+
+	if (obj.channel === 'internal') {
+		const { type, body } = obj.message;
+		switch (type) {
+			case 'userChangeSuspendedState':
+			case 'userChangeSilencedState':
+			case 'userChangeModeratorState':
+			case 'remoteUserUpdated': {
+				const user = await Users.findOneOrFail(body.id);
+				userByIdCache.set(user.id, user);
+				for (const [k, v] of uriPersonCache.cache.entries()) {
+					if (v.value?.id === user.id) {
+						uriPersonCache.set(k, user);
+					}
+				}
+				if (Users.isLocalUser(user)) {
+					localUserByNativeTokenCache.set(user.token, user);
+					localUserByIdCache.set(user.id, user);
+				}
+				break;
+			}
+			case 'userTokenRegenerated': {
+				const user = await Users.findOneOrFail(body.id) as ILocalUser;
+				localUserByNativeTokenCache.delete(body.oldToken);
+				localUserByNativeTokenCache.set(body.newToken, user);
+				break;
+			}
+			default:
+				break;
+		}
+	}
+});
diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue
index 8ed29d5c24..e991d725b6 100644
--- a/packages/client/src/pages/settings/profile.vue
+++ b/packages/client/src/pages/settings/profile.vue
@@ -54,7 +54,7 @@
 	</FormSlot>
 
 	<FormSwitch v-model="profile.isCat" class="_formBlock">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></FormSwitch>
-	<FormSwitch v-model="profile.showTimelineReplies" class="_formBlock">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }}</template></FormSwitch>
+	<FormSwitch v-model="profile.showTimelineReplies" class="_formBlock">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></FormSwitch>
 	<FormSwitch v-model="profile.isBot" class="_formBlock">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></FormSwitch>
 
 	<FormSwitch v-model="profile.alwaysMarkNsfw" class="_formBlock">{{ i18n.ts.alwaysMarkSensitive }}</FormSwitch>
diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue
index 4bdc82f601..516ab4d440 100644
--- a/packages/client/src/pages/user-info.vue
+++ b/packages/client/src/pages/user-info.vue
@@ -25,6 +25,7 @@
 				<FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" class="_formBlock" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch>
 				<FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch>
 				<FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch>
+				{{ $ts.reflectMayTakeTime }}
 				<FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton>
 			</FormSection>