feat(backend): イベント用Redisを別サーバーに分離できるように

This commit is contained in:
syuilo 2023-04-07 11:20:14 +09:00
parent f4588f3907
commit ff6d9d2860
15 changed files with 98 additions and 51 deletions

View File

@ -62,6 +62,14 @@ redis:
#prefix: example-prefix #prefix: example-prefix
#db: 1 #db: 1
#redisForPubsub:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌─────────────────────────────┐ # ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └───────────────────────────── #───┘ Elasticsearch configuration └─────────────────────────────

View File

@ -62,6 +62,14 @@ redis:
#prefix: example-prefix #prefix: example-prefix
#db: 1 #db: 1
#redisForPubsub:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌─────────────────────────────┐ # ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └───────────────────────────── #───┘ Elasticsearch configuration └─────────────────────────────

View File

@ -62,6 +62,14 @@ redis:
#prefix: example-prefix #prefix: example-prefix
#db: 1 #db: 1
#redisForPubsub:
# host: redis
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌─────────────────────────────┐ # ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └───────────────────────────── #───┘ Elasticsearch configuration └─────────────────────────────

View File

@ -35,6 +35,7 @@
- 「UIのアニメーションを減らす」 (`reduceAnimation`) で猫耳を撫でられなくなります - 「UIのアニメーションを減らす」 (`reduceAnimation`) で猫耳を撫でられなくなります
### Server ### Server
- イベント用Redisを別サーバーに分離できるように
- サーバーの全体的なパフォーマンスを向上 - サーバーの全体的なパフォーマンスを向上
- ノート作成時のパフォーマンスを向上 - ノート作成時のパフォーマンスを向上
- アンテナのタイムライン取得時のパフォーマンスを向上 - アンテナのタイムライン取得時のパフォーマンスを向上

View File

@ -78,10 +78,19 @@ db:
redis: redis:
host: localhost host: localhost
port: 6379 port: 6379
#family: 0 # 0=Both, 4=IPv4, 6=IPv6
#pass: example-pass #pass: example-pass
#prefix: example-prefix #prefix: example-prefix
#db: 1 #db: 1
#redisForPubsub:
# host: localhost
# port: 6379
# #family: 0 # 0=Both, 4=IPv4, 6=IPv6
# #pass: example-pass
# #prefix: example-prefix
# #db: 1
# ┌─────────────────────────────┐ # ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └───────────────────────────── #───┘ Elasticsearch configuration └─────────────────────────────

View File

