Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pick redis push tl #75

Merged
merged 23 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aa2f87b
enhance(backend): RedisへのTLの構築をListで行うように
syuilo Oct 9, 2023
d5a04b0
fix of 0bb0c32908
syuilo Oct 9, 2023
23ec972
fix of 0bb0c32908
syuilo Oct 9, 2023
c941eae
feat: improve tl performance (#11946) (only updating redis TL)
anatawa12 Oct 10, 2023
c74fff1
enhance: TLキャッシュ容量を設定できるように
syuilo Oct 3, 2023
0c8e019
fix: type error (merge miss)
anatawa12 Oct 10, 2023
70f3984
enhance(backend): UserListMembershipにユーザーリストの作成者IDを非正規化
anatawa12 Oct 10, 2023
0f55162
fix: antenna become not working
anatawa12 Oct 10, 2023
a3cfff3
Revert "enhance: TLキャッシュ容量を設定できるように"
anatawa12 Oct 11, 2023
f4daa12
chore: revert configurable TL length
anatawa12 Oct 11, 2023
ab3c018
enhance: ローカルタイムライン、ソーシャルタイムラインで返信を含むかどうか設定可能に (redis更新のみ)
syuilo Oct 11, 2023
7a0125f
revert: adding withReplies, isHibernated, redisForTimelines, and user…
anatawa12 Oct 12, 2023
d8547d9
revert: deny RN of direct note
anatawa12 Oct 12, 2023
f10e159
revert: Renote Count
anatawa12 Oct 12, 2023
3646db8
fix: old roleTimeline not updated
anatawa12 Oct 12, 2023
fefdb4b
fix: withReplies remains in UserListJoining
anatawa12 Oct 12, 2023
d1dab2e
chore: use JOIN instead of Denormalized fields
anatawa12 Oct 12, 2023
27c9cbc
chore: remove userListUserId Denormalized field from MiUserListJoining
anatawa12 Oct 12, 2023
0864a99
fix: style
anatawa12 Oct 12, 2023
73cd4bd
fix: usage of userListUserId remains
anatawa12 Oct 12, 2023
690d4a5
pick フォローしているユーザーからの自分の投稿への返信がタイムラインに含まれない問題を修正
anatawa12 Oct 12, 2023
2389799
Merge branch 'develop' into pick-redis-push-tl
anatawa12 Oct 12, 2023
dc024b4
docs(changelog): 2023.10.x向けのTLを内部的に構築するようになりました
anatawa12 Oct 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .config/docker_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ redis:
# #prefix: example-prefix
# #db: 1

#redisForTimelines:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1

# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────

Expand Down
10 changes: 10 additions & 0 deletions .config/example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ redis:
# # You can specify more ioredis options...
# #username: example-username

#redisForTimelines:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# # You can specify more ioredis options...
# #username: example-username

# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────

Expand Down
8 changes: 8 additions & 0 deletions .devcontainer/devcontainer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ redis:
# #prefix: example-prefix
# #db: 1

#redisForTimelines:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1

# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────

Expand Down
8 changes: 8 additions & 0 deletions chart/files/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ redis:
# #prefix: example-prefix
# #db: 1

#redisForTimelines:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1

# ┌───────────────────────────┐
#───┘ MeiliSearch configuration └─────────────────────────────

Expand Down
2 changes: 2 additions & 0 deletions packages/backend/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,6 @@ module.exports = {
maxWorkers: 1, // Make it use worker (that can be killed and restarted)
logHeapUsage: true, // To debug when out-of-memory happens on CI
workerIdleMemoryLimit: '1GiB', // Limit the worker to 1GB (GitHub Workflows dies at 2GB)

maxConcurrency: 32,
};
20 changes: 20 additions & 0 deletions packages/backend/migration/1696222183852-withReplies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class WithReplies1696222183852 {
name = 'WithReplies1696222183852'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "following" ADD "withReplies" boolean NOT NULL DEFAULT false`);
Copy link
Collaborator Author

@anatawa12 anatawa12 Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

まだここのdefault値で悩んでます。なんか切り替える機能を実装するしかないかな

await queryRunner.query(`ALTER TABLE "user_list_joining" ADD "withReplies" boolean NOT NULL DEFAULT true`);
await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `);
}

async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`);
await queryRunner.query(`ALTER TABLE "user_list_joining" DROP COLUMN "withReplies"`);
await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "withReplies"`);
}
}
11 changes: 11 additions & 0 deletions packages/backend/migration/1696323464251-user-list-membership.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class UserListMembership1696323464251 {
name = 'UserListMembership1696323464251'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_list_joining" RENAME TO "user_list_membership"`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_list_membership" RENAME TO "user_list_joining"`);
}
}
17 changes: 17 additions & 0 deletions packages/backend/migration/1696331570827-hibernation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export class Hibernation1696331570827 {
name = 'Hibernation1696331570827'

async up(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`);
await queryRunner.query(`ALTER TABLE "user" ADD "isHibernated" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "following" ADD "isFollowerHibernated" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`CREATE INDEX "IDX_ce62b50d882d4e9dee10ad0d2f" ON "following" ("followeeId", "followerHost", "isFollowerHibernated") `);
}

async down(queryRunner) {
await queryRunner.query(`DROP INDEX "public"."IDX_ce62b50d882d4e9dee10ad0d2f"`);
await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "isFollowerHibernated"`);
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isHibernated"`);
await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `);
}
}
22 changes: 22 additions & 0 deletions packages/backend/migration/1696807733453-userListUserId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class UserListUserId1696807733453 {
name = 'UserListUserId1696807733453'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_list_membership" ADD "userListUserId" character varying(32) NOT NULL DEFAULT ''`);
const memberships = await queryRunner.query(`SELECT "id", "userListId" FROM "user_list_membership"`);
anatawa12 marked this conversation as resolved.
Show resolved Hide resolved
for(let i = 0; i < memberships.length; i++) {
const userList = await queryRunner.query(`SELECT "userId" FROM "user_list" WHERE "id" = $1`, [memberships[i].userListId]);
await queryRunner.query(`UPDATE "user_list_membership" SET "userListUserId" = $1 WHERE "id" = $2`, [userList[0].userId, memberships[i].id]);
}
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_list_membership" DROP COLUMN "userListUserId"`);
}
}
16 changes: 16 additions & 0 deletions packages/backend/migration/1696808725134-userListUserId-2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class UserListUserId21696808725134 {
name = 'UserListUserId21696808725134'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "userListUserId" DROP DEFAULT`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "userListUserId" SET DEFAULT ''`);
}
}
14 changes: 12 additions & 2 deletions packages/backend/src/GlobalModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,27 @@ const $redisForSub: Provider = {
inject: [DI.config],
};

const $redisForTimelines: Provider = {
provide: DI.redisForTimelines,
useFactory: (config: Config) => {
return new Redis.Redis(config.redisForTimelines);
},
inject: [DI.config],
};

