From 693cdde3273e0e8ffe1a04fd31fd54d982a3b1c4 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 30 May 2025 15:37:27 -0400 Subject: [PATCH 1/2] upsert reactions to avoid error+retry overhead --- packages/backend/src/core/ReactionService.ts | 25 ++++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index f05ee2ee73..e57a8d942d 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -176,27 +176,10 @@ export class ReactionService { reaction, }; - try { - await this.noteReactionsRepository.insert(record); - } catch (e) { - if (isDuplicateKeyValueError(e)) { - const exists = await this.noteReactionsRepository.findOneByOrFail({ - noteId: note.id, - userId: user.id, - }); - - if (exists.reaction !== reaction) { - // 別のリアクションがすでにされていたら置き換える - await this.delete(user, note); - await this.noteReactionsRepository.insert(record); - } else { - // 同じリアクションがすでにされていたらエラー - throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298'); - } - } else { - throw e; - } - } + await this.noteReactionsRepository.upsert(record, { + skipUpdateIfNoValuesChanged: true, + conflictPaths: ['noteId', 'userId'], + }); // Increment reactions count if (this.meta.enableReactionsBuffering) { From 3907355a4870df7229eb017e875390a3459ade6b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 1 Jun 2025 19:56:42 -0400 Subject: [PATCH 2/2] replace upsert with insert+fetch and delete+insert --- packages/backend/src/core/ReactionService.ts | 31 +++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index e57a8d942d..86bf20067e 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -10,7 +10,7 @@ import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MiRemoteUser, MiUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; import { IdService } from '@/core/IdService.js'; -import type { MiNoteReaction } from '@/models/NoteReaction.js'; +import { MiNoteReaction } from '@/models/NoteReaction.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { NotificationService } from '@/core/NotificationService.js'; @@ -31,6 +31,7 @@ import { isQuote, isRenote } from '@/misc/is-renote.js'; import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js'; import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js'; import { CacheService } from '@/core/CacheService.js'; +import type { DataSource } from 'typeorm'; const FALLBACK = '\u2764'; @@ -89,6 +90,9 @@ export class ReactionService { @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, + @Inject(DI.db) + private readonly db: DataSource, + private utilityService: UtilityService, private customEmojiService: CustomEmojiService, private roleService: RoleService, @@ -176,11 +180,30 @@ export class ReactionService { reaction, }; - await this.noteReactionsRepository.upsert(record, { - skipUpdateIfNoValuesChanged: true, - conflictPaths: ['noteId', 'userId'], + const result = await this.db.transaction(async tem => { + await tem.createQueryBuilder(MiNoteReaction, 'noteReaction') + .insert() + .values(record) + .orIgnore() + .execute(); + + return await tem.createQueryBuilder(MiNoteReaction, 'noteReaction') + .select() + .where({ noteId: note.id, userId: user.id }) + .getOneOrFail(); }); + if (result.id !== record.id) { + // Conflict with the same ID => nothing to do. + if (result.reaction === record.reaction) { + return; + } + + // 別のリアクションがすでにされていたら置き換える + await this.delete(user, note); + await this.noteReactionsRepository.insert(record); + } + // Increment reactions count if (this.meta.enableReactionsBuffering) { await this.reactionsBufferingService.create(note.id, user.id, reaction, note.reactionAndUserPairCache);