@ -1,8 +1,15 @@
import Redis from 'ioredis';
import { loadConfig } from './built/config.js'; import { loadConfig } from './built/config.js';
import { createRedisConnection } from './built/redis.js';
const config = loadConfig(); const config = loadConfig();
const redis = createRedisConnection(config); const redis = new Redis({
port: config.redis.port,
host: config.redis.host,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:`,
db: config.redis.db ?? 0,
});
redis.on('connect', () => redis.disconnect()); redis.on('connect', () => redis.disconnect());
redis.on('error', (e) => { redis.on('error', (e) => {

View File

@ -2,18 +2,15 @@ import { setTimeout } from 'node:timers/promises';
import { Global, Inject, Module } from '@nestjs/common'; import { Global, Inject, Module } from '@nestjs/common';
import Redis from 'ioredis'; import Redis from 'ioredis';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { createRedisConnection } from '@/redis.js';
import { DI } from './di-symbols.js'; import { DI } from './di-symbols.js';
import { loadConfig } from './config.js'; import { loadConfig } from './config.js';
import { createPostgresDataSource } from './postgres.js'; import { createPostgresDataSource } from './postgres.js';
import { RepositoryModule } from './models/RepositoryModule.js'; import { RepositoryModule } from './models/RepositoryModule.js';
import type { Provider, OnApplicationShutdown } from '@nestjs/common'; import type { Provider, OnApplicationShutdown } from '@nestjs/common';
const config = loadConfig();
const $config: Provider = { const $config: Provider = {
provide: DI.config, provide: DI.config,
useValue: config, useValue: loadConfig(),
}; };
const $db: Provider = { const $db: Provider = {
@ -28,18 +25,31 @@ const $db: Provider = {
const $redis: Provider = { const $redis: Provider = {
provide: DI.redis, provide: DI.redis,
useFactory: (config) => { useFactory: (config) => {
const redisClient = createRedisConnection(config); return new Redis({
return redisClient; port: config.redis.port,
host: config.redis.host,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:`,
db: config.redis.db ?? 0,
});
}, },
inject: [DI.config], inject: [DI.config],
}; };
const $redisSubscriber: Provider = { const $redisForPubsub: Provider = {
provide: DI.redisSubscriber, provide: DI.redisForPubsub,
useFactory: (config) => { useFactory: (config) => {
const redisSubscriber = createRedisConnection(config); const redis = new Redis({
redisSubscriber.subscribe(config.host); port: config.redisForPubsub.port,
return redisSubscriber; host: config.redisForPubsub.host,
family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
password: config.redisForPubsub.pass,
keyPrefix: `${config.redisForPubsub.prefix}:`,
db: config.redisForPubsub.db ?? 0,
});
redis.subscribe(config.host);
return redis;
}, },
inject: [DI.config], inject: [DI.config],
}; };
@ -47,14 +57,14 @@ const $redisSubscriber: Provider = {
@Global() @Global()
@Module({ @Module({
imports: [RepositoryModule], imports: [RepositoryModule],
providers: [$config, $db, $redis, $redisSubscriber], providers: [$config, $db, $redis, $redisForPubsub],
exports: [$config, $db, $redis, $redisSubscriber, RepositoryModule], exports: [$config, $db, $redis, $redisForPubsub, RepositoryModule],
}) })
export class GlobalModule implements OnApplicationShutdown { export class GlobalModule implements OnApplicationShutdown {
constructor( constructor(
@Inject(DI.db) private db: DataSource, @Inject(DI.db) private db: DataSource,
@Inject(DI.redis) private redisClient: Redis.Redis, @Inject(DI.redis) private redisClient: Redis.Redis,
@Inject(DI.redisSubscriber) private redisSubscriber: Redis.Redis, @Inject(DI.redisForPubsub) private redisForPubsub: Redis.Redis,
) {} ) {}
async onApplicationShutdown(signal: string): Promise<void> { async onApplicationShutdown(signal: string): Promise<void> {
@ -69,7 +79,7 @@ export class GlobalModule implements OnApplicationShutdown {
await Promise.all([ await Promise.all([
this.db.destroy(), this.db.destroy(),
this.redisClient.disconnect(), this.redisClient.disconnect(),
this.redisSubscriber.disconnect(), this.redisForPubsub.disconnect(),
]); ]);
} }
} }

View File

@ -33,6 +33,14 @@ export type Source = {
db?: number; db?: number;
prefix?: string; prefix?: string;
}; };
redisForPubsub?: {
host: string;
port: number;
family?: number;
pass: string;
db?: number;
prefix?: string;
};
elasticsearch: { elasticsearch: {
host: string; host: string;
port: number; port: number;
@ -151,6 +159,7 @@ export function loadConfig() {
: null; : null;
if (!config.redis.prefix) config.redis.prefix = mixin.host; if (!config.redis.prefix) config.redis.prefix = mixin.host;
if (config.redisForPubsub == null) config.redisForPubsub = config.redis;
return Object.assign(config, mixin); return Object.assign(config, mixin);
} }

View File

@ -27,8 +27,8 @@ export class AntennaService implements OnApplicationShutdown {
@Inject(DI.redis) @Inject(DI.redis)
private redisClient: Redis.Redis, private redisClient: Redis.Redis,
@Inject(DI.redisSubscriber) @Inject(DI.redisForPubsub)
private redisSubscriber: Redis.Redis, private redisForPubsub: Redis.Redis,
@Inject(DI.mutingsRepository) @Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository, private mutingsRepository: MutingsRepository,
@ -52,12 +52,12 @@ export class AntennaService implements OnApplicationShutdown {
this.antennasFetched = false; this.antennasFetched = false;
this.antennas = []; this.antennas = [];
this.redisSubscriber.on('message', this.onRedisMessage); this.redisForPubsub.on('message', this.onRedisMessage);
} }
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
this.redisSubscriber.off('message', this.onRedisMessage); this.redisForPubsub.off('message', this.onRedisMessage);
} }
@bindThis @bindThis

View File

@ -14,8 +14,8 @@ export class MetaService implements OnApplicationShutdown {
private intervalId: NodeJS.Timer; private intervalId: NodeJS.Timer;
constructor( constructor(
@Inject(DI.redisSubscriber) @Inject(DI.redisForPubsub)
private redisSubscriber: Redis.Redis, private redisForPubsub: Redis.Redis,
@Inject(DI.db) @Inject(DI.db)
private db: DataSource, private db: DataSource,
@ -33,7 +33,7 @@ export class MetaService implements OnApplicationShutdown {
}, 1000 * 60 * 5); }, 1000 * 60 * 5);
} }
this.redisSubscriber.on('message', this.onMessage); this.redisForPubsub.on('message', this.onMessage);
} }
@bindThis @bindThis
@ -122,6 +122,6 @@ export class MetaService implements OnApplicationShutdown {
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
clearInterval(this.intervalId); clearInterval(this.intervalId);
this.redisSubscriber.off('message', this.onMessage); this.redisForPubsub.off('message', this.onMessage);
} }
} }