@Global()
@Module({
imports: [RepositoryModule],
providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub],
exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, RepositoryModule],
providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines],
exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule],
})
export class GlobalModule implements OnApplicationShutdown {
constructor(
@Inject(DI.db) private db: DataSource,
@Inject(DI.redis) private redisClient: Redis.Redis,
@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
) {}

public async dispose(): Promise<void> {
Expand All @@ -98,6 +107,7 @@ export class GlobalModule implements OnApplicationShutdown {
this.redisClient.disconnect(),
this.redisForPub.disconnect(),
this.redisForSub.disconnect(),
this.redisForTimelines.disconnect(),
]);
}

Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type Source = {
redis: RedisOptionsSource;
redisForPubsub?: RedisOptionsSource;
redisForJobQueue?: RedisOptionsSource;
redisForTimelines?: RedisOptionsSource;
meilisearch?: {
host: string;
port: string;
Expand Down Expand Up @@ -167,6 +168,7 @@ export type Config = {
redis: RedisOptions & RedisOptionsSource;
redisForPubsub: RedisOptions & RedisOptionsSource;
redisForJobQueue: RedisOptions & RedisOptionsSource;
redisForTimelines: RedisOptions & RedisOptionsSource;
perChannelMaxNoteCacheCount: number;
perUserNotificationsMaxCount: number;
deactivateAntennaThreshold: number;
Expand Down Expand Up @@ -241,6 +243,7 @@ export function loadConfig(): Config {
redis,
redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
id: config.id,
proxy: config.proxy,
proxySmtp: config.proxySmtp,
Expand Down
31 changes: 16 additions & 15 deletions packages/backend/src/core/AccountMoveService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
import { bindThis } from '@/decorators.js';
import { DI } from '@/di-symbols.js';
import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/_.js';
import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
import type { RelationshipJobData, ThinUser } from '@/queue/types.js';

import { IdService } from '@/core/IdService.js';
Expand Down Expand Up @@ -42,8 +42,8 @@ export class AccountMoveService {
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,

@Inject(DI.userListJoiningsRepository)
private userListJoiningsRepository: UserListJoiningsRepository,
@Inject(DI.userListMembershipsRepository)
private userListMembershipsRepository: UserListMembershipsRepository,

@Inject(DI.instancesRepository)
private instancesRepository: InstancesRepository,
Expand Down Expand Up @@ -215,40 +215,41 @@ export class AccountMoveService {
@bindThis
public async updateLists(src: ThinUser, dst: MiUser): Promise<void> {
// Return if there is no list to be updated.
const oldJoinings = await this.userListJoiningsRepository.find({
const oldMemberships = await this.userListMembershipsRepository.find({
where: {
userId: src.id,
},
});
if (oldJoinings.length === 0) return;
if (oldMemberships.length === 0) return;

const existingUserListIds = await this.userListJoiningsRepository.find({
const existingUserListIds = await this.userListMembershipsRepository.find({
where: {
userId: dst.id,
},
}).then(joinings => joinings.map(joining => joining.userListId));
}).then(memberships => memberships.map(membership => membership.userListId));

const newJoinings: Map<string, { createdAt: Date; userId: string; userListId: string; }> = new Map();
const newMemberships: Map<string, { createdAt: Date; userId: string; userListId: string; userListUserId: string; }> = new Map();

// 重複しないようにIDを生成
const genId = (): string => {
let id: string;
do {
id = this.idService.genId();
} while (newJoinings.has(id));
} while (newMemberships.has(id));
return id;
};
for (const joining of oldJoinings) {
if (existingUserListIds.includes(joining.userListId)) continue; // skip if dst exists in this user's list
newJoinings.set(genId(), {
for (const membership of oldMemberships) {
if (existingUserListIds.includes(membership.userListId)) continue; // skip if dst exists in this user's list
newMemberships.set(genId(), {
createdAt: new Date(),
userId: dst.id,
userListId: joining.userListId,
userListId: membership.userListId,
userListUserId: membership.userListUserId,
});
}

const arrayToInsert = Array.from(newJoinings.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
await this.userListJoiningsRepository.insert(arrayToInsert);
const arrayToInsert = Array.from(newMemberships.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
await this.userListMembershipsRepository.insert(arrayToInsert);

// Have the proxy account follow the new account in the same way as UserListService.push
if (this.userEntityService.isRemoteUser(dst)) {
Expand Down
18 changes: 10 additions & 8 deletions packages/backend/src/core/AntennaService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
import * as Acct from '@/misc/acct.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
import type { AntennasRepository, UserListJoiningsRepository } from '@/models/_.js';
import type { AntennasRepository, UserListMembershipsRepository } from '@/models/_.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import type { GlobalEvents } from '@/core/GlobalEventService.js';
import { RedisTimelineService } from '@/core/RedisTimelineService.js';
import type { OnApplicationShutdown } from '@nestjs/common';

@Injectable()
Expand All @@ -24,20 +25,21 @@ export class AntennaService implements OnApplicationShutdown {
private antennas: MiAntenna[];

constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,
@Inject(DI.redisForTimelines)
private redisForTimelines: Redis.Redis,

@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,

@Inject(DI.antennasRepository)
private antennasRepository: AntennasRepository,

@Inject(DI.userListJoiningsRepository)
private userListJoiningsRepository: UserListJoiningsRepository,
@Inject(DI.userListMembershipsRepository)
private userListMembershipsRepository: UserListMembershipsRepository,

private utilityService: UtilityService,
private globalEventService: GlobalEventService,
private redisTimelineService: RedisTimelineService,
) {
this.antennasFetched = false;
this.antennas = [];
Expand Down Expand Up @@ -81,15 +83,15 @@ export class AntennaService implements OnApplicationShutdown {
const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const)));
const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna);

const redisPipeline = this.redisClient.pipeline();
const redisPipeline = this.redisForTimelines.pipeline();

for (const antenna of matchedAntennas) {
redisPipeline.xadd(
`antennaTimeline:${antenna.id}`,
'MAXLEN', '~', '200',
'*',
'note', note.id);

this.redisTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
}

Expand All @@ -108,7 +110,7 @@ export class AntennaService implements OnApplicationShutdown {
if (antenna.src === 'home') {
// TODO
} else if (antenna.src === 'list') {
const listUsers = (await this.userListJoiningsRepository.findBy({
const listUsers = (await this.userListMembershipsRepository.findBy({
userListId: antenna.userListId!,
})).map(x => x.userId);

Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/core/CacheService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js';
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
import type { MiLocalUser, MiUser } from '@/models/User.js';
import { DI } from '@/di-symbols.js';
Expand Down
Loading
Loading