add cache to follow and unfollow
This commit is contained in:
parent
b37717d431
commit
4b6d3e3fd6
8 changed files with 34 additions and 22 deletions
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue