summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2025-05-12 13:16:01 -0400
committerHazelnoot <acomputerdog@gmail.com>2025-05-12 21:35:06 -0400
commit7db48ffa8d27c4be37f87ea12e8d65942d5f9cdc (patch)
tree46666a0e8af0b49dfdf785030d88ca83b4aee348 /packages/backend/src
parentset common default values for RedisKVCache callbacks (diff)
downloadsharkey-7db48ffa8d27c4be37f87ea12e8d65942d5f9cdc.tar.gz
sharkey-7db48ffa8d27c4be37f87ea12e8d65942d5f9cdc.tar.bz2
sharkey-7db48ffa8d27c4be37f87ea12e8d65942d5f9cdc.zip
add redis cache for note translations
* Partitioned by target language * Invalidated if the note is edited
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/core/CacheService.ts47
1 files changed, 46 insertions, 1 deletions
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index 822bb9d42c..1cf63221f9 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -6,7 +6,7 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { IsNull } from 'typeorm';
-import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js';
+import type { BlockingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing, MiNote } from '@/models/_.js';
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
@@ -22,6 +22,17 @@ export interface FollowStats {
remoteFollowers: number;
}
+export interface CachedTranslation {
+ sourceLang: string | undefined;
+ text: string | undefined;
+}
+
+interface CachedTranslationEntity {
+ l?: string;
+ t?: string;
+ u?: number;
+}
+
@Injectable()
export class CacheService implements OnApplicationShutdown {
public userByIdCache: MemoryKVCache<MiUser>;
@@ -35,6 +46,7 @@ export class CacheService implements OnApplicationShutdown {
public renoteMutingsCache: RedisKVCache<Set<string>>;
public userFollowingsCache: RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
private readonly userFollowStatsCache = new MemoryKVCache<FollowStats>(1000 * 60 * 10); // 10 minutes
+ private readonly translationsCache: RedisKVCache<CachedTranslationEntity>;
constructor(
@Inject(DI.redis)
@@ -124,6 +136,11 @@ export class CacheService implements OnApplicationShutdown {
fromRedisConverter: (value) => JSON.parse(value),
});
+ this.translationsCache = new RedisKVCache<CachedTranslationEntity>(this.redisClient, 'translations', {
+ lifetime: 1000 * 60 * 60 * 24 * 7, // 1 week,
+ memoryCacheLifetime: 1000 * 60, // 1 minute
+ });
+
// NOTE: チャンネルのフォロー状況キャッシュはChannelFollowingServiceで行っている
this.redisForSub.on('message', this.onMessage);
@@ -254,6 +271,34 @@ export class CacheService implements OnApplicationShutdown {
}
@bindThis
+ public async getCachedTranslation(note: MiNote, targetLang: string): Promise<CachedTranslation | null> {
+ const cacheKey = `${note.id}@${targetLang}`;
+
+ // Use cached translation, if present and up-to-date
+ const cached = await this.translationsCache.get(cacheKey);
+ if (cached && cached.u === note.updatedAt?.valueOf()) {
+ return {
+ sourceLang: cached.l,
+ text: cached.t,
+ };
+ }
+
+ // No cache entry :(
+ return null;
+ }
+
+ @bindThis
+ public async setCachedTranslation(note: MiNote, targetLang: string, translation: CachedTranslation): Promise<void> {
+ const cacheKey = `${note.id}@${targetLang}`;
+
+ await this.translationsCache.set(cacheKey, {
+ l: translation.sourceLang,
+ t: translation.text,
+ u: note.updatedAt?.valueOf(),
+ });
+ }
+
+ @bindThis
public dispose(): void {
this.redisForSub.off('message', this.onMessage);
this.userByIdCache.dispose();