diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8612220c86..00c90987d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,13 +5,7 @@
 -
 
 ### Client
-- 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更
-  - 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります
-	  - 猫耳のアバター内部部分をぼかしでマスク表示してより猫耳っぽく見えるように
-		- 猫耳の色がアバター上部のピクセルから決定されます(無効化時はアバター全体の平均色)
-		  - 左耳は上からおよそ 10%, 左からおよそ 20% の位置で決定します
-			- 右耳は上からおよそ 10%, 左からおよそ 80% の位置で決定します
-	- 「UIのアニメーションを減らす」 (`reduceAnimation`) で猫耳を撫でられなくなります
+- 
 
 ### Server
 -
@@ -23,16 +17,24 @@
 ### General
 - チャンネルをお気に入りに登録できるように
 - チャンネルにノートをピン留めできるように
-- アンテナのタイムライン取得時のパフォーマンスを向上
-- チャンネルのタイムライン取得時のパフォーマンスを向上
 
 ### Client
 - 検索ページでURLを入力した際に照会したときと同等の挙動をするように
 - ノートのリアクションを大きく表示するオプションを追加
 - オブジェクトストレージの設定画面を分かりやすく
+- 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更
+  - 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります
+	  - 猫耳のアバター内部部分をぼかしでマスク表示してより猫耳っぽく見えるように
+		- 猫耳の色がアバター上部のピクセルから決定されます(無効化時はアバター全体の平均色)
+		  - 左耳は上からおよそ 10%, 左からおよそ 20% の位置で決定します
+			- 右耳は上からおよそ 10%, 左からおよそ 80% の位置で決定します
+	- 「UIのアニメーションを減らす」 (`reduceAnimation`) で猫耳を撫でられなくなります
 
 ### Server
--
+- ノート作成時のパフォーマンスを向上
+- アンテナのタイムライン取得時のパフォーマンスを向上
+- チャンネルのタイムライン取得時のパフォーマンスを向上
+- 通知に関する全体的なパフォーマンスを向上
 
 ## 13.10.3
 
diff --git a/packages/backend/migration/1680582195041-cleanup.js b/packages/backend/migration/1680582195041-cleanup.js
new file mode 100644
index 0000000000..c587e456a5
--- /dev/null
+++ b/packages/backend/migration/1680582195041-cleanup.js
@@ -0,0 +1,11 @@
+export class cleanup1680582195041 {
+    name = 'cleanup1680582195041'
+
+    async up(queryRunner) {
+			await queryRunner.query(`DROP TABLE "notification" `);
+    }
+
+    async down(queryRunner) {
+        
+    }
+}
diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts
index 1bf0eb918f..7c6808fbd0 100644
--- a/packages/backend/src/core/NoteReadService.ts
+++ b/packages/backend/src/core/NoteReadService.ts
@@ -169,10 +169,6 @@ export class NoteReadService implements OnApplicationShutdown {
 					this.globalEventService.publishMainStream(userId, 'readAllChannels');
 				}
 			});
-	
-			this.notificationService.readNotificationByQuery(userId, {
-				noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
-			});
 		}
 	}
 
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index 48f2c65847..2a4dbba6a4 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -1,8 +1,9 @@
 import { setTimeout } from 'node:timers/promises';
+import Redis from 'ioredis';
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
 import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { MutingsRepository, NotificationsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
+import type { MutingsRepository, 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';
@@ -17,15 +18,15 @@ export class NotificationService implements OnApplicationShutdown {
 	#shutdownController = new AbortController();
 
 	constructor(
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
 		@Inject(DI.userProfilesRepository)
 		private userProfilesRepository: UserProfilesRepository,
 
-		@Inject(DI.notificationsRepository)
-		private notificationsRepository: NotificationsRepository,
-
 		@Inject(DI.mutingsRepository)
 		private mutingsRepository: MutingsRepository,
 
@@ -38,50 +39,31 @@ export class NotificationService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public async readNotification(
+	public async readAllNotification(
 		userId: User['id'],
-		notificationIds: Notification['id'][],
 	) {
-		if (notificationIds.length === 0) return;
+		const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
+		
+		const latestNotificationIdsRes = await this.redisClient.xrevrange(
+			`notificationTimeline:${userId}`,
+			'+',
+			'-',
+			'COUNT', 1);
+		console.log('latestNotificationIdsRes', latestNotificationIdsRes);
+		const latestNotificationId = latestNotificationIdsRes[0]?.[0];
 
-		// Update documents
-		const result = await this.notificationsRepository.update({
-			notifieeId: userId,
-			id: In(notificationIds),
-			isRead: false,
-		}, {
-			isRead: true,
-		});
+		if (latestNotificationId == null) return;
 
-		if (result.affected === 0) return;
+		this.redisClient.set(`latestReadNotification:${userId}`, latestNotificationId);
 
-		if (!await this.userEntityService.getHasUnreadNotification(userId)) return this.postReadAllNotifications(userId);
-		else return this.postReadNotifications(userId, notificationIds);
-	}
-
-	@bindThis
-	public async readNotificationByQuery(
-		userId: User['id'],
-		query: Record<string, any>,
-	) {
-		const notificationIds = await this.notificationsRepository.findBy({
-			...query,
-			notifieeId: userId,
-			isRead: false,
-		}).then(notifications => notifications.map(notification => notification.id));
-
-		return this.readNotification(userId, notificationIds);
+		if (latestReadNotificationId == null || (latestReadNotificationId < latestNotificationId)) {
+			return this.postReadAllNotifications(userId);
+		}
 	}
 
 	@bindThis
 	private postReadAllNotifications(userId: User['id']) {
 		this.globalEventService.publishMainStream(userId, 'readAllNotifications');
-		return this.pushNotificationService.pushNotification(userId, 'readAllNotifications', undefined);
-	}
-
-	@bindThis
-	private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
-		return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds });
 	}
 
 	@bindThis
@@ -90,45 +72,48 @@ export class NotificationService implements OnApplicationShutdown {
 		type: Notification['type'],
 		data: Partial<Notification>,
 	): Promise<Notification | null> {
-		if (data.notifierId && (notifieeId === data.notifierId)) {
-			return null;
+		// TODO: Cache
+		const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId });
+		const isMuted = profile?.mutingNotificationTypes.includes(type);
+		if (isMuted) return null;
+
+		if (data.notifierId) {
+			if (notifieeId === data.notifierId) {
+				return null;
+			}
+
+			// TODO: cache
+			const mutings = await this.mutingsRepository.findOneBy({
+				muterId: notifieeId,
+				muteeId: data.notifierId,
+			});
+			if (mutings) {
+				return null;
+			}
 		}
 
-		const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId });
-
-		const isMuted = profile?.mutingNotificationTypes.includes(type);
-
-		// Create notification
-		const notification = await this.notificationsRepository.insert({
+		const notification = {
 			id: this.idService.genId(),
 			createdAt: new Date(),
-			notifieeId: notifieeId,
 			type: type,
-			// 相手がこの通知をミュートしているようなら、既読を予めつけておく
-			isRead: isMuted,
 			...data,
-		} as Partial<Notification>)
-			.then(x => this.notificationsRepository.findOneByOrFail(x.identifiers[0]));
+		} as Notification;
 
