add cache to follow and unfollow

This commit is contained in:
Namekuji 2023-07-27 04:43:50 -04:00
parent b37717d431
commit 4b6d3e3fd6
No known key found for this signature in database
GPG key ID: 1D62332C07FBA532
8 changed files with 34 additions and 22 deletions

View file

@ -21,7 +21,7 @@ export type Source = {
keyspace: string; keyspace: string;
replicationFactor: number; replicationFactor: number;
localDataCentre: string; localDataCentre: string;
}, };
redis: { redis: {
host: string; host: string;
port: number; port: number;

View file

@ -131,17 +131,17 @@ export class Cache<T> {
} }
} }
export class FollowingsCache { export class LocalFollowingsCache {
private myId: string; private myId: string;
private key: string; private key: string;
private constructor(userId: string) { private constructor(userId: string) {
this.myId = userId; this.myId = userId;
this.key = `followings:${userId}`; this.key = `follow:${userId}`;
} }
public static async init(userId: string) { public static async init(userId: string) {
const cache = new FollowingsCache(userId); const cache = new LocalFollowingsCache(userId);
// Sync from DB if no relationships is cached // Sync from DB if no relationships is cached
if ((await redisClient.scard(cache.key)) === 0) { if ((await redisClient.scard(cache.key)) === 0) {
@ -157,12 +157,14 @@ export class FollowingsCache {
public async follow(...targetIds: string[]) { public async follow(...targetIds: string[]) {
if (targetIds.length > 0) { if (targetIds.length > 0) {
// This is no-op if targets are already in cache
await redisClient.sadd(this.key, targetIds); await redisClient.sadd(this.key, targetIds);
} }
} }
public async unfollow(...targetIds: string[]) { public async unfollow(...targetIds: string[]) {
if (targetIds.length > 0) { if (targetIds.length > 0) {
// This is no-op if targets are not in cache
await redisClient.srem(this.key, targetIds); await redisClient.srem(this.key, targetIds);
} }
} }

View file

@ -4,7 +4,7 @@ import type { Note } from "@/models/entities/note.js";
import { Notes, Users } from "@/models/index.js"; import { Notes, Users } from "@/models/index.js";
import { generateVisibilityQuery } from "./generate-visibility-query.js"; import { generateVisibilityQuery } from "./generate-visibility-query.js";
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.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. * Get note for API processing, taking into account visibility.
@ -29,7 +29,7 @@ export async function getNote(
) { ) {
valid = true; valid = true;
} else if (me) { } else if (me) {
const cache = await FollowingsCache.init(me.id); const cache = await LocalFollowingsCache.init(me.id);
valid = valid =
candidate.userId === me.id || // my own post candidate.userId === me.id || // my own post

View file

@ -5,6 +5,7 @@ import { getUser } from "../../common/getters.js";
import { Followings, Users } from "@/models/index.js"; import { Followings, Users } from "@/models/index.js";
import { IdentifiableError } from "@/misc/identifiable-error.js"; import { IdentifiableError } from "@/misc/identifiable-error.js";
import { HOUR } from "@/const.js"; import { HOUR } from "@/const.js";
import { LocalFollowingsCache } from "@/misc/cache.js";
export const meta = { export const meta = {
tags: ["following", "users"], tags: ["following", "users"],
@ -82,12 +83,8 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check if already following // Check if already following
const exist = await Followings.exist({ const cache = await LocalFollowingsCache.init(follower.id);
where: { const exist = await cache.isFollowing(followee.id);
followerId: follower.id,
followeeId: followee.id,
},
});
if (exist) { if (exist) {
throw new ApiError(meta.errors.alreadyFollowing); throw new ApiError(meta.errors.alreadyFollowing);

View file

@ -4,6 +4,7 @@ import { ApiError } from "../../error.js";
import { getUser } from "../../common/getters.js"; import { getUser } from "../../common/getters.js";
import { Followings, Users } from "@/models/index.js"; import { Followings, Users } from "@/models/index.js";
import { HOUR } from "@/const.js"; import { HOUR } from "@/const.js";
import { LocalFollowingsCache } from "@/misc/cache.js";
export const meta = { export const meta = {
tags: ["following", "users"], tags: ["following", "users"],
@ -69,12 +70,8 @@ export default define(meta, paramDef, async (ps, user) => {
}); });
// Check not following // Check not following
const exist = await Followings.exist({ const cache = await LocalFollowingsCache.init(follower.id);
where: { const exist = await cache.isFollowing(followee.id);
followerId: follower.id,
followeeId: followee.id,
},
});
if (!exist) { if (!exist) {
throw new ApiError(meta.errors.notFollowing); throw new ApiError(meta.errors.notFollowing);

View file

@ -28,6 +28,7 @@ import type { Packed } from "@/misc/schema.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { webhookDeliver } from "@/queue/index.js"; import { webhookDeliver } from "@/queue/index.js";
import { shouldSilenceInstance } from "@/misc/should-block-instance.js"; import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
import { LocalFollowingsCache } from "@/misc/cache.js";
const logger = new Logger("following/create"); const logger = new Logger("following/create");
@ -57,7 +58,7 @@ export async function insertFollowingDoc(
followerId: follower.id, followerId: follower.id,
followeeId: followee.id, followeeId: followee.id,
// 非正規化 // Denormalization
followerHost: follower.host, followerHost: follower.host,
followerInbox: Users.isRemoteUser(follower) ? follower.inbox : null, followerInbox: Users.isRemoteUser(follower) ? follower.inbox : null,
followerSharedInbox: Users.isRemoteUser(follower) 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({ const req = await FollowRequests.findOneBy({
followeeId: followee.id, followeeId: followee.id,
followerId: follower.id, followerId: follower.id,

View file

@ -13,6 +13,7 @@ import {
perUserFollowingChart, perUserFollowingChart,
} from "@/services/chart/index.js"; } from "@/services/chart/index.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { LocalFollowingsCache } from "@/misc/cache.js";
const logger = new Logger("following/delete"); const logger = new Logger("following/delete");
@ -39,14 +40,17 @@ export default async function (
}); });
if (following == null) { if (following == null) {
logger.warn( logger.warn("Requested follow removal but not yet following");
"フォロー解除がリクエストされましたがフォローしていませんでした",
);
return; return;
} }
await Followings.delete(following.id); await Followings.delete(following.id);
if (Users.isLocalUser(follower)) {
const cache = await LocalFollowingsCache.init(follower.id);
await cache.unfollow(followee.id);
}
decrementFollowing(follower, followee); decrementFollowing(follower, followee);
// Publish unfollow event // Publish unfollow event

View file

@ -8,6 +8,7 @@ import { User } from "@/models/entities/user.js";
import { Users, FollowRequests, Followings } from "@/models/index.js"; import { Users, FollowRequests, Followings } from "@/models/index.js";
import { decrementFollowing } from "./delete.js"; import { decrementFollowing } from "./delete.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { LocalFollowingsCache } from "@/misc/cache.js";
type Local = type Local =
| ILocalUser | ILocalUser
@ -91,6 +92,10 @@ async function removeFollow(followee: Both, follower: Both) {
if (!following) return; if (!following) return;
await Followings.delete(following.id); await Followings.delete(following.id);
if (Users.isLocalUser(follower)) {
const cache = await LocalFollowingsCache.init(follower.id);
await cache.unfollow(followee.id);
}
decrementFollowing(follower, followee); decrementFollowing(follower, followee);
} }