View File

@ -64,8 +64,8 @@ export class RoleService implements OnApplicationShutdown {
public static NotAssignedError = class extends Error {}; public static NotAssignedError = class extends Error {};
constructor( constructor(
@Inject(DI.redisSubscriber) @Inject(DI.redisForPubsub)
private redisSubscriber: Redis.Redis, private redisForPubsub: Redis.Redis,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -87,7 +87,7 @@ export class RoleService implements OnApplicationShutdown {
this.rolesCache = new MemorySingleCache<Role[]>(Infinity); this.rolesCache = new MemorySingleCache<Role[]>(Infinity);
this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(Infinity); this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(Infinity);
this.redisSubscriber.on('message', this.onMessage); this.redisForPubsub.on('message', this.onMessage);
} }
@bindThis @bindThis
@ -400,6 +400,6 @@ export class RoleService implements OnApplicationShutdown {
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
this.redisSubscriber.off('message', this.onMessage); this.redisForPubsub.off('message', this.onMessage);
} }
} }

View File

@ -13,14 +13,14 @@ export class WebhookService implements OnApplicationShutdown {
private webhooks: Webhook[] = []; private webhooks: Webhook[] = [];
constructor( constructor(
@Inject(DI.redisSubscriber) @Inject(DI.redisForPubsub)
private redisSubscriber: Redis.Redis, private redisForPubsub: Redis.Redis,
@Inject(DI.webhooksRepository) @Inject(DI.webhooksRepository)
private webhooksRepository: WebhooksRepository, private webhooksRepository: WebhooksRepository,
) { ) {
//this.onMessage = this.onMessage.bind(this); //this.onMessage = this.onMessage.bind(this);
this.redisSubscriber.on('message', this.onMessage); this.redisForPubsub.on('message', this.onMessage);
} }
@bindThis @bindThis
@ -82,6 +82,6 @@ export class WebhookService implements OnApplicationShutdown {
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined) { public onApplicationShutdown(signal?: string | undefined) {
this.redisSubscriber.off('message', this.onMessage); this.redisForPubsub.off('message', this.onMessage);
} }
} }

View File

@ -2,7 +2,7 @@ export const DI = {
config: Symbol('config'), config: Symbol('config'),
db: Symbol('db'), db: Symbol('db'),
redis: Symbol('redis'), redis: Symbol('redis'),
redisSubscriber: Symbol('redisSubscriber'), redisForPubsub: Symbol('redisForPubsub'),
//#region Repositories //#region Repositories
usersRepository: Symbol('usersRepository'), usersRepository: Symbol('usersRepository'),

View File

@ -1,13 +0,0 @@
import Redis from 'ioredis';
import { Config } from '@/config.js';
export function createRedisConnection(config: Config): Redis.Redis {
return new Redis({
port: config.redis.port,
host: config.redis.host,
family: config.redis.family == null ? 0 : config.redis.family,
password: config.redis.pass,
keyPrefix: `${config.redis.prefix}:`,
db: config.redis.db ?? 0,
});
}

View File

@ -22,8 +22,8 @@ export class StreamingApiServerService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.redisSubscriber) @Inject(DI.redisForPubsub)
private redisSubscriber: Redis.Redis, private redisForPubsub: Redis.Redis,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -81,7 +81,7 @@ export class StreamingApiServerService {
ev.emit(parsed.channel, parsed.message); ev.emit(parsed.channel, parsed.message);
} }
this.redisSubscriber.on('message', onRedisMessage); this.redisForPubsub.on('message', onRedisMessage);
const main = new MainStreamConnection( const main = new MainStreamConnection(
this.channelsService, this.channelsService,
@ -111,7 +111,7 @@ export class StreamingApiServerService {
connection.once('close', () => { connection.once('close', () => {
ev.removeAllListeners(); ev.removeAllListeners();
main.dispose(); main.dispose();
this.redisSubscriber.off('message', onRedisMessage); this.redisForPubsub.off('message', onRedisMessage);
if (intervalId) clearInterval(intervalId); if (intervalId) clearInterval(intervalId);
}); });