-		const packed = await this.notificationEntityService.pack(notification, {});
+		this.redisClient.xadd(
+			`notificationTimeline:${notifieeId}`,
+			'MAXLEN', '~', '300',
+			`${this.idService.parse(notification.id).date.getTime()}-*`,
+			'data', JSON.stringify(notification));
+
+		const packed = await this.notificationEntityService.pack(notification, notifieeId, {});
 
 		// Publish notification event
 		this.globalEventService.publishMainStream(notifieeId, 'notification', packed);
 
 		// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
-		setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
-			const fresh = await this.notificationsRepository.findOneBy({ id: notification.id });
-			if (fresh == null) return; // 既に削除されているかもしれない
-			if (fresh.isRead) return;
-
-			//#region ただしミュートしているユーザーからの通知なら無視
-			const mutings = await this.mutingsRepository.findBy({
-				muterId: notifieeId,
-			});
-			if (data.notifierId && mutings.map(m => m.muteeId).includes(data.notifierId)) {
-				return;
-			}
-			//#endregion
+		setTimeout(2000, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
+			const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`);
+			if (latestReadNotificationId && (latestReadNotificationId >= notification.id)) return;
 
 			this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
 			this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts
index 32c38ad480..69020f7e84 100644
--- a/packages/backend/src/core/PushNotificationService.ts
+++ b/packages/backend/src/core/PushNotificationService.ts
@@ -15,10 +15,6 @@ type PushNotificationsTypes = {
 		antenna: { id: string, name: string };
 		note: Packed<'Note'>;
 	};
-	'readNotifications': { notificationIds: string[] };
-	'readAllNotifications': undefined;
-	'readAntenna': { antennaId: string };
-	'readAllAntennas': undefined;
 };
 
 // Reduce length because push message servers have character limits
@@ -72,14 +68,6 @@ export class PushNotificationService {
 		});
 	
 		for (const subscription of subscriptions) {
-			// Continue if sendReadMessage is false
-			if ([
-				'readNotifications',
-				'readAllNotifications',
-				'readAntenna',
-				'readAllAntennas',
-			].includes(type) && !subscription.sendReadMessage) continue;
-
 			const pushSubscription = {
 				endpoint: subscription.endpoint,
 				keys: {
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 70e56cb3d7..7cffb8d568 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -1,7 +1,8 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { ModuleRef } from '@nestjs/core';
+import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { AccessTokensRepository, NoteReactionsRepository, NotificationsRepository, User } from '@/models/index.js';
+import type { AccessTokensRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository } from '@/models/index.js';
 import { awaitAll } from '@/misc/prelude/await-all.js';
 import type { Notification } from '@/models/entities/Notification.js';
 import type { Note } from '@/models/entities/Note.js';
@@ -25,8 +26,11 @@ export class NotificationEntityService implements OnModuleInit {
 	constructor(
 		private moduleRef: ModuleRef,
 
-		@Inject(DI.notificationsRepository)
-		private notificationsRepository: NotificationsRepository,
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
 
 		@Inject(DI.noteReactionsRepository)
 		private noteReactionsRepository: NoteReactionsRepository,
@@ -48,30 +52,39 @@ export class NotificationEntityService implements OnModuleInit {
 
 	@bindThis
 	public async pack(
-		src: Notification['id'] | Notification,
+		src: Notification,
+		meId: User['id'],
 		options: {
-			_hint_?: {
-				packedNotes: Map<Note['id'], Packed<'Note'>>;
-			};
+			
+		},
+		hint?: {
+			packedNotes: Map<Note['id'], Packed<'Note'>>;
+			packedUsers: Map<User['id'], Packed<'User'>>;
 		},
 	): Promise<Packed<'Notification'>> {
-		const notification = typeof src === 'object' ? src : await this.notificationsRepository.findOneByOrFail({ id: src });
+		const notification = src;
 		const token = notification.appAccessTokenId ? await this.accessTokensRepository.findOneByOrFail({ id: notification.appAccessTokenId }) : null;
 		const noteIfNeed = NOTE_REQUIRED_NOTIFICATION_TYPES.has(notification.type) && notification.noteId != null ? (
-			options._hint_?.packedNotes != null
-				? options._hint_.packedNotes.get(notification.noteId)
-				: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, {
+			hint?.packedNotes != null
+				? hint.packedNotes.get(notification.noteId)
+				: this.noteEntityService.pack(notification.noteId!, { id: meId }, {
 					detail: true,
 				})
 		) : undefined;
+		const userIfNeed = notification.notifierId != null ? (
+			hint?.packedUsers != null
+				? hint.packedUsers.get(notification.notifierId)
+				: this.userEntityService.pack(notification.notifierId!, { id: meId }, {
+					detail: false,
+				})
+		) : undefined;
 
 		return await awaitAll({
 			id: notification.id,
-			createdAt: notification.createdAt.toISOString(),
+			createdAt: new Date(notification.createdAt).toISOString(),
 			type: notification.type,
-			isRead: notification.isRead,
 			userId: notification.notifierId,
-			user: notification.notifierId ? this.userEntityService.pack(notification.notifier ?? notification.notifierId) : null,
+			...(userIfNeed != null ? { user: userIfNeed } : {}),
 			...(noteIfNeed != null ? { note: noteIfNeed } : {}),
 			...(notification.type === 'reaction' ? {
 				reaction: notification.reaction,
@@ -87,33 +100,36 @@ export class NotificationEntityService implements OnModuleInit {
 		});
 	}
 
-	/**
-	 * @param notifications you should join "note" property when fetch from DB, and all notifieeId should be same as meId
-	 */
 	@bindThis
 	public async packMany(
 		notifications: Notification[],
 		meId: User['id'],
 	) {
 		if (notifications.length === 0) return [];
-		
-		for (const notification of notifications) {
-			if (meId !== notification.notifieeId) {
-				// because we call note packMany with meId, all notifieeId should be same as meId
-				throw new Error('TRY_TO_PACK_ANOTHER_USER_NOTIFICATION');
-			}
-		}
 
-		const notes = notifications.map(x => x.note).filter(isNotNull);
+		const noteIds = notifications.map(x => x.noteId).filter(isNotNull);
+		const notes = noteIds.length > 0 ? await this.notesRepository.find({
+			where: { id: In(noteIds) },
+			relations: ['user', 'user.avatar', 'user.banner', 'reply', 'reply.user', 'reply.user.avatar', 'reply.user.banner', 'renote', 'renote.user', 'renote.user.avatar', 'renote.user.banner'],
+		}) : [];
 		const packedNotesArray = await this.noteEntityService.packMany(notes, { id: meId }, {
 			detail: true,
 		});
 		const packedNotes = new Map(packedNotesArray.map(p => [p.id, p]));
 
-		return await Promise.all(notifications.map(x => this.pack(x, {
-			_hint_: {
-				packedNotes,
-			},
+		const userIds = notifications.map(x => x.notifierId).filter(isNotNull);
+		const users = userIds.length > 0 ? await this.usersRepository.find({
+			where: { id: In(userIds) },
+			relations: ['avatar', 'banner'],
+		}) : [];
+		const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, {
+			detail: false,
+		});
+		const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
+
+		return await Promise.all(notifications.map(x => this.pack(x, meId, {}, {
+			packedNotes,
+			packedUsers,
 		})));
 	}
 }
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 61fd6f2f66..ae7c47a990 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -1,5 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { In, Not } from 'typeorm';
+import Redis from 'ioredis';
 import Ajv from 'ajv';
 import { ModuleRef } from '@nestjs/core';
 import { DI } from '@/di-symbols.js';
@@ -12,7 +13,7 @@ import { KVCache } from '@/misc/cache.js';
 import type { Instance } from '@/models/entities/Instance.js';
 import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
 import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
-import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js';
+import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js';
 import { bindThis } from '@/decorators.js';
 import { RoleService } from '@/core/RoleService.js';
 import type { OnModuleInit } from '@nestjs/common';
@@ -60,6 +61,9 @@ export class UserEntityService implements OnModuleInit {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -90,9 +94,6 @@ export class UserEntityService implements OnModuleInit {
 		@Inject(DI.channelFollowingsRepository)
 		private channelFollowingsRepository: ChannelFollowingsRepository,
 
-		@Inject(DI.notificationsRepository)
-		private notificationsRepository: NotificationsRepository,
-
 		@Inject(DI.userNotePiningsRepository)
 		private userNotePiningsRepository: UserNotePiningsRepository,
 
@@ -247,21 +248,17 @@ export class UserEntityService implements OnModuleInit {
 
 	@bindThis
 	public async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
-		const mute = await this.mutingsRepository.findBy({
-			muterId: userId,
-		});
-		const mutedUserIds = mute.map(m => m.muteeId);
+		const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
+		
+		const latestNotificationIdsRes = await this.redisClient.xrevrange(
+			`notificationTimeline:${userId}`,
+			'+',
+			'-',
+			'COUNT', 1);
+		console.log('latestNotificationIdsRes', latestNotificationIdsRes);
+		const latestNotificationId = latestNotificationIdsRes[0]?.[0];
 
-		const count = await this.notificationsRepository.count({
-			where: {
-				notifieeId: userId,
-				...(mutedUserIds.length > 0 ? { notifierId: Not(In(mutedUserIds)) } : {}),
-				isRead: false,
-			},
-			take: 1,
-		});
-
-		return count > 0;
+		return latestNotificationId != null && (latestReadNotificationId == null || latestReadNotificationId < latestNotificationId);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index f2ab6cb864..56ce755a1a 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -33,7 +33,6 @@ export const DI = {
 	emojisRepository: Symbol('emojisRepository'),
 	driveFilesRepository: Symbol('driveFilesRepository'),
 	driveFoldersRepository: Symbol('driveFoldersRepository'),
-	notificationsRepository: Symbol('notificationsRepository'),
 	metasRepository: Symbol('metasRepository'),
 	mutingsRepository: Symbol('mutingsRepository'),
 	renoteMutingsRepository: Symbol('renoteMutingsRepository'),
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index b74ee3689c..7be7b81904 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -1,6 +1,6 @@
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
+import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
 import type { DataSource } from 'typeorm';
 import type { Provider } from '@nestjs/common';
 
@@ -172,12 +172,6 @@ const $driveFoldersRepository: Provider = {
 	inject: [DI.db],
 };
 
-const $notificationsRepository: Provider = {
-	provide: DI.notificationsRepository,
-	useFactory: (db: DataSource) => db.getRepository(Notification),
-	inject: [DI.db],
-};
-
 const $metasRepository: Provider = {
 	provide: DI.metasRepository,
 	useFactory: (db: DataSource) => db.getRepository(Meta),
@@ -426,7 +420,6 @@ const $roleAssignmentsRepository: Provider = {
 		$emojisRepository,
 		$driveFilesRepository,
 		$driveFoldersRepository,
-		$notificationsRepository,
 		$metasRepository,
 		$mutingsRepository,
 		$renoteMutingsRepository,
@@ -493,7 +486,6 @@ const $roleAssignmentsRepository: Provider = {
 		$emojisRepository,
 		$driveFilesRepository,
 		$driveFoldersRepository,
-		$notificationsRepository,
 		$metasRepository,
 		$mutingsRepository,
 		$renoteMutingsRepository,
diff --git a/packages/backend/src/models/entities/Notification.ts b/packages/backend/src/models/entities/Notification.ts
index 51117efba5..aa6f997124 100644
--- a/packages/backend/src/models/entities/Notification.ts
+++ b/packages/backend/src/models/entities/Notification.ts
@@ -1,54 +1,19 @@
-import { Entity, Index, JoinColumn, ManyToOne, Column, PrimaryColumn } from 'typeorm';
-import { notificationTypes, obsoleteNotificationTypes } from '@/types.js';
-import { id } from '../id.js';
+import { notificationTypes } from '@/types.js';
 import { User } from './User.js';
 import { Note } from './Note.js';
 import { FollowRequest } from './FollowRequest.js';
 import { AccessToken } from './AccessToken.js';
 
-@Entity()
-export class Notification {
-	@PrimaryColumn(id())
-	public id: string;
+export type Notification = {
+	id: string;
 
-	@Index()
-	@Column('timestamp with time zone', {
-		comment: 'The created date of the Notification.',
-	})
-	public createdAt: Date;
-
-	/**
-	 * 通知の受信者
-	 */
-	@Index()
-	@Column({
-		...id(),
-		comment: 'The ID of recipient user of the Notification.',
-	})
-	public notifieeId: User['id'];
-
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
-	})
-	@JoinColumn()
-	public notifiee: User | null;
+	// RedisのためDateではなくstring
+	createdAt: string;
 
 	/**
 	 * 通知の送信者(initiator)
 	 */
-	@Index()
-	@Column({
-		...id(),
-		nullable: true,
-		comment: 'The ID of sender user of the Notification.',
-	})
-	public notifierId: User['id'] | null;
-
-	@ManyToOne(type => User, {
-		onDelete: 'CASCADE',
-	})
-	@JoinColumn()
-	public notifier: User | null;
+	notifierId: User['id'] | null;
 
 	/**
 	 * 通知の種類。
@@ -64,104 +29,37 @@ export class Notification {
 	 * achievementEarned - 実績を獲得
 	 * app - アプリ通知
 	 */
-	@Index()
-	@Column('enum', {
-		enum: [
-			...notificationTypes,
-			...obsoleteNotificationTypes,
-		],
-		comment: 'The type of the Notification.',
-	})
-	public type: typeof notificationTypes[number];
+	type: typeof notificationTypes[number];
 
-	/**
-	 * 通知が読まれたかどうか
-	 */
-	@Index()
-	@Column('boolean', {
-		default: false,
-		comment: 'Whether the Notification is read.',
-	})
-	public isRead: boolean;
+	noteId: Note['id'] | null;
 
-	@Column({
-		...id(),
-		nullable: true,
-	})
-	public noteId: Note['id'] | null;
+	followRequestId: FollowRequest['id'] | null;
 
-	@ManyToOne(type => Note, {
-		onDelete: 'CASCADE',
-	})
-	@JoinColumn()
-	public note: Note | null;
+	reaction: string | null;
 
-	@Column({
-		...id(),
-		nullable: true,
-	})
-	public followRequestId: FollowRequest['id'] | null;
+	choice: number | null;
 
-	@ManyToOne(type => FollowRequest, {
-		onDelete: 'CASCADE',
-	})
-	@JoinColumn()
-	public followRequest: FollowRequest | null;
-
-	@Column('varchar', {
-		length: 128, nullable: true,
-	})
-	public reaction: string | null;
-
-	@Column('integer', {
-		nullable: true,
-	})
-	public choice: number | null;
-
-	@Column('varchar', {
-		length: 128, nullable: true,
-	})
-	public achievement: string | null;
+	achievement: string | null;
 
 	/**
 	 * アプリ通知のbody
 	 */
-	@Column('varchar', {
-		length: 2048, nullable: true,
-	})
-	public customBody: string | null;
+	customBody: string | null;
 
 	/**
 	 * アプリ通知のheader
 	 * (省略時はアプリ名で表示されることを期待)
 	 */
-	@Column('varchar', {
-		length: 256, nullable: true,
-	})
-	public customHeader: string | null;
+	customHeader: string | null;
 
 	/**
 	 * アプリ通知のicon(URL)
 	 * (省略時はアプリアイコンで表示されることを期待)
 	 */
-	@Column('varchar', {
-		length: 1024, nullable: true,
-	})
-	public customIcon: string | null;
+	customIcon: string | null;
 
 	/**
 	 * アプリ通知のアプリ(のトークン)
 	 */
-	@Index()
-	@Column({
-		...id(),
-		nullable: true,
-	})
-	public appAccessTokenId: AccessToken['id'] | null;
-
-	@ManyToOne(type => AccessToken, {
-		onDelete: 'CASCADE',
-	})
-	@JoinColumn()
-	public appAccessToken: AccessToken | null;
+	appAccessTokenId: AccessToken['id'] | null;
 }
diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts
index c4c9717ed5..48d6e15f2a 100644
--- a/packages/backend/src/models/index.ts
+++ b/packages/backend/src/models/index.ts
@@ -32,7 +32,6 @@ import { NoteFavorite } from '@/models/entities/NoteFavorite.js';
 import { NoteReaction } from '@/models/entities/NoteReaction.js';
 import { NoteThreadMuting } from '@/models/entities/NoteThreadMuting.js';
 import { NoteUnread } from '@/models/entities/NoteUnread.js';
-import { Notification } from '@/models/entities/Notification.js';
 import { Page } from '@/models/entities/Page.js';
 import { PageLike } from '@/models/entities/PageLike.js';
 import { PasswordResetRequest } from '@/models/entities/PasswordResetRequest.js';
@@ -100,7 +99,6 @@ export {
 	NoteReaction,
 	NoteThreadMuting,
 	NoteUnread,
-	Notification,
 	Page,
 	PageLike,
 	PasswordResetRequest,
@@ -167,7 +165,6 @@ export type NoteFavoritesRepository = Repository<NoteFavorite>;
 export type NoteReactionsRepository = Repository<NoteReaction>;
 export type NoteThreadMutingsRepository = Repository<NoteThreadMuting>;
 export type NoteUnreadsRepository = Repository<NoteUnread>;
-export type NotificationsRepository = Repository<Notification>;
 export type PagesRepository = Repository<Page>;
 export type PageLikesRepository = Repository<PageLike>;
 export type PasswordResetRequestsRepository = Repository<PasswordResetRequest>;
diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts
index d3f2405cdd..e88ca61ba0 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -14,10 +14,6 @@ export const packedNotificationSchema = {
 			optional: false, nullable: false,
 			format: 'date-time',
 		},
-		isRead: {
-			type: 'boolean',
-			optional: false, nullable: false,
-		},
 		type: {
 			type: 'string',
 			optional: false, nullable: false,
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 024aa114fc..efeca46b49 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -40,7 +40,6 @@ import { NoteFavorite } from '@/models/entities/NoteFavorite.js';
 import { NoteReaction } from '@/models/entities/NoteReaction.js';
 import { NoteThreadMuting } from '@/models/entities/NoteThreadMuting.js';
 import { NoteUnread } from '@/models/entities/NoteUnread.js';
-import { Notification } from '@/models/entities/Notification.js';
 import { Page } from '@/models/entities/Page.js';
 import { PageLike } from '@/models/entities/PageLike.js';
 import { PasswordResetRequest } from '@/models/entities/PasswordResetRequest.js';
@@ -155,7 +154,6 @@ export const entities = [
 	DriveFolder,
 	Poll,
 	PollVote,
-	Notification,
 	Emoji,
 	Hashtag,
 	SwSubscription,
diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts
index 3feb86f86f..1936e8df23 100644
--- a/packages/backend/src/queue/processors/CleanProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanProcessorService.ts
@@ -1,7 +1,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { In, LessThan } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { AntennasRepository, MutedNotesRepository, NotificationsRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js';
+import type { AntennasRepository, MutedNotesRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
@@ -20,9 +20,6 @@ export class CleanProcessorService {
 		@Inject(DI.userIpsRepository)
 		private userIpsRepository: UserIpsRepository,
 
-		@Inject(DI.notificationsRepository)
-		private notificationsRepository: NotificationsRepository,
-
 		@Inject(DI.mutedNotesRepository)
 		private mutedNotesRepository: MutedNotesRepository,
 
@@ -46,10 +43,6 @@ export class CleanProcessorService {
 			createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))),
 		});
 
-		this.notificationsRepository.delete({
-			createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))),
-		});
-
 		this.mutedNotesRepository.delete({
 			id: LessThan(this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90)))),
 			reason: 'word',
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index f39643abeb..cab2477414 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -268,7 +268,6 @@ import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
 import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
 import * as ep___notifications_create from './endpoints/notifications/create.js';
 import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_read from './endpoints/notifications/read.js';
 import * as ep___pagePush from './endpoints/page-push.js';
 import * as ep___pages_create from './endpoints/pages/create.js';
 import * as ep___pages_delete from './endpoints/pages/delete.js';
@@ -600,7 +599,6 @@ const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep__
 const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
 const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
 const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
-const $notifications_read: Provider = { provide: 'ep:notifications/read', useClass: ep___notifications_read.default };
 const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
 const $pages_create: Provider = { provide: 'ep:pages/create', useClass: ep___pages_create.default };
 const $pages_delete: Provider = { provide: 'ep:pages/delete', useClass: ep___pages_delete.default };
@@ -936,7 +934,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$notes_userListTimeline,
 		$notifications_create,
 		$notifications_markAllAsRead,
-		$notifications_read,
 		$pagePush,
 		$pages_create,
 		$pages_delete,
@@ -1266,7 +1263,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$notes_userListTimeline,
 		$notifications_create,
 		$notifications_markAllAsRead,
-		$notifications_read,
 		$pagePush,
 		$pages_create,
 		$pages_delete,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 16b20c1a4d..e33c2349cd 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -268,7 +268,6 @@ import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
 import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
 import * as ep___notifications_create from './endpoints/notifications/create.js';
 import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
-import * as ep___notifications_read from './endpoints/notifications/read.js';
 import * as ep___pagePush from './endpoints/page-push.js';
 import * as ep___pages_create from './endpoints/pages/create.js';
 import * as ep___pages_delete from './endpoints/pages/delete.js';
@@ -598,7 +597,6 @@ const eps = [
 	['notes/user-list-timeline', ep___notes_userListTimeline],
 	['notifications/create', ep___notifications_create],
 	['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
-	['notifications/read', ep___notifications_read],
 	['page-push', ep___pagePush],
 	['pages/create', ep___pages_create],
 	['pages/delete', ep___pages_delete],
diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
index 3ad6c7c484..770b61850a 100644
--- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository, FollowingsRepository, NotificationsRepository } from '@/models/index.js';
+import type { UsersRepository, FollowingsRepository } from '@/models/index.js';
 import type { User } from '@/models/entities/User.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
@@ -36,9 +36,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		@Inject(DI.followingsRepository)
 		private followingsRepository: FollowingsRepository,
 
-		@Inject(DI.notificationsRepository)
-		private notificationsRepository: NotificationsRepository,
-
 		private userEntityService: UserEntityService,
 		private userFollowingService: UserFollowingService,
 		private userSuspendService: UserSuspendService,
@@ -73,7 +70,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			(async () => {
 				await this.userSuspendService.doPostSuspend(user).catch(e => {});
 				await this.unFollowAll(user).catch(e => {});
-				await this.readAllNotify(user).catch(e => {});
 			})();
 		});
 	}
@@ -96,14 +92,4 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			await this.userFollowingService.unfollow(follower, followee, true);
 		}
 	}
-	
-	@bindThis
-	private async readAllNotify(notifier: User) {
-		await this.notificationsRepository.update({
-			notifierId: notifier.id,
-			isRead: false,
-		}, {
-			isRead: true,
-		});
-	}
 }
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index e3897d38bd..f27b4e86d4 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -1,6 +1,7 @@
-import { Brackets } from 'typeorm';
+import { Brackets, In } from 'typeorm';
+import Redis from 'ioredis';
 import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js';
+import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js';
 import { obsoleteNotificationTypes, notificationTypes } from '@/types.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueryService } from '@/core/QueryService.js';
@@ -8,6 +9,8 @@ import { NoteReadService } from '@/core/NoteReadService.js';
 import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
 import { NotificationService } from '@/core/NotificationService.js';
 import { DI } from '@/di-symbols.js';
+import { IdService } from '@/core/IdService.js';
+import { Notification } from '@/models/entities/Notification.js';
 
 export const meta = {
 	tags: ['account', 'notifications'],
@@ -38,8 +41,6 @@ export const paramDef = {
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
 		untilId: { type: 'string', format: 'misskey:id' },
-		following: { type: 'boolean', default: false },
-		unreadOnly: { type: 'boolean', default: false },
 		markAsRead: { type: 'boolean', default: true },
 		// 後方互換のため、廃止された通知タイプも受け付ける
 		includeTypes: { type: 'array', items: {
@@ -56,21 +57,22 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> {
 	constructor(
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
-		@Inject(DI.followingsRepository)
-		private followingsRepository: FollowingsRepository,
-
 		@Inject(DI.mutingsRepository)
 		private mutingsRepository: MutingsRepository,
 
 		@Inject(DI.userProfilesRepository)
 		private userProfilesRepository: UserProfilesRepository,
 
-		@Inject(DI.notificationsRepository)
-		private notificationsRepository: NotificationsRepository,
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
 
+		private idService: IdService,
 		private notificationEntityService: NotificationEntityService,
 		private notificationService: NotificationService,
 		private queryService: QueryService,
@@ -89,85 +91,39 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
 			const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
 
-			const followingQuery = this.followingsRepository.createQueryBuilder('following')
-				.select('following.followeeId')
-				.where('following.followerId = :followerId', { followerId: me.id });
+			const notificationsRes = await this.redisClient.xrevrange(
+				`notificationTimeline:${me.id}`,
+				ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
+				'-',
+				'COUNT', ps.limit + 1); // untilIdに指定したものも含まれるため+1
 
-			const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
-				.select('muting.muteeId')
-				.where('muting.muterId = :muterId', { muterId: me.id });
-
-			const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile')
-				.select('user_profile.mutedInstances')
-				.where('user_profile.userId = :muterId', { muterId: me.id });
-
-			const suspendedQuery = this.usersRepository.createQueryBuilder('users')
-				.select('users.id')
-				.where('users.isSuspended = TRUE');
-
-			const query = this.queryService.makePaginationQuery(this.notificationsRepository.createQueryBuilder('notification'), ps.sinceId, ps.untilId)
-				.andWhere('notification.notifieeId = :meId', { meId: me.id })
-				.leftJoinAndSelect('notification.notifier', 'notifier')
-				.leftJoinAndSelect('notification.note', 'note')
-				.leftJoinAndSelect('notifier.avatar', 'notifierAvatar')
-				.leftJoinAndSelect('notifier.banner', 'notifierBanner')
-				.leftJoinAndSelect('note.user', 'user')
-				.leftJoinAndSelect('user.avatar', 'avatar')
-				.leftJoinAndSelect('user.banner', 'banner')
-				.leftJoinAndSelect('note.reply', 'reply')
-				.leftJoinAndSelect('note.renote', 'renote')
-				.leftJoinAndSelect('reply.user', 'replyUser')
-				.leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
-				.leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
-				.leftJoinAndSelect('renote.user', 'renoteUser')
-				.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
-				.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
-
-			// muted users
-			query.andWhere(new Brackets(qb => { qb
-				.where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`)
-				.orWhere('notification.notifierId IS NULL');
-			}));
-			query.setParameters(mutingQuery.getParameters());
-
-			// muted instances
-			query.andWhere(new Brackets(qb => { qb
-				.andWhere('notifier.host IS NULL')
-				.orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
-			}));
-			query.setParameters(mutingInstanceQuery.getParameters());
-
-			// suspended users
-			query.andWhere(new Brackets(qb => { qb
-				.where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
-				.orWhere('notification.notifierId IS NULL');
-			}));
-
-			if (ps.following) {
-				query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: me.id });
-				query.setParameters(followingQuery.getParameters());
+			if (notificationsRes.length === 0) {
+				return [];
 			}
 
