From 35888eb8f49332d4f4e4cc38a4bd8a9b3575345a Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 31 Jul 2025 18:16:21 +0900 Subject: enhance(backend): BullMQの廃止されたRepeatableからJob Schedulersに移行 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/QueueService.ts | 92 +++++++++++++++++++------------ 1 file changed, 58 insertions(+), 34 deletions(-) (limited to 'packages/backend/src/core/QueueService.ts') diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 04bbc7e38a..fce8cc2067 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -17,6 +17,7 @@ import { bindThis } from '@/decorators.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; import { type SystemWebhookPayload } from '@/core/SystemWebhookService.js'; +import type { Packed } from '@/misc/json-schema.js'; import { type UserWebhookPayload } from './UserWebhookService.js'; import type { DbJobData, @@ -39,7 +40,6 @@ import type { } from './QueueModule.js'; import type httpSignature from '@peertube/http-signature'; import type * as Bull from 'bullmq'; -import type { Packed } from '@/misc/json-schema.js'; export const QUEUE_TYPES = [ 'system', @@ -69,61 +69,85 @@ export class QueueService { @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue, @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue, ) { - this.systemQueue.add('tickCharts', { + this.systemQueue.upsertJobScheduler('tickCharts', { + pattern: '55 * * * *', }, { - repeat: { pattern: '55 * * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + name: 'tickCharts', + opts: { + removeOnComplete: 10, + removeOnFail: 30, + }, }); - this.systemQueue.add('resyncCharts', { + this.systemQueue.upsertJobScheduler('resyncCharts', { + pattern: '0 0 * * *', }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + name: 'resyncCharts', + opts: { + removeOnComplete: 10, + removeOnFail: 30, + }, }); - this.systemQueue.add('cleanCharts', { + this.systemQueue.upsertJobScheduler('cleanCharts', { + pattern: '0 0 * * *', }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + name: 'cleanCharts', + opts: { + removeOnComplete: 10, + removeOnFail: 30, + }, }); - this.systemQueue.add('aggregateRetention', { + this.systemQueue.upsertJobScheduler('aggregateRetention', { + pattern: '0 0 * * *', }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + name: 'aggregateRetention', + opts: { + removeOnComplete: 10, + removeOnFail: 30, + }, }); - this.systemQueue.add('clean', { + this.systemQueue.upsertJobScheduler('clean', { + pattern: '0 0 * * *', }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + name: 'clean', + opts: { + removeOnComplete: 10, + removeOnFail: 30, + }, }); - this.systemQueue.add('checkExpiredMutings', { + this.systemQueue.upsertJobScheduler('checkExpiredMutings', { + pattern: '*/5 * * * *', }, { - repeat: { pattern: '*/5 * * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + name: 'checkExpiredMutings', + opts: { + removeOnComplete: 10, + removeOnFail: 30, + }, }); - this.systemQueue.add('bakeBufferedReactions', { + this.systemQueue.upsertJobScheduler('bakeBufferedReactions', { + pattern: '0 0 * * *', }, { - repeat: { pattern: '0 0 * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + name: 'bakeBufferedReactions', + opts: { + removeOnComplete: 10, + removeOnFail: 30, + }, }); - this.systemQueue.add('checkModeratorsActivity', { - }, { + this.systemQueue.upsertJobScheduler('checkModeratorsActivity', { // 毎時30分に起動 - repeat: { pattern: '30 * * * *' }, - removeOnComplete: 10, - removeOnFail: 30, + pattern: '30 * * * *', + }, { + name: 'checkModeratorsActivity', + opts: { + removeOnComplete: 10, + removeOnFail: 30, + }, }); } -- cgit v1.2.3-freya From 862a6fae793271928fc05de0dfc22a473d3af2d2 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 31 Jul 2025 20:57:36 +0900 Subject: enhance(backend): 古いバージョンで作成され現在使われなくなったrepeatableジョブをクリーンアップするように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/QueueService.ts | 123 +++++++++++------------------- 1 file changed, 45 insertions(+), 78 deletions(-) (limited to 'packages/backend/src/core/QueueService.ts') diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index fce8cc2067..31de66ca88 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -53,6 +53,33 @@ export const QUEUE_TYPES = [ 'systemWebhookDeliver', ] as const; +const REPEATABLE_SYSTEM_JOB_DEF = [{ + name: 'tickCharts', + pattern: '55 * * * *', +}, { + name: 'resyncCharts', + pattern: '0 0 * * *', +}, { + name: 'cleanCharts', + pattern: '0 0 * * *', +}, { + name: 'aggregateRetention', + pattern: '0 0 * * *', +}, { + name: 'clean', + pattern: '0 0 * * *', +}, { + name: 'checkExpiredMutings', + pattern: '*/5 * * * *', +}, { + name: 'bakeBufferedReactions', + pattern: '0 0 * * *', +}, { + name: 'checkModeratorsActivity', + // 毎時30分に起動 + pattern: '30 * * * *', +}]; + @Injectable() export class QueueService { constructor( @@ -69,85 +96,25 @@ export class QueueService { @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue, @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue, ) { - this.systemQueue.upsertJobScheduler('tickCharts', { - pattern: '55 * * * *', - }, { - name: 'tickCharts', - opts: { - removeOnComplete: 10, - removeOnFail: 30, - }, - }); - - this.systemQueue.upsertJobScheduler('resyncCharts', { - pattern: '0 0 * * *', - }, { - name: 'resyncCharts', - opts: { - removeOnComplete: 10, - removeOnFail: 30, - }, - }); - - this.systemQueue.upsertJobScheduler('cleanCharts', { - pattern: '0 0 * * *', - }, { - name: 'cleanCharts', - opts: { - removeOnComplete: 10, - removeOnFail: 30, - }, - }); - - this.systemQueue.upsertJobScheduler('aggregateRetention', { - pattern: '0 0 * * *', - }, { - name: 'aggregateRetention', - opts: { - removeOnComplete: 10, - removeOnFail: 30, - }, - }); - - this.systemQueue.upsertJobScheduler('clean', { - pattern: '0 0 * * *', - }, { - name: 'clean', - opts: { - removeOnComplete: 10, - removeOnFail: 30, - }, - }); - - this.systemQueue.upsertJobScheduler('checkExpiredMutings', { - pattern: '*/5 * * * *', - }, { - name: 'checkExpiredMutings', - opts: { - removeOnComplete: 10, - removeOnFail: 30, - }, - }); - - this.systemQueue.upsertJobScheduler('bakeBufferedReactions', { - pattern: '0 0 * * *', - }, { - name: 'bakeBufferedReactions', - opts: { - removeOnComplete: 10, - removeOnFail: 30, - }, - }); + for (const def of REPEATABLE_SYSTEM_JOB_DEF) { + this.systemQueue.upsertJobScheduler(def.name, { + pattern: def.pattern, + }, { + name: def.name, + opts: { + removeOnComplete: 10, + removeOnFail: 30, + }, + }); + } - this.systemQueue.upsertJobScheduler('checkModeratorsActivity', { - // 毎時30分に起動 - pattern: '30 * * * *', - }, { - name: 'checkModeratorsActivity', - opts: { - removeOnComplete: 10, - removeOnFail: 30, - }, + // 古いバージョンで作成され現在使われなくなったrepeatableジョブをクリーンアップ + this.systemQueue.getJobSchedulers().then(schedulers => { + for (const scheduler of schedulers) { + if (!REPEATABLE_SYSTEM_JOB_DEF.some(def => def.name === scheduler.key)) { + this.systemQueue.removeJobScheduler(scheduler.key); + } + } }); } -- cgit v1.2.3-freya From a7d1c94f4819c537111a21ffc1cd467a94698e20 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 1 Aug 2025 09:51:43 +0900 Subject: enhance(backend): tweak system job log --- packages/backend/src/core/QueueService.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'packages/backend/src/core/QueueService.ts') diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 31de66ca88..2e49f8cf5e 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -102,8 +102,13 @@ export class QueueService { }, { name: def.name, opts: { - removeOnComplete: 10, - removeOnFail: 30, + // 期限ではなくcountで設定したいが、ジョブごとではなくキュー全体でカウントされるため、高頻度で実行されるジョブによって低頻度で実行されるジョブのログが消えることになる + removeOnComplete: { + age: 3600 * 24 * 7, // keep up to 7 days + }, + removeOnFail: { + age: 3600 * 24 * 7, // keep up to 7 days + }, }, }); } -- cgit v1.2.3-freya From d624da9c1aac731bd49a7bbb949744ebf4986479 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:49:12 +0900 Subject: feat: remote notes cleaning (#16292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create CleanRemoteNotesProcessorService.ts * Update CleanRemoteNotesProcessorService.ts * Update CleanRemoteNotesProcessorService.ts * wip * Update CleanRemoteNotesProcessorService.ts * Update CleanRemoteNotesProcessorService.ts * Update CleanRemoteNotesProcessorService.ts * Update CleanRemoteNotesProcessorService.ts * Update CleanRemoteNotesProcessorService.ts * Update CleanRemoteNotesProcessorService.ts * Update CleanRemoteNotesProcessorService.ts * Update CleanRemoteNotesProcessorService.ts * Update job-queue.job.vue * wip * Update CleanRemoteNotesProcessorService.ts * wip * wip * wip * Update CleanRemoteNotesProcessorService.ts * wip * Update CHANGELOG.md * Revert "wip" This reverts commit 89d455d302c1106c421bcec309fd7bf02509465e. * wip * woip * Update QueueService.ts * Update QueueService.ts * ピン留め考慮 * Update CleanRemoteNotesProcessorService.ts * Update QueueService.ts * Update CleanRemoteNotesProcessorService.ts * add log * Update CHANGELOG.md * wip * Update MkServerSetupWizard.vue --- CHANGELOG.md | 3 + locales/index.d.ts | 32 ++++ locales/ja-JP.yml | 8 + .../migration/1753863104203-remoteNotesCleaning.js | 20 +++ packages/backend/src/core/QueueService.ts | 4 + packages/backend/src/models/Meta.ts | 15 ++ packages/backend/src/queue/QueueProcessorModule.ts | 4 +- .../backend/src/queue/QueueProcessorService.ts | 3 + .../processors/CleanRemoteNotesProcessorService.ts | 174 +++++++++++++++++++++ .../backend/src/server/api/endpoints/admin/meta.ts | 15 ++ .../src/server/api/endpoints/admin/update-meta.ts | 15 ++ .../src/components/MkServerSetupWizard.vue | 15 +- .../frontend/src/pages/admin/job-queue.job.vue | 4 +- packages/frontend/src/pages/admin/performance.vue | 42 +++++ packages/misskey-js/src/autogen/types.ts | 6 + 15 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 packages/backend/migration/1753863104203-remoteNotesCleaning.js create mode 100644 packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts (limited to 'packages/backend/src/core/QueueService.ts') diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e2a82e574..4025f8ab44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ ### General - ノートを削除した際、関連するノートが同時に削除されないようになりました - APIで、「replyIdが存在しているのにreplyがnull」や「renoteIdが存在しているのにrenoteがnull」であるという、今までにはなかったパターンが表れることになります +- 定期的に参照されていない古いリモートの投稿を削除する機能が実装されました(コントロールパネル→パフォーマンス→Remote Notes Cleaning) + - **デフォルトでオン**になっています + - データベースの肥大化を防止することが可能です ### Client - Fix: 一部の設定検索結果が存在しないパスになる問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index 1be8811a25..6bb6d59476 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5493,6 +5493,14 @@ export interface Locale extends ILocale { * 低くすると画質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、画質は低下します。 */ "defaultImageCompressionLevel_description": string; + /** + * 分 + */ + "inMinutes": string; + /** + * 日 + */ + "inDays": string; "_order": { /** * 新しい順 @@ -6486,6 +6494,22 @@ export interface Locale extends ILocale { * 有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。 */ "reactionsBufferingDescription": string; + /** + * リモート投稿の自動クリーニング + */ + "remoteNotesCleaning": string; + /** + * 有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。 + */ + "remoteNotesCleaning_description": string; + /** + * 最大クリーニング処理継続時間 + */ + "remoteNotesCleaningMaxProcessingDuration": string; + /** + * 最低ノート保持日数 + */ + "remoteNotesCleaningExpiryDaysForEachNotes": string; /** * 問い合わせ先URL */ @@ -11951,6 +11975,14 @@ export interface Locale extends ILocale { * 連合可能なサーバーの指定など、高度な設定も後ほど可能です。 */ "youCanConfigureMoreFederationSettingsLater": string; + /** + * 受信コンテンツの自動クリーニング + */ + "remoteContentsCleaning": string; + /** + * 連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。 + */ + "remoteContentsCleaning_description": string; /** * 管理者情報 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d4edfc5aab..f141d23ecc 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1368,6 +1368,8 @@ redisplayAllTips: "全ての「ヒントとコツ」を再表示" hideAllTips: "全ての「ヒントとコツ」を非表示" defaultImageCompressionLevel: "デフォルトの画像圧縮度" defaultImageCompressionLevel_description: "低くすると画質を保てますが、ファイルサイズは増加します。
高くするとファイルサイズを減らせますが、画質は低下します。" +inMinutes: "分" +inDays: "日" _order: newest: "新しい順" @@ -1649,6 +1651,10 @@ _serverSettings: fanoutTimelineDbFallback: "データベースへのフォールバック" fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。" reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。" + remoteNotesCleaning: "リモート投稿の自動クリーニング" + remoteNotesCleaning_description: "有効にすると、参照されていない古いリモートの投稿を定期的にクリーンアップしてデータベースの肥大化を抑制します。" + remoteNotesCleaningMaxProcessingDuration: "最大クリーニング処理継続時間" + remoteNotesCleaningExpiryDaysForEachNotes: "最低ノート保持日数" inquiryUrl: "問い合わせ先URL" inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。" openRegistration: "アカウントの作成をオープンにする" @@ -3196,6 +3202,8 @@ _serverSetupWizard: doYouConnectToFediverse_description1: "分散型サーバーで構成されるネットワーク(Fediverse)に接続すると、他のサーバーと相互にコンテンツのやり取りが可能です。" doYouConnectToFediverse_description2: "Fediverseと接続することは「連合」とも呼ばれます。" youCanConfigureMoreFederationSettingsLater: "連合可能なサーバーの指定など、高度な設定も後ほど可能です。" + remoteContentsCleaning: "受信コンテンツの自動クリーニング" + remoteContentsCleaning_description: "連合を行うと、継続して多くのコンテンツを受信します。自動クリーニングを有効にすると、参照されていない古くなったコンテンツを自動でサーバーから削除し、ストレージを節約できます。" adminInfo: "管理者情報" adminInfo_description: "問い合わせを受け付けるために使用される管理者情報を設定します。" adminInfo_mustBeFilled: "オープンサーバー、または連合がオンの場合は必ず入力が必要です。" diff --git a/packages/backend/migration/1753863104203-remoteNotesCleaning.js b/packages/backend/migration/1753863104203-remoteNotesCleaning.js new file mode 100644 index 0000000000..37d42a571d --- /dev/null +++ b/packages/backend/migration/1753863104203-remoteNotesCleaning.js @@ -0,0 +1,20 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RemoteNotesCleaning1753863104203 { + name = 'RemoteNotesCleaning1753863104203' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableRemoteNotesCleaning" boolean NOT NULL DEFAULT true`); + await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningMaxProcessingDurationInMinutes" integer NOT NULL DEFAULT \'60\''); + await queryRunner.query('ALTER TABLE "meta" ADD "remoteNotesCleaningExpiryDaysForEachNotes" integer NOT NULL DEFAULT \'90\''); + } + + async down(queryRunner) { + await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningExpiryDaysForEachNotes"'); + await queryRunner.query('ALTER TABLE "meta" DROP COLUMN "remoteNotesCleaningMaxProcessingDurationInMinutes"'); + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableRemoteNotesCleaning"`); + } +} diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 2e49f8cf5e..06170b242a 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -78,6 +78,10 @@ const REPEATABLE_SYSTEM_JOB_DEF = [{ name: 'checkModeratorsActivity', // 毎時30分に起動 pattern: '30 * * * *', +}, { + name: 'cleanRemoteNotes', + // 毎日午前4時に起動(最も人の少ない時間帯) + pattern: '0 4 * * *', }]; @Injectable() diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 85c10ab666..c97fcd8dfc 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -701,6 +701,21 @@ export class MiMeta { default: true, }) public allowExternalApRedirect: boolean; + + @Column('boolean', { + default: true, + }) + public enableRemoteNotesCleaning: boolean; + + @Column('integer', { + default: 60, // minutes + }) + public remoteNotesCleaningMaxProcessingDurationInMinutes: number; + + @Column('integer', { + default: 90, // days + }) + public remoteNotesCleaningExpiryDaysForEachNotes: number; } export type SoftwareSuspension = { diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index 9044285bf6..e01414cd53 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -6,7 +6,6 @@ import { Module } from '@nestjs/common'; import { CoreModule } from '@/core/CoreModule.js'; import { GlobalModule } from '@/GlobalModule.js'; -import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; import { QueueProcessorService } from './QueueProcessorService.js'; import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; @@ -18,6 +17,8 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js'; import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; +import { CheckModeratorsActivityProcessorService } from './processors/CheckModeratorsActivityProcessorService.js'; +import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js'; import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js'; import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js'; import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesProcessorService.js'; @@ -83,6 +84,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor AggregateRetentionProcessorService, CheckExpiredMutingsProcessorService, CheckModeratorsActivityProcessorService, + CleanRemoteNotesProcessorService, QueueProcessorService, ], exports: [ diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index c98ebcdcd9..7b64182754 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -43,6 +43,7 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js'; import { CleanProcessorService } from './processors/CleanProcessorService.js'; import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js'; +import { CleanRemoteNotesProcessorService } from './processors/CleanRemoteNotesProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; import { QUEUE, baseWorkerOptions } from './const.js'; @@ -123,6 +124,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService, private checkModeratorsActivityProcessorService: CheckModeratorsActivityProcessorService, private cleanProcessorService: CleanProcessorService, + private cleanRemoteNotesProcessorService: CleanRemoteNotesProcessorService, ) { this.logger = this.queueLoggerService.logger; @@ -164,6 +166,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process(); case 'checkModeratorsActivity': return this.checkModeratorsActivityProcessorService.process(); case 'clean': return this.cleanProcessorService.process(); + case 'cleanRemoteNotes': return this.cleanRemoteNotesProcessorService.process(job); default: throw new Error(`unrecognized job type ${job.name} for system`); } }; diff --git a/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts new file mode 100644 index 0000000000..5b682e20b8 --- /dev/null +++ b/packages/backend/src/queue/processors/CleanRemoteNotesProcessorService.ts @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { setTimeout } from 'node:timers/promises'; +import { Inject, Injectable } from '@nestjs/common'; +import { And, In, IsNull, LessThan, MoreThan, Not } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { MiMeta, MiNote, NoteFavoritesRepository, NotesRepository, UserNotePiningsRepository } from '@/models/_.js'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { IdService } from '@/core/IdService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; +import type * as Bull from 'bullmq'; + +@Injectable() +export class CleanRemoteNotesProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.meta) + private meta: MiMeta, + + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + + @Inject(DI.noteFavoritesRepository) + private noteFavoritesRepository: NoteFavoritesRepository, + + @Inject(DI.userNotePiningsRepository) + private userNotePiningsRepository: UserNotePiningsRepository, + + private idService: IdService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('clean-remote-notes'); + } + + @bindThis + public async process(job: Bull.Job>): Promise<{ + deletedCount: number; + oldest: number | null; + newest: number | null; + skipped?: boolean; + }> { + if (!this.meta.enableRemoteNotesCleaning) { + this.logger.info('Remote notes cleaning is disabled, skipping...'); + return { + deletedCount: 0, + oldest: null, + newest: null, + skipped: true, + }; + } + + this.logger.info('cleaning remote notes...'); + + const maxDuration = this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000; // Convert minutes to milliseconds + const startAt = Date.now(); + + const MAX_NOTE_COUNT_PER_QUERY = 50; + + const stats = { + deletedCount: 0, + oldest: null as number | null, + newest: null as number | null, + }; + + let cursor: MiNote['id'] = this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes)); + + while (true) { + const batchBeginAt = Date.now(); + + let notes: Pick[] = await this.notesRepository.find({ + where: { + id: LessThan(cursor), + userHost: Not(IsNull()), + clippedCount: 0, + renoteCount: 0, + }, + take: MAX_NOTE_COUNT_PER_QUERY, + order: { + // 新しい順 + // https://github.com/misskey-dev/misskey/pull/16292#issuecomment-3139376314 + id: -1, + }, + select: ['id'], + }); + + const fetchedCount = notes.length; + + for (const note of notes) { + if (note.id < cursor) { + cursor = note.id; + } + } + + const pinings = notes.length === 0 ? [] : await this.userNotePiningsRepository.find({ + where: { + noteId: In(notes.map(note => note.id)), + }, + select: ['noteId'], + }); + + notes = notes.filter(note => { + return !pinings.some(pining => pining.noteId === note.id); + }); + + const favorites = notes.length === 0 ? [] : await this.noteFavoritesRepository.find({ + where: { + noteId: In(notes.map(note => note.id)), + }, + select: ['noteId'], + }); + + notes = notes.filter(note => { + return !favorites.some(favorite => favorite.noteId === note.id); + }); + + const replies = notes.length === 0 ? [] : await this.notesRepository.find({ + where: { + replyId: In(notes.map(note => note.id)), + userHost: IsNull(), + }, + select: ['replyId'], + }); + + notes = notes.filter(note => { + return !replies.some(reply => reply.replyId === note.id); + }); + + if (notes.length > 0) { + await this.notesRepository.delete(notes.map(note => note.id)); + + for (const note of notes) { + const t = this.idService.parse(note.id).date.getTime(); + if (stats.oldest === null || t < stats.oldest) { + stats.oldest = t; + } + if (stats.newest === null || t > stats.newest) { + stats.newest = t; + } + } + + stats.deletedCount += notes.length; + } + + job.log(`Deleted ${notes.length} of ${fetchedCount}; ${Date.now() - batchBeginAt}ms`); + + const elapsed = Date.now() - startAt; + + if (elapsed >= maxDuration) { + this.logger.info(`Reached maximum duration of ${maxDuration}ms, stopping...`); + job.log('Reached maximum duration, stopping cleaning.'); + job.updateProgress(100); + break; + } + + job.updateProgress((elapsed / maxDuration) * 100); + + await setTimeout(1000 * 5); // Wait a moment to avoid overwhelming the db + } + + this.logger.succ('cleaning of remote notes completed.'); + + return { + deletedCount: stats.deletedCount, + oldest: stats.oldest, + newest: stats.newest, + skipped: false, + }; + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 924163afbb..4d3f6d6cd8 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -571,6 +571,18 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + enableRemoteNotesCleaning: { + type: 'boolean', + optional: false, nullable: false, + }, + remoteNotesCleaningExpiryDaysForEachNotes: { + type: 'number', + optional: false, nullable: false, + }, + remoteNotesCleaningMaxProcessingDurationInMinutes: { + type: 'number', + optional: false, nullable: false, + }, }, }, } as const; @@ -722,6 +734,9 @@ export default class extends Endpoint { // eslint- proxyRemoteFiles: instance.proxyRemoteFiles, signToActivityPubGet: instance.signToActivityPubGet, allowExternalApRedirect: instance.allowExternalApRedirect, + enableRemoteNotesCleaning: instance.enableRemoteNotesCleaning, + remoteNotesCleaningExpiryDaysForEachNotes: instance.remoteNotesCleaningExpiryDaysForEachNotes, + remoteNotesCleaningMaxProcessingDurationInMinutes: instance.remoteNotesCleaningMaxProcessingDurationInMinutes, }; }); } diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 578aa2b662..08cea23119 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -205,6 +205,9 @@ export const paramDef = { proxyRemoteFiles: { type: 'boolean' }, signToActivityPubGet: { type: 'boolean' }, allowExternalApRedirect: { type: 'boolean' }, + enableRemoteNotesCleaning: { type: 'boolean' }, + remoteNotesCleaningExpiryDaysForEachNotes: { type: 'number' }, + remoteNotesCleaningMaxProcessingDurationInMinutes: { type: 'number' }, }, required: [], } as const; @@ -723,6 +726,18 @@ export default class extends Endpoint { // eslint- set.allowExternalApRedirect = ps.allowExternalApRedirect; } + if (ps.enableRemoteNotesCleaning !== undefined) { + set.enableRemoteNotesCleaning = ps.enableRemoteNotesCleaning; + } + + if (ps.remoteNotesCleaningExpiryDaysForEachNotes !== undefined) { + set.remoteNotesCleaningExpiryDaysForEachNotes = ps.remoteNotesCleaningExpiryDaysForEachNotes; + } + + if (ps.remoteNotesCleaningMaxProcessingDurationInMinutes !== undefined) { + set.remoteNotesCleaningMaxProcessingDurationInMinutes = ps.remoteNotesCleaningMaxProcessingDurationInMinutes; + } + const before = await this.metaService.fetch(true); await this.metaService.update(set); diff --git a/packages/frontend/src/components/MkServerSetupWizard.vue b/packages/frontend/src/components/MkServerSetupWizard.vue index e5614d63d7..23e0e85bc9 100644 --- a/packages/frontend/src/components/MkServerSetupWizard.vue +++ b/packages/frontend/src/components/MkServerSetupWizard.vue @@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
{{ i18n.ts._serverSetupWizard.doYouConnectToFediverse_description1 }}
{{ i18n.ts._serverSetupWizard.doYouConnectToFediverse_description2 }}
+
{{ i18n.ts._serverSetupWizard.doYouConnectToFediverse_description1 }}
{{ i18n.ts._serverSetupWizard.doYouConnectToFediverse_description2 }}
{{ i18n.ts.learnMore }}
@@ -63,6 +63,11 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._serverSetupWizard.youCanConfigureMoreFederationSettingsLater }} + + + + +
@@ -110,6 +115,10 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.federation }}:
{{ serverSettings.federation === 'none' ? i18n.ts.no : i18n.ts.all }}
+
+
{{ i18n.ts._serverSettings.remoteNotesCleaning }}:
+
{{ serverSettings.enableRemoteNotesCleaning ? i18n.ts.yes : i18n.ts.no }}
+
FTT:
{{ serverSettings.enableFanoutTimeline ? i18n.ts.yes : i18n.ts.no }}
@@ -185,7 +194,9 @@ import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; import MkFolder from '@/components/MkFolder.vue'; import MkRadios from '@/components/MkRadios.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; import MkInfo from '@/components/MkInfo.vue'; +import MkLink from '@/components/MkLink.vue'; const emit = defineEmits<{ (ev: 'finished'): void; @@ -202,6 +213,7 @@ const q_name = ref(currentMeta.name ?? ''); const q_use = ref('single'); const q_scale = ref('small'); const q_federation = ref(currentMeta.federation === 'none' ? 'no' : 'yes'); +const q_remoteContentsCleaning = ref(currentMeta.enableRemoteNotesCleaning); const q_adminName = ref(currentMeta.maintainerName ?? ''); const q_adminEmail = ref(currentMeta.maintainerEmail ?? ''); @@ -219,6 +231,7 @@ const serverSettings = computed(() => { emailRequiredForSignup: q_use.value === 'open', enableIpLogging: q_use.value === 'open', federation: q_federation.value === 'yes' ? 'all' : 'none', + enableRemoteNotesCleaning: q_remoteContentsCleaning.value, enableFanoutTimeline: true, enableFanoutTimelineDbFallback: q_use.value === 'single', enableReactionsBuffering, diff --git a/packages/frontend/src/pages/admin/job-queue.job.vue b/packages/frontend/src/pages/admin/job-queue.job.vue index 659aa02b50..4ecdb74199 100644 --- a/packages/frontend/src/pages/admin/job-queue.job.vue +++ b/packages/frontend/src/pages/admin/job-queue.job.vue @@ -98,7 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only - +
@@ -150,7 +150,7 @@ SPDX-License-Identifier: AGPL-3.0-only Update
- +
diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue index c28621b11e..ff3a5b9d7f 100644 --- a/packages/frontend/src/pages/admin/performance.vue +++ b/packages/frontend/src/pages/admin/performance.vue @@ -101,6 +101,35 @@ SPDX-License-Identifier: AGPL-3.0-only
+ + + + + + + + +
+ + + + + + +
+
@@ -196,6 +225,19 @@ const rbtForm = useForm({ fetchInstance(true); }); +const remoteNotesCleaningForm = useForm({ + enableRemoteNotesCleaning: meta.enableRemoteNotesCleaning, + remoteNotesCleaningExpiryDaysForEachNotes: meta.remoteNotesCleaningExpiryDaysForEachNotes, + remoteNotesCleaningMaxProcessingDurationInMinutes: meta.remoteNotesCleaningMaxProcessingDurationInMinutes, +}, async (state) => { + await os.apiWithDialog('admin/update-meta', { + enableRemoteNotesCleaning: state.enableRemoteNotesCleaning, + remoteNotesCleaningExpiryDaysForEachNotes: state.remoteNotesCleaningExpiryDaysForEachNotes, + remoteNotesCleaningMaxProcessingDurationInMinutes: state.remoteNotesCleaningMaxProcessingDurationInMinutes, + }); + fetchInstance(true); +}); + const headerActions = computed(() => []); const headerTabs = computed(() => []); diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 7594117deb..c0a6dca67e 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -9370,6 +9370,9 @@ export interface operations { proxyRemoteFiles: boolean; signToActivityPubGet: boolean; allowExternalApRedirect: boolean; + enableRemoteNotesCleaning: boolean; + remoteNotesCleaningExpiryDaysForEachNotes: number; + remoteNotesCleaningMaxProcessingDurationInMinutes: number; }; }; }; @@ -12599,6 +12602,9 @@ export interface operations { proxyRemoteFiles?: boolean; signToActivityPubGet?: boolean; allowExternalApRedirect?: boolean; + enableRemoteNotesCleaning?: boolean; + remoteNotesCleaningExpiryDaysForEachNotes?: number; + remoteNotesCleaningMaxProcessingDurationInMinutes?: number; }; }; }; -- cgit v1.2.3-freya From 1082145c749dd2812dd89ca4ad323d6591ebac49 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 1 Aug 2025 12:54:33 +0900 Subject: enhance: ジョブのログを表示できるように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/QueueService.ts | 7 ++ packages/backend/src/server/api/endpoint-list.ts | 1 + .../api/endpoints/admin/queue/show-job-logs.ts | 45 +++++++++++++ .../frontend/src/pages/admin/job-queue.job.vue | 9 +++ packages/misskey-js/etc/misskey-js.api.md | 8 +++ packages/misskey-js/src/autogen/apiClientJSDoc.ts | 11 ++++ packages/misskey-js/src/autogen/endpoint.ts | 3 + packages/misskey-js/src/autogen/entities.ts | 2 + packages/misskey-js/src/autogen/types.ts | 76 ++++++++++++++++++++++ 9 files changed, 162 insertions(+) create mode 100644 packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts (limited to 'packages/backend/src/core/QueueService.ts') diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 06170b242a..4be568b334 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -810,6 +810,13 @@ export class QueueService { } } + @bindThis + public async queueGetJobLogs(queueType: typeof QUEUE_TYPES[number], jobId: string) { + const queue = this.getQueue(queueType); + const result = await queue.getJobLogs(jobId); + return result.logs; + } + @bindThis public async queueGetJobs(queueType: typeof QUEUE_TYPES[number], jobTypes: JobType[], search?: string) { const RETURN_LIMIT = 100; diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts index 5c4a58a6fc..c0c43dd5c9 100644 --- a/packages/backend/src/server/api/endpoint-list.ts +++ b/packages/backend/src/server/api/endpoint-list.ts @@ -70,6 +70,7 @@ export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-dela export * as 'admin/queue/retry-job' from './endpoints/admin/queue/retry-job.js'; export * as 'admin/queue/remove-job' from './endpoints/admin/queue/remove-job.js'; export * as 'admin/queue/show-job' from './endpoints/admin/queue/show-job.js'; +export * as 'admin/queue/show-job-logs' from './endpoints/admin/queue/show-job-logs.js'; export * as 'admin/queue/promote-jobs' from './endpoints/admin/queue/promote-jobs.js'; export * as 'admin/queue/jobs' from './endpoints/admin/queue/jobs.js'; export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts b/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts new file mode 100644 index 0000000000..b9292ed12a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'read:admin:queue', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + optional: false, nullable: false, + type: 'string', + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + queue: { type: 'string', enum: QUEUE_TYPES }, + jobId: { type: 'string' }, + }, + required: ['queue', 'jobId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + private queueService: QueueService, + ) { + super(meta, paramDef, async (ps, me) => { + return this.queueService.queueGetJobLogs(ps.queue, ps.jobId); + }); + } +} diff --git a/packages/frontend/src/pages/admin/job-queue.job.vue b/packages/frontend/src/pages/admin/job-queue.job.vue index 4ecdb74199..f96830e57a 100644 --- a/packages/frontend/src/pages/admin/job-queue.job.vue +++ b/packages/frontend/src/pages/admin/job-queue.job.vue @@ -155,6 +155,10 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+ Load logs +
{{ log }}
+
@@ -198,6 +202,7 @@ const emit = defineEmits<{ const tab = ref('info'); const editData = ref(JSON5.stringify(props.job.data, null, '\t')); const canEdit = true; +const logs = ref([]); type TlType = TlEvent<{ type: 'created' | 'processed' | 'finished'; @@ -268,6 +273,10 @@ async function removeJob() { os.apiWithDialog('admin/queue/remove-job', { queue: props.queueType, jobId: props.job.id }); } +async function loadLogs() { + logs.value = await os.apiWithDialog('admin/queue/show-job-logs', { queue: props.queueType, jobId: props.job.id }); +} + // TODO // function moveJob() { // diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 4ed1c3629f..ae12547f35 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -296,6 +296,12 @@ type AdminQueueRemoveJobRequest = operations['admin___queue___remove-job']['requ // @public (undocumented) type AdminQueueRetryJobRequest = operations['admin___queue___retry-job']['requestBody']['content']['application/json']; +// @public (undocumented) +type AdminQueueShowJobLogsRequest = operations['admin___queue___show-job-logs']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminQueueShowJobLogsResponse = operations['admin___queue___show-job-logs']['responses']['200']['content']['application/json']; + // @public (undocumented) type AdminQueueShowJobRequest = operations['admin___queue___show-job']['requestBody']['content']['application/json']; @@ -1559,6 +1565,8 @@ declare namespace entities { AdminQueueRetryJobRequest, AdminQueueShowJobRequest, AdminQueueShowJobResponse, + AdminQueueShowJobLogsRequest, + AdminQueueShowJobLogsResponse, AdminQueueStatsResponse, AdminRelaysAddRequest, AdminRelaysAddResponse, diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 4a13045592..5407b7a653 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -713,6 +713,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:queue* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 5ef493946c..d7cb2a46eb 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -88,6 +88,8 @@ import type { AdminQueueRetryJobRequest, AdminQueueShowJobRequest, AdminQueueShowJobResponse, + AdminQueueShowJobLogsRequest, + AdminQueueShowJobLogsResponse, AdminQueueStatsResponse, AdminRelaysAddRequest, AdminRelaysAddResponse, @@ -717,6 +719,7 @@ export type Endpoints = { 'admin/queue/remove-job': { req: AdminQueueRemoveJobRequest; res: EmptyResponse }; 'admin/queue/retry-job': { req: AdminQueueRetryJobRequest; res: EmptyResponse }; 'admin/queue/show-job': { req: AdminQueueShowJobRequest; res: AdminQueueShowJobResponse }; + 'admin/queue/show-job-logs': { req: AdminQueueShowJobLogsRequest; res: AdminQueueShowJobLogsResponse }; 'admin/queue/stats': { req: EmptyRequest; res: AdminQueueStatsResponse }; 'admin/relays/add': { req: AdminRelaysAddRequest; res: AdminRelaysAddResponse }; 'admin/relays/list': { req: EmptyRequest; res: AdminRelaysListResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index a11bbefde5..a14febb6e6 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -91,6 +91,8 @@ export type AdminQueueRemoveJobRequest = operations['admin___queue___remove-job' export type AdminQueueRetryJobRequest = operations['admin___queue___retry-job']['requestBody']['content']['application/json']; export type AdminQueueShowJobRequest = operations['admin___queue___show-job']['requestBody']['content']['application/json']; export type AdminQueueShowJobResponse = operations['admin___queue___show-job']['responses']['200']['content']['application/json']; +export type AdminQueueShowJobLogsRequest = operations['admin___queue___show-job-logs']['requestBody']['content']['application/json']; +export type AdminQueueShowJobLogsResponse = operations['admin___queue___show-job-logs']['responses']['200']['content']['application/json']; export type AdminQueueStatsResponse = operations['admin___queue___stats']['responses']['200']['content']['application/json']; export type AdminRelaysAddRequest = operations['admin___relays___add']['requestBody']['content']['application/json']; export type AdminRelaysAddResponse = operations['admin___relays___add']['responses']['200']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index c0a6dca67e..50a96174c7 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -584,6 +584,15 @@ export type paths = { */ post: operations['admin___queue___show-job']; }; + '/admin/queue/show-job-logs': { + /** + * admin/queue/show-job-logs + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:queue* + */ + post: operations['admin___queue___show-job-logs']; + }; '/admin/queue/stats': { /** * admin/queue/stats @@ -10167,6 +10176,73 @@ export interface operations { }; }; }; + 'admin___queue___show-job-logs': { + requestBody: { + content: { + 'application/json': { + /** @enum {string} */ + queue: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver'; + jobId: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': string[]; + }; + }; + /** @description Client error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; admin___queue___stats: { responses: { /** @description OK (with results) */ -- cgit v1.2.3-freya From 7595bff43bc5f93bb0c1019796996eed9e25a4e5 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sat, 9 Aug 2025 14:37:09 +0900 Subject: fix(backend): prevent run repeatable job immediately Fix #16357 --- packages/backend/src/core/QueueService.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/backend/src/core/QueueService.ts') diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 4be568b334..0f225a8242 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -103,6 +103,7 @@ export class QueueService { for (const def of REPEATABLE_SYSTEM_JOB_DEF) { this.systemQueue.upsertJobScheduler(def.name, { pattern: def.pattern, + immediately: false, }, { name: def.name, opts: { -- cgit v1.2.3-freya