diff --git a/CHANGELOG.md b/CHANGELOG.md index fd56700e1f..90d875d37c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ - Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 ) - Fix: 起動前の疎通チェックが機能しなくなっていた問題を修正 (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/737) - +- Fix: リードレプリカ設定時にレコードの追加・更新・削除を伴うクエリを発行した際はmasterノードで実行されるように調整( #10897 ) ## 2024.11.0 diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts index c72bdaa727..5973abe381 100644 --- a/packages/backend/src/models/_.ts +++ b/packages/backend/src/models/_.ts @@ -3,13 +3,20 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { FindOneOptions, InsertQueryBuilder, ObjectLiteral, Repository, SelectQueryBuilder, TypeORMError } from 'typeorm'; -import { DriverUtils } from 'typeorm/driver/DriverUtils.js'; +import { + FindOneOptions, + InsertQueryBuilder, + ObjectLiteral, + QueryRunner, + Repository, + SelectQueryBuilder, +} from 'typeorm'; import { RelationCountLoader } from 'typeorm/query-builder/relation-count/RelationCountLoader.js'; import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLoader.js'; -import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js'; -import { ObjectUtils } from 'typeorm/util/ObjectUtils.js'; -import { OrmUtils } from 'typeorm/util/OrmUtils.js'; +import { + RawSqlResultsToEntityTransformer, +} from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js'; +import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; import { MiAbuseUserReport } from '@/models/AbuseUserReport.js'; import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js'; import { MiAccessToken } from '@/models/AccessToken.js'; @@ -83,7 +90,11 @@ import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialE export interface MiRepository { createTableColumnNames(this: Repository & MiRepository): string[]; + insertOne(this: Repository & MiRepository, entity: QueryDeepPartialEntity, findOptions?: Pick, 'relations'>): Promise; + + insertOneImpl(this: Repository & MiRepository, entity: QueryDeepPartialEntity, findOptions?: Pick, 'relations'>, queryRunner?: QueryRunner): Promise; + selectAliasColumnNames(this: Repository & MiRepository, queryBuilder: InsertQueryBuilder, builder: SelectQueryBuilder): void; } @@ -92,6 +103,21 @@ export const miRepository = { return this.metadata.columns.filter(column => column.isSelect && !column.isVirtual).map(column => column.databaseName); }, async insertOne(entity, findOptions?) { + const opt = this.manager.connection.options as PostgresConnectionOptions; + if (opt.replication) { + const queryRunner = this.manager.connection.createQueryRunner('master'); + try { + return this.insertOneImpl(entity, findOptions, queryRunner); + } finally { + await queryRunner.release(); + } + } else { + return this.insertOneImpl(entity, findOptions); + } + }, + async insertOneImpl(entity, findOptions?, queryRunner?) { + // ---- insert + returningの結果を共通テーブル式(CTE)に保持するクエリを生成 ---- + const queryBuilder = this.createQueryBuilder().insert().values(entity); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const mainAlias = queryBuilder.expressionMap.mainAlias!; @@ -99,7 +125,9 @@ export const miRepository = { mainAlias.name = 't'; const columnNames = this.createTableColumnNames(); queryBuilder.returning(columnNames.reduce((a, c) => `${a}, ${queryBuilder.escape(c)}`, '').slice(2)); - const builder = this.createQueryBuilder().addCommonTableExpression(queryBuilder, 'cte', { columnNames }); + + // ---- 共通テーブル式(CTE)から結果を取得 ---- + const builder = this.createQueryBuilder(undefined, queryRunner).addCommonTableExpression(queryBuilder, 'cte', { columnNames }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion builder.expressionMap.mainAlias!.tablePath = 'cte'; this.selectAliasColumnNames(queryBuilder, builder); @@ -197,7 +225,9 @@ export { }; export type AbuseUserReportsRepository = Repository & MiRepository; -export type AbuseReportNotificationRecipientRepository = Repository & MiRepository; +export type AbuseReportNotificationRecipientRepository = + Repository + & MiRepository; export type AccessTokensRepository = Repository & MiRepository; export type AdsRepository = Repository & MiRepository; export type AnnouncementsRepository = Repository & MiRepository; diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts index 251a03c303..df865b2a01 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, Logger, type QueryRunner } from 'typeorm'; import * as highlight from 'cli-highlight'; import { entities as charts } from '@/core/chart/entities.js'; @@ -90,6 +90,11 @@ export const dbLogger = new MisskeyLogger('db'); const sqlLogger = dbLogger.createSubLogger('sql', 'gray'); class MyCustomLogger implements Logger { + constructor( + private printReplicationMode?: boolean, + ) { + } + @bindThis private highlight(sql: string) { return highlight.highlight(sql, { @@ -98,18 +103,29 @@ class MyCustomLogger implements Logger { } @bindThis - public logQuery(query: string, parameters?: any[]) { - sqlLogger.info(this.highlight(query).substring(0, 100)); + private appendPrefixIfNeeded(message: string, opts?: { + queryRunner?: QueryRunner; + }): string { + if (this.printReplicationMode && opts?.queryRunner) { + return `[${opts.queryRunner.getReplicationMode()}] ${message}`; + } else { + return message; + } } @bindThis - public logQueryError(error: string, query: string, parameters?: any[]) { - sqlLogger.error(this.highlight(query)); + public logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) { + sqlLogger.info(this.appendPrefixIfNeeded(this.highlight(query).substring(0, 100), { queryRunner })); } @bindThis - public logQuerySlow(time: number, query: string, parameters?: any[]) { - sqlLogger.warn(this.highlight(query)); + public logQueryError(error: string, query: string, parameters?: any[], queryRunner?: QueryRunner) { + sqlLogger.error(this.appendPrefixIfNeeded(this.highlight(query), { queryRunner })); + } + + @bindThis + public logQuerySlow(time: number, query: string, parameters?: any[], queryRunner?: QueryRunner) { + sqlLogger.warn(this.appendPrefixIfNeeded(this.highlight(query), { queryRunner })); } @bindThis @@ -247,7 +263,7 @@ export function createPostgresDataSource(config: Config) { }, } : false, logging: log, - logger: log ? new MyCustomLogger() : undefined, + logger: log ? new MyCustomLogger(config.dbReplications) : undefined, maxQueryExecutionTime: 300, entities: entities, migrations: ['../../migration/*.js'],