+			let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId) as Notification[];
+
 			if (includeTypes && includeTypes.length > 0) {
-				query.andWhere('notification.type IN (:...includeTypes)', { includeTypes });
+				notifications = notifications.filter(notification => includeTypes.includes(notification.type));
 			} else if (excludeTypes && excludeTypes.length > 0) {
-				query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes });
+				notifications = notifications.filter(notification => !excludeTypes.includes(notification.type));
 			}
 
-			if (ps.unreadOnly) {
-				query.andWhere('notification.isRead = false');
+			if (notifications.length === 0) {
+				return [];
 			}
 
-			const notifications = await query.take(ps.limit).getMany();
-
 			// Mark all as read
-			if (notifications.length > 0 && ps.markAsRead) {
-				this.notificationService.readNotification(me.id, notifications.map(x => x.id));
+			if (ps.markAsRead) {
+				this.notificationService.readAllNotification(me.id);
 			}
 
-			const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!);
+			const noteIds = notifications
+				.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type))
+				.map(notification => notification.noteId!);
 
-			if (notes.length > 0) {
+			if (noteIds.length > 0) {
+				const notes = await this.notesRepository.findBy({ id: In(noteIds) });
 				this.noteReadService.read(me.id, notes);
 			}
 
diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
index 09134cf48f..9ba6079189 100644
--- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
@@ -1,9 +1,7 @@
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotificationsRepository } from '@/models/index.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { PushNotificationService } from '@/core/PushNotificationService.js';
 import { DI } from '@/di-symbols.js';
