diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a43045be..04e7017001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Fix: 特定の条件下でノートがnyaizeされない問題を修正 ### Server +- Enhance: FTTのデータベースへのフォールバック処理を行うかどうかを設定可能に - Fix: トークンのないプラグインをアンインストールするときにエラーが出ないように - Fix: 投稿通知がオンでもダイレクト投稿はユーザーに通知されないようにされました - Fix: ユーザタイムラインの「ノート」選択時にリノートが混ざり込んでしまうことがある問題の修正 #12306 diff --git a/locales/index.d.ts b/locales/index.d.ts index fc6653b05b..968334e31b 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1285,6 +1285,8 @@ export interface Locale { "shortName": string; "shortNameDescription": string; "fanoutTimelineDescription": string; + "fanoutTimelineDbFallback": string; + "fanoutTimelineDbFallbackDescription": string; }; "_accountMigration": { "moveFrom": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 67a57f994c..5b1d0d62bd 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1272,6 +1272,8 @@ _serverSettings: shortName: "略称" shortNameDescription: "サーバーの正式名称が長い場合に、代わりに表示することのできる略称や通称。" fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。" + fanoutTimelineDbFallback: "データベースへのフォールバック" + fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。" _accountMigration: moveFrom: "別のアカウントからこのアカウントに移行" diff --git a/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js b/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js new file mode 100644 index 0000000000..94fa588985 --- /dev/null +++ b/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class EnableFanoutTimelineDbFallback1700096812223 { + name = 'EnableFanoutTimelineDbFallback1700096812223' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableFanoutTimelineDbFallback" boolean NOT NULL DEFAULT true`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableFanoutTimelineDbFallback"`); + } +} diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 360239f509..14a72add1d 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -494,6 +494,11 @@ export class MiMeta { }) public enableFanoutTimeline: boolean; + @Column('boolean', { + default: true, + }) + public enableFanoutTimelineDbFallback: boolean; + @Column('integer', { default: 300, }) diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 73c84a8674..cc9afaf7fd 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -295,6 +295,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + enableFanoutTimelineDbFallback: { + type: 'boolean', + optional: false, nullable: false, + }, perLocalUserUserTimelineCacheMax: { type: 'number', optional: false, nullable: false, @@ -424,6 +428,7 @@ export default class extends Endpoint { // eslint- policies: { ...DEFAULT_POLICIES, ...instance.policies }, manifestJsonOverride: instance.manifestJsonOverride, enableFanoutTimeline: instance.enableFanoutTimeline, + enableFanoutTimelineDbFallback: instance.enableFanoutTimelineDbFallback, perLocalUserUserTimelineCacheMax: instance.perLocalUserUserTimelineCacheMax, perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax, perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax, 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 c58569a31c..da3e5dd9ac 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -121,6 +121,7 @@ export const paramDef = { preservedUsernames: { type: 'array', items: { type: 'string' } }, manifestJsonOverride: { type: 'string' }, enableFanoutTimeline: { type: 'boolean' }, + enableFanoutTimelineDbFallback: { type: 'boolean' }, perLocalUserUserTimelineCacheMax: { type: 'integer' }, perRemoteUserUserTimelineCacheMax: { type: 'integer' }, perUserHomeTimelineCacheMax: { type: 'integer' }, @@ -485,6 +486,10 @@ export default class extends Endpoint { // eslint- set.enableFanoutTimeline = ps.enableFanoutTimeline; } + if (ps.enableFanoutTimelineDbFallback !== undefined) { + set.enableFanoutTimelineDbFallback = ps.enableFanoutTimelineDbFallback; + } + if (ps.perLocalUserUserTimelineCacheMax !== undefined) { set.perLocalUserUserTimelineCacheMax = ps.perLocalUserUserTimelineCacheMax; } diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index 2dda0bf2a4..d01dc50060 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -4,7 +4,8 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { MiNote, NotesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; +import { Brackets } from 'typeorm'; +import type { MiNote, MiUserList, NotesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import ActiveUsersChart from '@/core/chart/charts/active-users.js'; @@ -14,9 +15,9 @@ import { IdService } from '@/core/IdService.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { FunoutTimelineService } from '@/core/FunoutTimelineService.js'; import { QueryService } from '@/core/QueryService.js'; -import { ApiError } from '../../error.js'; -import { Brackets } from 'typeorm'; import { MiLocalUser } from '@/models/User.js'; +import { MetaService } from '@/core/MetaService.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes', 'lists'], @@ -83,7 +84,7 @@ export default class extends Endpoint { // eslint- private idService: IdService, private funoutTimelineService: FunoutTimelineService, private queryService: QueryService, - + private metaService: MetaService, ) { super(meta, paramDef, async (ps, me) => { const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null); @@ -98,6 +99,21 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.noSuchList); } + const serverSettings = await this.metaService.fetch(); + + if (!serverSettings.enableFanoutTimeline) { + return await this.getFromDb(list, { + untilId, + sinceId, + limit: ps.limit, + includeMyRenotes: ps.includeMyRenotes, + includeRenotedMyNotes: ps.includeRenotedMyNotes, + includeLocalRenotes: ps.includeLocalRenotes, + withFiles: ps.withFiles, + withRenotes: ps.withRenotes, + }, me); + } + const [ userIdsWhoMeMuting, userIdsWhoMeMutingRenotes, @@ -146,7 +162,7 @@ export default class extends Endpoint { // eslint- if (redisTimeline.length === 0) { // fallback to db - return await this.getFromDb({ + return await this.getFromDb(list, { untilId, sinceId, limit: ps.limit, @@ -155,13 +171,13 @@ export default class extends Endpoint { // eslint- includeLocalRenotes: ps.includeLocalRenotes, withFiles: ps.withFiles, withRenotes: ps.withRenotes, - }, me, list.id); + }, me); } const packedNotes = await this.noteEntityService.packMany(redisTimeline, me); if (!ps.allowPartial && redisTimeline.length < ps.limit) { - const notes = await this.getFromDb({ + const notes = await this.getFromDb(list, { untilId: redisTimeline[redisTimeline.length - 1].id, sinceId, limit: ps.limit - redisTimeline.length, @@ -170,7 +186,7 @@ export default class extends Endpoint { // eslint- includeLocalRenotes: ps.includeLocalRenotes, withFiles: ps.withFiles, withRenotes: ps.withRenotes, - }, me, list.id); + }, me); packedNotes.push(...notes); } else { process.nextTick(() => { @@ -182,7 +198,16 @@ export default class extends Endpoint { // eslint- }); } - private async getFromDb(ps: { untilId: string | null; sinceId: string | null; limit: number; includeMyRenotes: boolean; includeRenotedMyNotes: boolean; includeLocalRenotes: boolean; withFiles: boolean; withRenotes: boolean; }, me: MiLocalUser, userListId: string) { + private async getFromDb(list: MiUserList, ps: { + untilId: string | null, + sinceId: string | null, + limit: number, + includeMyRenotes: boolean, + includeRenotedMyNotes: boolean, + includeLocalRenotes: boolean, + withFiles: boolean, + withRenotes: boolean, + }, me: MiLocalUser) { //#region Construct query const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) .innerJoin(this.userListMembershipsRepository.metadata.targetName, 'userListMemberships', 'userListMemberships.userId = note.userId') @@ -191,7 +216,7 @@ export default class extends Endpoint { // eslint- .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') .leftJoinAndSelect('renote.user', 'renoteUser') - .andWhere('userListMemberships.userListId = :userListId', { userListId }) + .andWhere('userListMemberships.userListId = :userListId', { userListId: list.id }) .andWhere('note.channelId IS NULL') // チャンネルノートではない .andWhere(new Brackets(qb => { qb diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts index 832d1f490f..63952e6434 100644 --- a/packages/backend/test/unit/activitypub.ts +++ b/packages/backend/test/unit/activitypub.ts @@ -94,6 +94,7 @@ describe('ActivityPub', () => { cacheRemoteFiles: true, cacheRemoteSensitiveFiles: true, enableFanoutTimeline: true, + enableFanoutTimelineDbFallback: true, perUserHomeTimelineCacheMax: 100, perLocalUserUserTimelineCacheMax: 100, perRemoteUserUserTimelineCacheMax: 100, diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index a15be25620..86fbfa0827 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -95,6 +95,11 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + @@ -171,6 +176,7 @@ let enableServiceWorker: boolean = $ref(false); let swPublicKey: any = $ref(null); let swPrivateKey: any = $ref(null); let enableFanoutTimeline: boolean = $ref(false); +let enableFanoutTimelineDbFallback: boolean = $ref(false); let perLocalUserUserTimelineCacheMax: number = $ref(0); let perRemoteUserUserTimelineCacheMax: number = $ref(0); let perUserHomeTimelineCacheMax: number = $ref(0); @@ -192,6 +198,7 @@ async function init(): Promise { swPublicKey = meta.swPublickey; swPrivateKey = meta.swPrivateKey; enableFanoutTimeline = meta.enableFanoutTimeline; + enableFanoutTimelineDbFallback = meta.enableFanoutTimelineDbFallback; perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax; perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax; perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax; @@ -214,6 +221,7 @@ async function save(): void { swPublicKey, swPrivateKey, enableFanoutTimeline, + enableFanoutTimelineDbFallback, perLocalUserUserTimelineCacheMax, perRemoteUserUserTimelineCacheMax, perUserHomeTimelineCacheMax,