diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index c2695aa78..588931ff1 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -6,8 +6,8 @@ export class Cache { private ttl: number; private prefix: string; - constructor(prefix: string, ttl: number) { - this.ttl = ttl; + constructor(prefix: string, ttlSeconds: number) { + this.ttl = ttlSeconds; this.prefix = `cache:${prefix}`; } @@ -15,26 +15,28 @@ export class Cache { return key ? `${this.prefix}:${key}` : this.prefix; } - public async set(key: string | null, value: T, transaction?: ChainableCommander): Promise { + public async set( + key: string | null, + value: T, + transaction?: ChainableCommander, + ): Promise { const _key = this.prefixedKey(key); const _value = Buffer.from(encode(value)); const commander = transaction ?? redisClient; - if (this.ttl === Infinity) { - await commander.set(_key, _value); - } else { - await commander.set(_key, _value, "PX", this.ttl); - } + await commander.set(_key, _value, "EX", this.ttl); } - public async get(key: string | null): Promise { + public async get(key: string | null, renew = false): Promise { const _key = this.prefixedKey(key); const cached = await redisClient.getBuffer(_key); if (cached === null) return undefined; + if (renew) await redisClient.expire(_key, this.ttl); + return decode(cached) as T; } - public async getAll(): Promise> { + public async getAll(renew = false): Promise> { const keys = await redisClient.keys(`${this.prefix}*`); const map = new Map(); if (keys.length === 0) { @@ -49,6 +51,14 @@ export class Cache { } } + if (renew) { + const trans = redisClient.multi(); + for (const key of map.keys()) { + trans.expire(key, this.ttl); + } + await trans.exec(); + } + return map; } @@ -66,9 +76,10 @@ export class Cache { public async fetch( key: string | null, fetcher: () => Promise, + renew = false, validator?: (cachedValue: T) => boolean, ): Promise { - const cachedValue = await this.get(key); + const cachedValue = await this.get(key, renew); if (cachedValue !== undefined) { if (validator) { if (validator(cachedValue)) { @@ -94,9 +105,10 @@ export class Cache { public async fetchMaybe( key: string | null, fetcher: () => Promise, + renew = false, validator?: (cachedValue: T) => boolean, ): Promise { - const cachedValue = await this.get(key); + const cachedValue = await this.get(key, renew); if (cachedValue !== undefined) { if (validator) { if (validator(cachedValue)) { diff --git a/packages/backend/src/misc/check-hit-antenna.ts b/packages/backend/src/misc/check-hit-antenna.ts index c422cca94..1ff09d629 100644 --- a/packages/backend/src/misc/check-hit-antenna.ts +++ b/packages/backend/src/misc/check-hit-antenna.ts @@ -11,7 +11,7 @@ import * as Acct from "@/misc/acct.js"; import type { Packed } from "./schema.js"; import { Cache } from "./cache.js"; -const blockingCache = new Cache("blocking", 1000 * 60 * 5); +const blockingCache = new Cache("blocking", 60 * 5); // NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている diff --git a/packages/backend/src/misc/emoji-meta.ts b/packages/backend/src/misc/emoji-meta.ts index d2d15411f..2b9365b82 100644 --- a/packages/backend/src/misc/emoji-meta.ts +++ b/packages/backend/src/misc/emoji-meta.ts @@ -11,7 +11,7 @@ export type Size = { height: number; }; -const cache = new Cache("emojiMeta",1000 * 60 * 10); // once every 10 minutes for the same url +const cache = new Cache("emojiMeta", 60 * 10); // once every 10 minutes for the same url const logger = new Logger("emoji"); export async function getEmojiSize(url: string): Promise { diff --git a/packages/backend/src/misc/keypair-store.ts b/packages/backend/src/misc/keypair-store.ts index b0e07c4ab..625577359 100644 --- a/packages/backend/src/misc/keypair-store.ts +++ b/packages/backend/src/misc/keypair-store.ts @@ -3,10 +3,12 @@ import type { User } from "@/models/entities/user.js"; import type { UserKeypair } from "@/models/entities/user-keypair.js"; import { Cache } from "./cache.js"; -const cache = new Cache("keypairStore", Infinity); +const cache = new Cache("keypairStore", 60 * 30); export async function getUserKeypair(userId: User["id"]): Promise { - return await cache.fetch(userId, () => - UserKeypairs.findOneByOrFail({ userId: userId }), + return await cache.fetch( + userId, + () => UserKeypairs.findOneByOrFail({ userId: userId }), + true, ); } diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts index e6e6c2fb9..795a267f9 100644 --- a/packages/backend/src/misc/populate-emojis.ts +++ b/packages/backend/src/misc/populate-emojis.ts @@ -9,7 +9,7 @@ import config from "@/config/index.js"; import { query } from "@/prelude/url.js"; import { redisClient } from "@/db/redis.js"; -const cache = new Cache("populateEmojis", 1000 * 60 * 60 * 12); +const cache = new Cache("populateEmojis", 60 * 60 * 12); /** * 添付用絵文字情報 diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 2bd8d4fba..5ca36e3d3 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -1,4 +1,3 @@ -import { URL } from "url"; import { In, Not } from "typeorm"; import Ajv from "ajv"; import type { ILocalUser, IRemoteUser } from "@/models/entities/user.js"; @@ -40,7 +39,10 @@ import { } from "../index.js"; import type { Instance } from "../entities/instance.js"; -const userInstanceCache = new Cache("userInstance", 1000 * 60 * 60 * 3); +const userInstanceCache = new Cache( + "userInstance", + 60 * 60 * 3, +); type IsUserDetailed = Detailed extends true ? Packed<"UserDetailed"> diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index 4b4ea9627..a710b9f11 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -5,7 +5,6 @@ import type { CacheableRemoteUser, CacheableUser, } from "@/models/entities/user.js"; -import { User, IRemoteUser } from "@/models/entities/user.js"; import type { UserPublickey } from "@/models/entities/user-publickey.js"; import type { MessagingMessage } from "@/models/entities/messaging-message.js"; import { @@ -20,8 +19,11 @@ import type { IObject } from "./type.js"; import { getApId } from "./type.js"; import { resolvePerson } from "./models/person.js"; -const publicKeyCache = new Cache("publicKey", Infinity); -const publicKeyByUserIdCache = new Cache("publicKeyByUserId", Infinity); +const publicKeyCache = new Cache("publicKey", 60 * 30); +const publicKeyByUserIdCache = new Cache( + "publicKeyByUserId", + 60 * 30, +); export type UriParseResult = | { @@ -123,17 +125,23 @@ export default class DbResolver { if (parsed.type !== "users") return null; return ( - (await userByIdCache.fetchMaybe(parsed.id, () => - Users.findOneBy({ - id: parsed.id, - }).then((x) => x ?? undefined), + (await userByIdCache.fetchMaybe( + parsed.id, + () => + Users.findOneBy({ + id: parsed.id, + }).then((x) => x ?? undefined), + true, )) ?? null ); } else { - return await uriPersonCache.fetch(parsed.uri, () => - Users.findOneBy({ - uri: parsed.uri, - }), + return await uriPersonCache.fetch( + parsed.uri, + () => + Users.findOneBy({ + uri: parsed.uri, + }), + true, ); } } @@ -156,14 +164,17 @@ export default class DbResolver { return key; }, + true, (key) => key != null, ); if (key == null) return null; return { - user: (await userByIdCache.fetch(key.userId, () => - Users.findOneByOrFail({ id: key.userId }), + user: (await userByIdCache.fetch( + key.userId, + () => Users.findOneByOrFail({ id: key.userId }), + true, )) as CacheableRemoteUser, key, }; @@ -183,6 +194,7 @@ export default class DbResolver { const key = await publicKeyByUserIdCache.fetch( user.id, () => UserPublickeys.findOneBy({ userId: user.id }), + true, (v) => v != null, ); diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index c541e9ae5..c5519ba03 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -135,7 +135,7 @@ export async function fetchPerson( ): Promise { if (typeof uri !== "string") throw new Error("uri is not string"); - const cached = await uriPersonCache.get(uri); + const cached = await uriPersonCache.get(uri, true); if (cached) return cached; // Fetch from the database if the URI points to this server diff --git a/packages/backend/src/server/api/authenticate.ts b/packages/backend/src/server/api/authenticate.ts index b6fa973eb..460a0ce84 100644 --- a/packages/backend/src/server/api/authenticate.ts +++ b/packages/backend/src/server/api/authenticate.ts @@ -9,7 +9,7 @@ import { localUserByNativeTokenCache, } from "@/services/user-cache.js"; -const appCache = new Cache("app", Infinity); +const appCache = new Cache("app", 60 * 30); export class AuthenticationError extends Error { constructor(message: string) { @@ -49,6 +49,7 @@ export default async ( const user = await localUserByNativeTokenCache.fetch( token, () => Users.findOneBy({ token }) as Promise, + true, ); if (user == null) { @@ -82,11 +83,14 @@ export default async ( Users.findOneBy({ id: accessToken.userId, }) as Promise, + true, ); if (accessToken.appId) { - const app = await appCache.fetch(accessToken.appId, () => - Apps.findOneByOrFail({ id: accessToken.appId! }), + const app = await appCache.fetch( + accessToken.appId, + () => Apps.findOneByOrFail({ id: accessToken.appId! }), + true, ); return [ diff --git a/packages/backend/src/server/nodeinfo.ts b/packages/backend/src/server/nodeinfo.ts index 28cefd2cf..940ca2e13 100644 --- a/packages/backend/src/server/nodeinfo.ts +++ b/packages/backend/src/server/nodeinfo.ts @@ -100,7 +100,10 @@ const nodeinfo2 = async () => { }; }; -const cache = new Cache>>("nodeinfo", 1000 * 60 * 10); +const cache = new Cache>>( + "nodeinfo", + 60 * 10, +); router.get(nodeinfo2_1path, async (ctx) => { const base = await cache.fetch(null, () => nodeinfo2()); diff --git a/packages/backend/src/services/instance-actor.ts b/packages/backend/src/services/instance-actor.ts index 1822cb3c7..a8b34ea57 100644 --- a/packages/backend/src/services/instance-actor.ts +++ b/packages/backend/src/services/instance-actor.ts @@ -6,10 +6,10 @@ import { IsNull } from "typeorm"; const ACTOR_USERNAME = "instance.actor" as const; -const cache = new Cache("instanceActor", Infinity); +const cache = new Cache("instanceActor", 60 * 30); export async function getInstanceActor(): Promise { - const cached = await cache.get(null); + const cached = await cache.get(null, true); if (cached) return cached; const user = (await Users.findOneBy({ diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index ca0f05f2c..095c75f42 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -29,17 +29,14 @@ import { Notes, Instances, UserProfiles, - Antennas, - Followings, MutedNotes, Channels, ChannelFollowings, - Blockings, NoteThreadMutings, } from "@/models/index.js"; import type { DriveFile } from "@/models/entities/drive-file.js"; import type { App } from "@/models/entities/app.js"; -import { Not, In, IsNull } from "typeorm"; +import { Not, In } from "typeorm"; import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import { genId } from "@/misc/gen-id.js"; import { @@ -73,7 +70,7 @@ import { Mutex } from "redis-semaphore"; const mutedWordsCache = new Cache< { userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[] ->("mutedWords", 1000 * 60 * 5); +>("mutedWords", 60 * 5); type NotificationType = "reply" | "renote" | "quote" | "mention"; diff --git a/packages/backend/src/services/register-or-fetch-instance-doc.ts b/packages/backend/src/services/register-or-fetch-instance-doc.ts index e0c288037..c0ead0819 100644 --- a/packages/backend/src/services/register-or-fetch-instance-doc.ts +++ b/packages/backend/src/services/register-or-fetch-instance-doc.ts @@ -4,7 +4,7 @@ import { genId } from "@/misc/gen-id.js"; import { toPuny } from "@/misc/convert-host.js"; import { Cache } from "@/misc/cache.js"; -const cache = new Cache("registerOrFetchInstanceDoc", 1000 * 60 * 60); +const cache = new Cache("registerOrFetchInstanceDoc", 60 * 60); export async function registerOrFetchInstanceDoc( host: string, diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index 1ec2891e8..6f7829c21 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -15,7 +15,7 @@ import { createSystemUser } from "./create-system-user.js"; const ACTOR_USERNAME = "relay.actor" as const; -const relaysCache = new Cache("relay", 1000 * 60 * 10); +const relaysCache = new Cache("relay", 60 * 10); export async function getRelayActor(): Promise { const user = await Users.findOneBy({ diff --git a/packages/backend/src/services/user-cache.ts b/packages/backend/src/services/user-cache.ts index 7fde21d87..ed700185d 100644 --- a/packages/backend/src/services/user-cache.ts +++ b/packages/backend/src/services/user-cache.ts @@ -7,13 +7,19 @@ import { Users } from "@/models/index.js"; import { Cache } from "@/misc/cache.js"; import { redisClient, subscriber } from "@/db/redis.js"; -export const userByIdCache = new Cache("userById", Infinity); +export const userByIdCache = new Cache("userById", 60 * 30); export const localUserByNativeTokenCache = new Cache( "localUserByNativeToken", - Infinity, + 60 * 30, +); +export const localUserByIdCache = new Cache( + "localUserByIdCache", + 60 * 30, +); +export const uriPersonCache = new Cache( + "uriPerson", + 60 * 30, ); -export const localUserByIdCache = new Cache("localUserByIdCache", Infinity); -export const uriPersonCache = new Cache("uriPerson", Infinity); subscriber.on("message", async (_, data) => { const obj = JSON.parse(data);