From 480ec9803a9a130482a83b7664fe77ac4b1f4d20 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:08:04 +0900 Subject: [PATCH] wip --- .config/cypress-devcontainer.yml | 8 +++++++ .config/docker_example.yml | 8 +++++++ .config/example.yml | 10 ++++++++ .devcontainer/devcontainer.yml | 8 +++++++ chart/files/default.yml | 8 +++++++ packages/backend/src/GlobalModule.ts | 14 +++++++++-- packages/backend/src/config.ts | 3 +++ packages/backend/src/core/ReactionService.ts | 8 +------ .../src/core/ReactionsBufferingService.ts | 23 +++++++++++-------- .../src/core/entities/NoteEntityService.ts | 10 ++++++-- packages/backend/src/di-symbols.ts | 1 + .../BakeBufferedReactionsProcessorService.ts | 8 +++++++ .../backend/src/server/HealthServerService.ts | 4 ++++ 13 files changed, 93 insertions(+), 20 deletions(-) diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index e8da5f5e27..91dce35155 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -103,6 +103,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForReactions: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.config/docker_example.yml b/.config/docker_example.yml index d347882d1a..3f8e5734ce 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -106,6 +106,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForReactions: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index b11cbd1373..7080159117 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -172,6 +172,16 @@ redis: # # You can specify more ioredis options... # #username: example-username +#redisForReactions: +# host: localhost +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 +# # You can specify more ioredis options... +# #username: example-username + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index beefcfd0a2..3eb4fc2879 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -103,6 +103,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForReactions: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/chart/files/default.yml b/chart/files/default.yml index f98b8ebfee..4d17131c25 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -124,6 +124,14 @@ redis: # #prefix: example-prefix # #db: 1 +#redisForReactions: +# host: redis +# port: 6379 +# #family: 0 # 0=Both, 4=IPv4, 6=IPv6 +# #pass: example-pass +# #prefix: example-prefix +# #db: 1 + # ┌───────────────────────────┐ #───┘ MeiliSearch configuration └───────────────────────────── diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts index 09971e8ca0..2ecc1f4742 100644 --- a/packages/backend/src/GlobalModule.ts +++ b/packages/backend/src/GlobalModule.ts @@ -78,11 +78,19 @@ const $redisForTimelines: Provider = { inject: [DI.config], }; +const $redisForReactions: Provider = { + provide: DI.redisForReactions, + useFactory: (config: Config) => { + return new Redis.Redis(config.redisForReactions); + }, + inject: [DI.config], +}; + @Global() @Module({ imports: [RepositoryModule], - providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines], - exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule], + providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions], + exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, RepositoryModule], }) export class GlobalModule implements OnApplicationShutdown { constructor( @@ -91,6 +99,7 @@ export class GlobalModule implements OnApplicationShutdown { @Inject(DI.redisForPub) private redisForPub: Redis.Redis, @Inject(DI.redisForSub) private redisForSub: Redis.Redis, @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, + @Inject(DI.redisForReactions) private redisForReactions: Redis.Redis, ) { } public async dispose(): Promise { @@ -103,6 +112,7 @@ export class GlobalModule implements OnApplicationShutdown { this.redisForPub.disconnect(), this.redisForSub.disconnect(), this.redisForTimelines.disconnect(), + this.redisForReactions.disconnect(), ]); } diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index cbd6d1c086..97ba79c574 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -49,6 +49,7 @@ type Source = { redisForPubsub?: RedisOptionsSource; redisForJobQueue?: RedisOptionsSource; redisForTimelines?: RedisOptionsSource; + redisForReactions?: RedisOptionsSource; meilisearch?: { host: string; port: string; @@ -171,6 +172,7 @@ export type Config = { redisForPubsub: RedisOptions & RedisOptionsSource; redisForJobQueue: RedisOptions & RedisOptionsSource; redisForTimelines: RedisOptions & RedisOptionsSource; + redisForReactions: RedisOptions & RedisOptionsSource; sentryForBackend: { options: Partial; enableNodeProfiling: boolean; } | undefined; sentryForFrontend: { options: Partial } | undefined; perChannelMaxNoteCacheCount: number; @@ -251,6 +253,7 @@ export function loadConfig(): Config { redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis, redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis, redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis, + redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis, sentryForBackend: config.sentryForBackend, sentryForFrontend: config.sentryForFrontend, id: config.id, diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 1c8d62bc2d..220a4f163e 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -4,7 +4,6 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; @@ -72,9 +71,6 @@ const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/; @Injectable() export class ReactionService { constructor( - @Inject(DI.redis) - private redisClient: Redis.Redis, // TODO: 専用のRedisインスタンスにする - @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -198,10 +194,8 @@ export class ReactionService { } } - const rbt = true; - // Increment reactions count - if (rbt) { + if (meta.enableReactionsBuffering) { this.reactionsBufferingService.create(note, reaction); // for debugging diff --git a/packages/backend/src/core/ReactionsBufferingService.ts b/packages/backend/src/core/ReactionsBufferingService.ts index 1c8e46ea56..2a4a59e30a 100644 --- a/packages/backend/src/core/ReactionsBufferingService.ts +++ b/packages/backend/src/core/ReactionsBufferingService.ts @@ -19,8 +19,8 @@ export class ReactionsBufferingService { @Inject(DI.config) private config: Config, - @Inject(DI.redis) - private redisClient: Redis.Redis, // TODO: 専用のRedisインスタンスにする + @Inject(DI.redisForReactions) + private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする @Inject(DI.notesRepository) private notesRepository: NotesRepository, @@ -29,17 +29,17 @@ export class ReactionsBufferingService { @bindThis public async create(note: MiNote, reaction: string) { - this.redisClient.hincrby(`${REDIS_PREFIX}:${note.id}`, reaction, 1); + this.redisForReactions.hincrby(`${REDIS_PREFIX}:${note.id}`, reaction, 1); } @bindThis public async delete(note: MiNote, reaction: string) { - this.redisClient.hincrby(`${REDIS_PREFIX}:${note.id}`, reaction, -1); + this.redisForReactions.hincrby(`${REDIS_PREFIX}:${note.id}`, reaction, -1); } @bindThis public async get(noteId: MiNote['id']): Promise> { - const result = await this.redisClient.hgetall(`${REDIS_PREFIX}:${noteId}`); + const result = await this.redisForReactions.hgetall(`${REDIS_PREFIX}:${noteId}`); const delta = {} as Record; for (const [name, count] of Object.entries(result)) { delta[name] = parseInt(count); @@ -51,7 +51,7 @@ export class ReactionsBufferingService { public async getMany(noteIds: MiNote['id'][]): Promise>> { const deltas = new Map>(); - const pipeline = this.redisClient.pipeline(); + const pipeline = this.redisForReactions.pipeline(); for (const noteId of noteIds) { pipeline.hgetall(`${REDIS_PREFIX}:${noteId}`); } @@ -77,8 +77,13 @@ export class ReactionsBufferingService { let cursor = '0'; do { // https://github.com/redis/ioredis#transparent-key-prefixing - const result = await this.redisClient.scan(cursor, 'MATCH', `${this.config.redis.prefix}:${REDIS_PREFIX}:*`, 'COUNT', '1000'); - console.log(result); + const result = await this.redisForReactions.scan( + cursor, + 'MATCH', + `${this.config.redis.prefix}:${REDIS_PREFIX}:*`, + 'COUNT', + '1000'); + cursor = result[0]; bufferedNoteIds.push(...result[1].map(x => x.replace(`${this.config.redis.prefix}:${REDIS_PREFIX}:`, ''))); } while (cursor !== '0'); @@ -86,7 +91,7 @@ export class ReactionsBufferingService { const deltas = await this.getMany(bufferedNoteIds); // clear - const pipeline = this.redisClient.pipeline(); + const pipeline = this.redisForReactions.pipeline(); for (const noteId of bufferedNoteIds) { pipeline.del(`${REDIS_PREFIX}:${noteId}`); } diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index e4a55ef4d0..8a44d03fc2 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -16,6 +16,7 @@ import { bindThis } from '@/decorators.js'; import { DebounceLoader } from '@/misc/loader.js'; import { IdService } from '@/core/IdService.js'; import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; +import { MetaService } from '@/core/MetaService.js'; import type { OnModuleInit } from '@nestjs/common'; import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { ReactionService } from '../ReactionService.js'; @@ -42,6 +43,7 @@ export class NoteEntityService implements OnModuleInit { private reactionService: ReactionService; private reactionsBufferingService: ReactionsBufferingService; private idService: IdService; + private metaService: MetaService; private noteLoader = new DebounceLoader(this.findNoteOrFail); constructor( @@ -73,6 +75,8 @@ export class NoteEntityService implements OnModuleInit { //private customEmojiService: CustomEmojiService, //private reactionService: ReactionService, //private reactionsBufferingService: ReactionsBufferingService, + //private idService: IdService, + //private metaService: MetaService, ) { } @@ -83,6 +87,7 @@ export class NoteEntityService implements OnModuleInit { this.reactionService = this.moduleRef.get('ReactionService'); this.reactionsBufferingService = this.moduleRef.get('ReactionsBufferingService'); this.idService = this.moduleRef.get('IdService'); + this.metaService = this.moduleRef.get('MetaService'); } @bindThis @@ -417,8 +422,9 @@ export class NoteEntityService implements OnModuleInit { ) { if (notes.length === 0) return []; - const rbt = true; - const reactionsDeltas = rbt ? await this.reactionsBufferingService.getMany(notes.map(x => x.id)) : new Map(); + const meta = await this.metaService.fetch(); + + const reactionsDeltas = meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(notes.map(x => x.id)) : new Map(); const meId = me ? me.id : null; const myReactionsMap = new Map(); diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts index 271082b4ff..b6f003c2e6 100644 --- a/packages/backend/src/di-symbols.ts +++ b/packages/backend/src/di-symbols.ts @@ -11,6 +11,7 @@ export const DI = { redisForPub: Symbol('redisForPub'), redisForSub: Symbol('redisForSub'), redisForTimelines: Symbol('redisForTimelines'), + redisForReactions: Symbol('redisForReactions'), //#region Repositories usersRepository: Symbol('usersRepository'), diff --git a/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts b/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts index 22bc286692..cd56ba9837 100644 --- a/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts +++ b/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts @@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; +import { MetaService } from '@/core/MetaService.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; @@ -16,6 +17,7 @@ export class BakeBufferedReactionsProcessorService { constructor( private reactionsBufferingService: ReactionsBufferingService, + private metaService: MetaService, private queueLoggerService: QueueLoggerService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('bake-buffered-reactions'); @@ -23,6 +25,12 @@ export class BakeBufferedReactionsProcessorService { @bindThis public async process(): Promise { + const meta = await this.metaService.fetch(); + if (!meta.enableReactionsBuffering) { + this.logger.info('Reactions buffering is disabled. Skipping...'); + return; + } + this.logger.info('Baking buffered reactions...'); await this.reactionsBufferingService.bake(); diff --git a/packages/backend/src/server/HealthServerService.ts b/packages/backend/src/server/HealthServerService.ts index 2c3ed85925..5980609f02 100644 --- a/packages/backend/src/server/HealthServerService.ts +++ b/packages/backend/src/server/HealthServerService.ts @@ -27,6 +27,9 @@ export class HealthServerService { @Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis, + @Inject(DI.redisForReactions) + private redisForReactions: Redis.Redis, + @Inject(DI.db) private db: DataSource, @@ -43,6 +46,7 @@ export class HealthServerService { this.redisForPub.ping(), this.redisForSub.ping(), this.redisForTimelines.ping(), + this.redisForReactions.ping(), this.db.query('SELECT 1'), ...(this.meilisearch ? [this.meilisearch.health()] : []), ]).then(() => 200, () => 503));