From 34282d8eb0c1625b859367e87836b8159131320c Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 2 Apr 2025 14:29:43 +0900 Subject: [PATCH 1/2] chore: use longer timeout for removing user --- packages/backend/src/postgres.ts | 31 +++++++++++++++++-- .../DeleteAccountProcessorService.ts | 11 +++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 4694e7003d..0f69a0958d 100644 --- a/packages/backend/src/postgres.ts +++ b/packages/backend/src/postgres.ts @@ -5,7 +5,7 @@ // https://github.com/typeorm/typeorm/issues/2400 import pg from 'pg'; -import { DataSource, Logger } from 'typeorm'; +import { DataSource, EntityManager, Logger } from 'typeorm'; import * as highlight from 'cli-highlight'; import { entities as charts } from '@/core/chart/entities.js'; import { Config } from '@/config.js'; @@ -250,6 +250,33 @@ export const entities = [ const log = process.env.NODE_ENV !== 'production'; +/** + * Execute query with extended timeout. + * This can be used to execute long-running queries like deleting many rows CASCADE-ly. + * + * TODO: consider remove this function when we removed CASCADE delete. + */ +export async function extendTimeoutQuery(dataSource: DataSource, query: (manager: EntityManager) => Promise): Promise { + const queryRunner = dataSource.createQueryRunner('master'); + const manager = dataSource.createEntityManager(queryRunner); + const extendedTimeout = 1000 * 100; // 100 sec for now. How long should it be? + try { + await queryRunner.connect(); + try { + // it looks postgres doesn't support statement_timeout with placeholder + // await manager.query(`set statement_timeout to $1`, [extendedTimeout]); + await manager.query(`set statement_timeout to ${extendedTimeout}`); + await query(manager); + } finally { + await manager.query(`set statement_timeout to ${default_statement_timeout}`); + } + } finally { + await queryRunner.release(); + } +} + +export const default_statement_timeout = 1000 * 10; + export function createPostgresDataSource(config: Config) { return new DataSource({ type: 'postgres', @@ -259,7 +286,7 @@ export function createPostgresDataSource(config: Config) { password: config.db.pass, database: config.db.db, extra: { - statement_timeout: 1000 * 10, + statement_timeout: default_statement_timeout, ...config.db.extra, }, ...(config.dbReplications ? { diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 14a53e0c42..a6440b5c74 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -4,9 +4,10 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import { MoreThan } from 'typeorm'; +import { DataSource, MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { DriveFilesRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import { MiUser } from '@/models/User.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; @@ -14,6 +15,7 @@ import type { MiNote } from '@/models/Note.js'; import { EmailService } from '@/core/EmailService.js'; import { bindThis } from '@/decorators.js'; import { SearchService } from '@/core/SearchService.js'; +import { extendTimeoutQuery } from '@/postgres.js'; import { QueueLoggerService } from '../QueueLoggerService.js'; import type * as Bull from 'bullmq'; import type { DbUserDeleteJobData } from '../types.js'; @@ -23,6 +25,9 @@ export class DeleteAccountProcessorService { private logger: Logger; constructor( + @Inject(DI.db) + private db: DataSource, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -125,7 +130,9 @@ export class DeleteAccountProcessorService { if (job.data.soft) { // nop } else { - await this.usersRepository.delete(job.data.user.id); + await extendTimeoutQuery(this.db, async (manager) => { + await manager.delete(MiUser, job.data.user.id); + }); } return 'Account deleted'; From 00b09924a6b085e62a8ed1dc70b819e5b4639b4b Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 14 Apr 2025 00:00:07 +0900 Subject: [PATCH 2/2] chore: extend for deleting notes --- .../src/queue/processors/DeleteAccountProcessorService.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index a6440b5c74..55aaff8cf9 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -11,7 +11,7 @@ import { MiUser } from '@/models/User.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; -import type { MiNote } from '@/models/Note.js'; +import { MiNote } from '@/models/Note.js'; import { EmailService } from '@/core/EmailService.js'; import { bindThis } from '@/decorators.js'; import { SearchService } from '@/core/SearchService.js'; @@ -78,7 +78,9 @@ export class DeleteAccountProcessorService { cursor = notes.at(-1)?.id ?? null; - await this.notesRepository.delete(notes.map(note => note.id)); + await extendTimeoutQuery(this.db, async (manager) => { + await manager.delete(MiNote, notes.map(note => note.id)); + }); for (const note of notes) { await this.searchService.unindexNote(note);