+import { NotificationService } from '@/core/NotificationService.js';
 
 export const meta = {
 	tags: ['notifications', 'account'],
@@ -23,24 +21,10 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> {
 	constructor(
-		@Inject(DI.notificationsRepository)
-		private notificationsRepository: NotificationsRepository,
-
-		private globalEventService: GlobalEventService,
-		private pushNotificationService: PushNotificationService,
+		private notificationService: NotificationService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			// Update documents
-			await this.notificationsRepository.update({
-				notifieeId: me.id,
-				isRead: false,
-			}, {
-				isRead: true,
-			});
-
-			// 全ての通知を読みましたよというイベントを発行
-			this.globalEventService.publishMainStream(me.id, 'readAllNotifications');
-			this.pushNotificationService.pushNotification(me.id, 'readAllNotifications', undefined);
+			this.notificationService.readAllNotification(me.id);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts
deleted file mode 100644
index 6262c47fd0..0000000000
--- a/packages/backend/src/server/api/endpoints/notifications/read.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Injectable } from '@nestjs/common';
-import { Endpoint } from '@/server/api/endpoint-base.js';
-import { NotificationService } from '@/core/NotificationService.js';
-
-export const meta = {
-	tags: ['notifications', 'account'],
-
-	requireCredential: true,
-
-	kind: 'write:notifications',
-
-	description: 'Mark a notification as read.',
-
-	errors: {
-		noSuchNotification: {
-			message: 'No such notification.',
-			code: 'NO_SUCH_NOTIFICATION',
-			id: 'efa929d5-05b5-47d1-beec-e6a4dbed011e',
-		},
-	},
-} as const;
-
-export const paramDef = {
-	oneOf: [
-		{
-			type: 'object',
-			properties: {
-				notificationId: { type: 'string', format: 'misskey:id' },
-			},
-			required: ['notificationId'],
-		},
-		{
-			type: 'object',
-			properties: {
-				notificationIds: {
-					type: 'array',
-					items: { type: 'string', format: 'misskey:id' },
-					maxItems: 100,
-				},
-			},
-			required: ['notificationIds'],
-		},
-	],
-} as const;
-
-// eslint-disable-next-line import/no-default-export
-@Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
-	constructor(
-		private notificationService: NotificationService,
-	) {
-		super(meta, paramDef, async (ps, me) => {
-			if ('notificationId' in ps) return this.notificationService.readNotification(me.id, [ps.notificationId]);
-			return this.notificationService.readNotification(me.id, ps.notificationIds);
-		});
-	}
-}
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index 7c6eb9a20a..f1f8bfd3a2 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -195,8 +195,7 @@ export default class Connection {
 
 	@bindThis
 	private onReadNotification(payload: any) {
-		if (!payload.id) return;
-		this.notificationService.readNotification(this.user!.id, [payload.id]);
+		this.notificationService.readAllNotification(this.user!.id);
 	}
 
 	/**
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index b60967de02..efae687e66 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -83,7 +83,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, onMounted, onUnmounted, watch } from 'vue';
+import { ref, shallowRef } from 'vue';
 import * as misskey from 'misskey-js';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
@@ -94,7 +94,6 @@ import { notePage } from '@/filters/note';
 import { userPage } from '@/filters/user';
 import { i18n } from '@/i18n';
 import * as os from '@/os';
-import { stream } from '@/stream';
 import { useTooltip } from '@/scripts/use-tooltip';
 import { $i } from '@/account';
 
@@ -110,35 +109,6 @@ const props = withDefaults(defineProps<{
 const elRef = shallowRef<HTMLElement>(null);
 const reactionRef = ref(null);
 
-let readObserver: IntersectionObserver | undefined;
-let connection;
-
-onMounted(() => {
-	if (!props.notification.isRead) {
-		readObserver = new IntersectionObserver((entries, observer) => {
-			if (!entries.some(entry => entry.isIntersecting)) return;
-			stream.send('readNotification', {
-				id: props.notification.id,
-			});
-			observer.disconnect();
-		});
-
-		readObserver.observe(elRef.value);
-
-		connection = stream.useChannel('main');
-		connection.on('readAllNotifications', () => readObserver.disconnect());
-
-		watch(props.notification.isRead, () => {
-			readObserver.disconnect();
-		});
-	}
-});
-
-onUnmounted(() => {
-	if (readObserver) readObserver.disconnect();
-	if (connection) connection.dispose();
-});
-
 const followRequestDone = ref(false);
 
 const acceptFollowRequest = () => {
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 874f1f90ea..1aea95fe0e 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -29,7 +29,6 @@ import { notificationTypes } from '@/const';
 
 const props = defineProps<{
 	includeTypes?: typeof notificationTypes[number][];
-	unreadOnly?: boolean;
 }>();
 
 const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
@@ -40,23 +39,17 @@ const pagination: Paging = {
 	params: computed(() => ({
 		includeTypes: props.includeTypes ?? undefined,
 		excludeTypes: props.includeTypes ? undefined : $i.mutingNotificationTypes,
-		unreadOnly: props.unreadOnly,
 	})),
 };
 
 const onNotification = (notification) => {
 	const isMuted = props.includeTypes ? !props.includeTypes.includes(notification.type) : $i.mutingNotificationTypes.includes(notification.type);
 	if (isMuted || document.visibilityState === 'visible') {
-		stream.send('readNotification', {
-			id: notification.id,
-		});
+		stream.send('readNotification');
 	}
 
 	if (!isMuted) {
-		pagingComponent.value.prepend({
-			...notification,
-			isRead: document.visibilityState === 'visible',
-		});
+		pagingComponent.value.prepend(notification);
 	}
 };
 
@@ -65,30 +58,6 @@ let connection;
 onMounted(() => {
 	connection = stream.useChannel('main');
 	connection.on('notification', onNotification);
-	connection.on('readAllNotifications', () => {
-		if (pagingComponent.value) {
-			for (const item of pagingComponent.value.queue) {
-				item.isRead = true;
-			}
-			for (const item of pagingComponent.value.items) {
-				item.isRead = true;
-			}
-		}
-	});
-	connection.on('readNotifications', notificationIds => {
-		if (pagingComponent.value) {
-			for (let i = 0; i < pagingComponent.value.queue.length; i++) {
-				if (notificationIds.includes(pagingComponent.value.queue[i].id)) {
-					pagingComponent.value.queue[i].isRead = true;
-				}
-			}
-			for (let i = 0; i < (pagingComponent.value.items || []).length; i++) {
-				if (notificationIds.includes(pagingComponent.value.items[i].id)) {
-					pagingComponent.value.items[i].isRead = true;
-				}
-			}
-		}
-	});
 });
 
 onUnmounted(() => {
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index 9232ebb7c9..0f148022bf 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -12,7 +12,7 @@ import { onMounted } from 'vue';
 import { i18n } from '@/i18n';
 
 const props = withDefaults(defineProps<{
-	maxHeight: number;
+	maxHeight?: number;
 }>(), {
 	maxHeight: 200,
 });
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index a5c7cdaa71..1789606cd8 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -2,8 +2,8 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :content-max="800">
-		<div v-if="tab === 'all' || tab === 'unread'">
-			<XNotifications class="notifications" :include-types="includeTypes" :unread-only="unreadOnly"/>
+		<div v-if="tab === 'all'">
+			<XNotifications class="notifications" :include-types="includeTypes"/>
 		</div>
 		<div v-else-if="tab === 'mentions'">
 			<MkNotes :pagination="mentionsPagination"/>
@@ -26,7 +26,6 @@ import { notificationTypes } from '@/const';
 
 let tab = $ref('all');
 let includeTypes = $ref<string[] | null>(null);
-let unreadOnly = $computed(() => tab === 'unread');
 
 const mentionsPagination = {
 	endpoint: 'notes/mentions' as const,
@@ -76,10 +75,6 @@ const headerTabs = $computed(() => [{
 	key: 'all',
 	title: i18n.ts.all,
 	icon: 'ti ti-point',
-}, {
-	key: 'unread',
-	title: i18n.ts.unread,
-	icon: 'ti ti-loader',
 }, {
 	key: 'mentions',
 	title: i18n.ts.mentions,
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue
index e1561cb396..5a32c076a4 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -53,9 +53,7 @@ function onNotification(notification) {
 	if ($i.mutingNotificationTypes.includes(notification.type)) return;
 
 	if (document.visibilityState === 'visible') {
-		stream.send('readNotification', {
-			id: notification.id,
-		});
+		stream.send('readNotification');
 
 		notifications.unshift(notification);
 		window.setTimeout(() => {
diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts
index 8659261949..d72e163cd4 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -515,7 +515,6 @@ export type Endpoints = {
 	// notifications
 	'notifications/create': { req: { body: string; header?: string | null; icon?: string | null; }; res: null; };
 	'notifications/mark-all-as-read': { req: NoParams; res: null; };
-	'notifications/read': { req: { notificationId: Notification['id']; }; res: null; };
 
 	// page-push
 	'page-push': { req: { pageId: Page['id']; event: string; var?: any; }; res: null; };
diff --git a/packages/sw/src/scripts/notification-read.ts b/packages/sw/src/scripts/notification-read.ts
deleted file mode 100644
index 3b1dde0cd5..0000000000
--- a/packages/sw/src/scripts/notification-read.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { get } from 'idb-keyval';
-import { pushNotificationDataMap } from '@/types';
-import { api } from '@/scripts/operations';
-
-type Accounts = {
-	[x: string]: {
-		queue: string[],
-		timeout: number | null
-	}
-};
-
-class SwNotificationReadManager {
-	private accounts: Accounts = {};
-
-	public async construct() {
-		const accounts = await get('accounts');
-		if (!accounts) Error('Accounts are not recorded');
-
-		this.accounts = accounts.reduce((acc, e) => {
-			acc[e.id] = {
-				queue: [],
-				timeout: null
-			};
-			return acc;
-		}, {} as Accounts);
-
-		return this;
-	}
-
-	// プッシュ通知の既読をサーバーに送信
-	public async read(data: pushNotificationDataMap[keyof pushNotificationDataMap]) {
-		if (data.type !== 'notification' || !(data.userId in this.accounts)) return;
-
-		const account = this.accounts[data.userId];
-
-		account.queue.push(data.body.id as string);
-
-		if (account.queue.length >= 20) {
-			if (account.timeout) clearTimeout(account.timeout);
-			const notificationIds = account.queue;
-			account.queue = [];
-			await api('notifications/read', data.userId, { notificationIds });
-			return;
-		}
-
-		// 最後の呼び出しから200ms待ってまとめて処理する
-		if (account.timeout) clearTimeout(account.timeout);
-		account.timeout = setTimeout(() => {
-			account.timeout = null;
-
-			const notificationIds = account.queue;
-			account.queue = [];
-			api('notifications/read', data.userId, { notificationIds });
-		}, 200);
-	}
-}
-
-export const swNotificationRead = (new SwNotificationReadManager()).construct();
diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts
index 6f4c487354..9ceef890dd 100644
--- a/packages/sw/src/sw.ts
+++ b/packages/sw/src/sw.ts
@@ -1,6 +1,6 @@
 import { createEmptyNotification, createNotification } from '@/scripts/create-notification';
 import { swLang } from '@/scripts/lang';
-import { swNotificationRead } from '@/scripts/notification-read';
+import { api } from '@/scripts/operations';
 import { pushNotificationDataMap } from '@/types';
 import * as swos from '@/scripts/operations';
 import { acct as getAcct } from '@/filters/user';
@@ -54,30 +54,6 @@ globalThis.addEventListener('push', ev => {
 				if ((new Date()).getTime() - data.dateTime > 1000 * 60 * 60 * 24) break;
 
 				return createNotification(data);
-			case 'readAllNotifications':
-				for (const n of await globalThis.registration.getNotifications()) {
-					if (n?.data?.type === 'notification') n.close();
-				}
-				break;
-			case 'readAllAntennas':
-				for (const n of await globalThis.registration.getNotifications()) {
-					if (n?.data?.type === 'unreadAntennaNote') n.close();
-				}
-				break;
-			case 'readNotifications':
-				for (const n of await globalThis.registration.getNotifications()) {
-					if (data.body.notificationIds.includes(n.data.body.id)) {
-						n.close();
-					}
-				}
-				break;
-			case 'readAntenna':
-				for (const n of await globalThis.registration.getNotifications()) {
-					if (n?.data?.type === 'unreadAntennaNote' && data.body.antennaId === n.data.body.antenna.id) {
-						n.close();
-					}
-				}
-				break;
 		}
 
 		await createEmptyNotification();
@@ -154,7 +130,7 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
 			client.focus();
 		}
 		if (data.type === 'notification') {
-			swNotificationRead.then(that => that.read(data));
+			api('notifications/mark-all-as-read', data.userId);
 		}
 
 		notification.close();
@@ -165,7 +141,7 @@ globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEv
 	const data: pushNotificationDataMap[keyof pushNotificationDataMap] = ev.notification.data;
 
 	if (data.type === 'notification') {
-		swNotificationRead.then(that => that.read(data));
+		api('notifications/mark-all-as-read', data.userId);
 	}
 });
 
diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts
index 5b53ddecac..176b181be0 100644
--- a/packages/sw/src/types.ts
+++ b/packages/sw/src/types.ts
@@ -17,10 +17,6 @@ type pushNotificationDataSourceMap = {
 		antenna: { id: string, name: string };
 		note: Misskey.entities.Note;
 	};
-	readNotifications: { notificationIds: string[] };
-	readAllNotifications: undefined;
-	readAntenna: { antennaId: string };
-	readAllAntennas: undefined;
 };
 
 export type pushNotificationData<K extends keyof pushNotificationDataSourceMap> = {