From 4b6d3e3fd6480cba96c000649224592b434b4ae9 Mon Sep 17 00:00:00 2001 From: Namekuji Date: Thu, 27 Jul 2023 04:43:50 -0400 Subject: [PATCH] add cache to follow and unfollow --- packages/backend/src/config/types.ts | 2 +- packages/backend/src/misc/cache.ts | 8 +++++--- packages/backend/src/server/api/common/getters.ts | 4 ++-- .../src/server/api/endpoints/following/create.ts | 9 +++------ .../src/server/api/endpoints/following/delete.ts | 9 +++------ packages/backend/src/services/following/create.ts | 9 ++++++++- packages/backend/src/services/following/delete.ts | 10 +++++++--- packages/backend/src/services/following/reject.ts | 5 +++++ 8 files changed, 34 insertions(+), 22 deletions(-) diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index c5c5f97755..fe21e644cb 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -21,7 +21,7 @@ export type Source = { keyspace: string; replicationFactor: number; localDataCentre: string; - }, + }; redis: { host: string; port: number; diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index 838742e441..a6dab9c89f 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -131,17 +131,17 @@ export class Cache { } } -export class FollowingsCache { +export class LocalFollowingsCache { private myId: string; private key: string; private constructor(userId: string) { this.myId = userId; - this.key = `followings:${userId}`; + this.key = `follow:${userId}`; } public static async init(userId: string) { - const cache = new FollowingsCache(userId); + const cache = new LocalFollowingsCache(userId); // Sync from DB if no relationships is cached if ((await redisClient.scard(cache.key)) === 0) { @@ -157,12 +157,14 @@ export class FollowingsCache { public async follow(...targetIds: string[]) { if (targetIds.length > 0) { + // This is no-op if targets are already in cache await redisClient.sadd(this.key, targetIds); } } public async unfollow(...targetIds: string[]) { if (targetIds.length > 0) { + // This is no-op if targets are not in cache await redisClient.srem(this.key, targetIds); } } diff --git a/packages/backend/src/server/api/common/getters.ts b/packages/backend/src/server/api/common/getters.ts index 4c642ddd21..e4045fda81 100644 --- a/packages/backend/src/server/api/common/getters.ts +++ b/packages/backend/src/server/api/common/getters.ts @@ -4,7 +4,7 @@ import type { Note } from "@/models/entities/note.js"; import { Notes, Users } from "@/models/index.js"; import { generateVisibilityQuery } from "./generate-visibility-query.js"; import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js"; -import { FollowingsCache } from "@/misc/cache.js"; +import { LocalFollowingsCache } from "@/misc/cache.js"; /** * Get note for API processing, taking into account visibility. @@ -29,7 +29,7 @@ export async function getNote( ) { valid = true; } else if (me) { - const cache = await FollowingsCache.init(me.id); + const cache = await LocalFollowingsCache.init(me.id); valid = candidate.userId === me.id || // my own post diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 48ae6ae7af..f82024b621 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -5,6 +5,7 @@ import { getUser } from "../../common/getters.js"; import { Followings, Users } from "@/models/index.js"; import { IdentifiableError } from "@/misc/identifiable-error.js"; import { HOUR } from "@/const.js"; +import { LocalFollowingsCache } from "@/misc/cache.js"; export const meta = { tags: ["following", "users"], @@ -82,12 +83,8 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check if already following - const exist = await Followings.exist({ - where: { - followerId: follower.id, - followeeId: followee.id, - }, - }); + const cache = await LocalFollowingsCache.init(follower.id); + const exist = await cache.isFollowing(followee.id); if (exist) { throw new ApiError(meta.errors.alreadyFollowing); diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index cbc6097f4d..0cb6c58863 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -4,6 +4,7 @@ import { ApiError } from "../../error.js"; import { getUser } from "../../common/getters.js"; import { Followings, Users } from "@/models/index.js"; import { HOUR } from "@/const.js"; +import { LocalFollowingsCache } from "@/misc/cache.js"; export const meta = { tags: ["following", "users"], @@ -69,12 +70,8 @@ export default define(meta, paramDef, async (ps, user) => { }); // Check not following - const exist = await Followings.exist({ - where: { - followerId: follower.id, - followeeId: followee.id, - }, - }); + const cache = await LocalFollowingsCache.init(follower.id); + const exist = await cache.isFollowing(followee.id); if (!exist) { throw new ApiError(meta.errors.notFollowing); diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index 3a77676b38..8a5b68ab90 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -28,6 +28,7 @@ import type { Packed } from "@/misc/schema.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { webhookDeliver } from "@/queue/index.js"; import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; +import { LocalFollowingsCache } from "@/misc/cache.js"; const logger = new Logger("following/create"); @@ -57,7 +58,7 @@ export async function insertFollowingDoc( followerId: follower.id, followeeId: followee.id, - // 非正規化 + // Denormalization followerHost: follower.host, followerInbox: Users.isRemoteUser(follower) ? follower.inbox : null, followerSharedInbox: Users.isRemoteUser(follower) @@ -81,6 +82,12 @@ export async function insertFollowingDoc( } }); + if (Users.isLocalUser(follower)) { + // Cache following ID set + const cache = await LocalFollowingsCache.init(follower.id); + await cache.follow(followee.id); + } + const req = await FollowRequests.findOneBy({ followeeId: followee.id, followerId: follower.id, diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts index fae4bd3cec..1ba874b207 100644 --- a/packages/backend/src/services/following/delete.ts +++ b/packages/backend/src/services/following/delete.ts @@ -13,6 +13,7 @@ import { perUserFollowingChart, } from "@/services/chart/index.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; +import { LocalFollowingsCache } from "@/misc/cache.js"; const logger = new Logger("following/delete"); @@ -39,14 +40,17 @@ export default async function ( }); if (following == null) { - logger.warn( - "フォロー解除がリクエストされましたがフォローしていませんでした", - ); + logger.warn("Requested follow removal but not yet following"); return; } await Followings.delete(following.id); + if (Users.isLocalUser(follower)) { + const cache = await LocalFollowingsCache.init(follower.id); + await cache.unfollow(followee.id); + } + decrementFollowing(follower, followee); // Publish unfollow event diff --git a/packages/backend/src/services/following/reject.ts b/packages/backend/src/services/following/reject.ts index 7464219bf6..ca08a26e84 100644 --- a/packages/backend/src/services/following/reject.ts +++ b/packages/backend/src/services/following/reject.ts @@ -8,6 +8,7 @@ import { User } from "@/models/entities/user.js"; import { Users, FollowRequests, Followings } from "@/models/index.js"; import { decrementFollowing } from "./delete.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js"; +import { LocalFollowingsCache } from "@/misc/cache.js"; type Local = | ILocalUser @@ -91,6 +92,10 @@ async function removeFollow(followee: Both, follower: Both) { if (!following) return; await Followings.delete(following.id); + if (Users.isLocalUser(follower)) { + const cache = await LocalFollowingsCache.init(follower.id); + await cache.unfollow(followee.id); + } decrementFollowing(follower, followee); }