This commit is contained in:
anatawa12 2025-05-04 12:57:51 +00:00 committed by GitHub
commit e5a084ec15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 6 deletions

View File

@ -5,7 +5,7 @@
// https://github.com/typeorm/typeorm/issues/2400
import pg from 'pg';
import { DataSource, Logger, type QueryRunner } from 'typeorm';
import { DataSource, EntityManager, Logger, type QueryRunner } from 'typeorm';
import * as highlight from 'cli-highlight';
import { entities as charts } from '@/core/chart/entities.js';
import { Config } from '@/config.js';
@ -262,6 +262,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<void>): Promise<void> {
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',
@ -271,7 +298,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 ? {

View File

@ -4,16 +4,18 @@
*/
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';
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';
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,
@ -73,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);
@ -125,7 +132,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';