mirror of
https://activitypub.software/TransFem-org/Sharkey.git
synced 2025-04-13 09:44:40 +00:00
merge: Add separate redis for rate limit (!908)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/908 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
commit
d67eefaaf5
10 changed files with 60 additions and 4 deletions
|
@ -103,6 +103,16 @@ redis:
|
||||||
# #prefix: example-prefix
|
# #prefix: example-prefix
|
||||||
# #db: 1
|
# #db: 1
|
||||||
|
|
||||||
|
#redisForRateLimit:
|
||||||
|
# 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
|
||||||
|
|
||||||
# ┌───────────────────────────────┐
|
# ┌───────────────────────────────┐
|
||||||
#───┘ Fulltext search configuration └─────────────────────────────
|
#───┘ Fulltext search configuration └─────────────────────────────
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,16 @@ redis:
|
||||||
# #prefix: example-prefix
|
# #prefix: example-prefix
|
||||||
# #db: 1
|
# #db: 1
|
||||||
|
|
||||||
|
#redisForRateLimit:
|
||||||
|
# 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
|
||||||
|
|
||||||
# ┌───────────────────────────────┐
|
# ┌───────────────────────────────┐
|
||||||
#───┘ Fulltext search configuration └─────────────────────────────
|
#───┘ Fulltext search configuration └─────────────────────────────
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,16 @@ redis:
|
||||||
# #prefix: example-prefix
|
# #prefix: example-prefix
|
||||||
# #db: 1
|
# #db: 1
|
||||||
|
|
||||||
|
#redisForRateLimit:
|
||||||
|
# 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
|
||||||
|
|
||||||
# ┌───────────────────────────────┐
|
# ┌───────────────────────────────┐
|
||||||
#───┘ Fulltext search configuration └─────────────────────────────
|
#───┘ Fulltext search configuration └─────────────────────────────
|
||||||
|
|
||||||
|
|
|
@ -198,6 +198,16 @@ redis:
|
||||||
# # You can specify more ioredis options...
|
# # You can specify more ioredis options...
|
||||||
# #username: example-username
|
# #username: example-username
|
||||||
|
|
||||||
|
#redisForRateLimit:
|
||||||
|
# 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
|
||||||
|
|
||||||
# ┌───────────────────────────────┐
|
# ┌───────────────────────────────┐
|
||||||
#───┘ Fulltext search configuration └─────────────────────────────
|
#───┘ Fulltext search configuration └─────────────────────────────
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ const promises = Array
|
||||||
config.redisForJobQueue,
|
config.redisForJobQueue,
|
||||||
config.redisForTimelines,
|
config.redisForTimelines,
|
||||||
config.redisForReactions,
|
config.redisForReactions,
|
||||||
|
config.redisForRateLimit,
|
||||||
]))
|
]))
|
||||||
.map(connectToRedis)
|
.map(connectToRedis)
|
||||||
.concat([
|
.concat([
|
||||||
|
|
|
@ -92,6 +92,14 @@ const $redisForReactions: Provider = {
|
||||||
inject: [DI.config],
|
inject: [DI.config],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $redisForRateLimit: Provider = {
|
||||||
|
provide: DI.redisForRateLimit,
|
||||||
|
useFactory: (config: Config) => {
|
||||||
|
return new Redis.Redis(config.redisForRateLimit);
|
||||||
|
},
|
||||||
|
inject: [DI.config],
|
||||||
|
};
|
||||||
|
|
||||||
const $meta: Provider = {
|
const $meta: Provider = {
|
||||||
provide: DI.meta,
|
provide: DI.meta,
|
||||||
useFactory: async (db: DataSource, redisForSub: Redis.Redis) => {
|
useFactory: async (db: DataSource, redisForSub: Redis.Redis) => {
|
||||||
|
@ -152,8 +160,8 @@ const $meta: Provider = {
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RepositoryModule],
|
imports: [RepositoryModule],
|
||||||
providers: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions],
|
providers: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, $redisForRateLimit],
|
||||||
exports: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, RepositoryModule],
|
exports: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, $redisForRateLimit, RepositoryModule],
|
||||||
})
|
})
|
||||||
export class GlobalModule implements OnApplicationShutdown {
|
export class GlobalModule implements OnApplicationShutdown {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -163,6 +171,7 @@ export class GlobalModule implements OnApplicationShutdown {
|
||||||
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
|
@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
|
||||||
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
|
@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
|
||||||
@Inject(DI.redisForReactions) private redisForReactions: Redis.Redis,
|
@Inject(DI.redisForReactions) private redisForReactions: Redis.Redis,
|
||||||
|
@Inject(DI.redisForRateLimit) private redisForRateLimit: Redis.Redis,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public async dispose(): Promise<void> {
|
public async dispose(): Promise<void> {
|
||||||
|
@ -176,6 +185,7 @@ export class GlobalModule implements OnApplicationShutdown {
|
||||||
this.redisForSub.disconnect(),
|
this.redisForSub.disconnect(),
|
||||||
this.redisForTimelines.disconnect(),
|
this.redisForTimelines.disconnect(),
|
||||||
this.redisForReactions.disconnect(),
|
this.redisForReactions.disconnect(),
|
||||||
|
this.redisForRateLimit.disconnect(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@ type Source = {
|
||||||
redisForJobQueue?: RedisOptionsSource;
|
redisForJobQueue?: RedisOptionsSource;
|
||||||
redisForTimelines?: RedisOptionsSource;
|
redisForTimelines?: RedisOptionsSource;
|
||||||
redisForReactions?: RedisOptionsSource;
|
redisForReactions?: RedisOptionsSource;
|
||||||
|
redisForRateLimit?: RedisOptionsSource;
|
||||||
fulltextSearch?: {
|
fulltextSearch?: {
|
||||||
provider?: FulltextSearchProvider;
|
provider?: FulltextSearchProvider;
|
||||||
};
|
};
|
||||||
|
@ -231,6 +232,7 @@ export type Config = {
|
||||||
redisForJobQueue: RedisOptions & RedisOptionsSource;
|
redisForJobQueue: RedisOptions & RedisOptionsSource;
|
||||||
redisForTimelines: RedisOptions & RedisOptionsSource;
|
redisForTimelines: RedisOptions & RedisOptionsSource;
|
||||||
redisForReactions: RedisOptions & RedisOptionsSource;
|
redisForReactions: RedisOptions & RedisOptionsSource;
|
||||||
|
redisForRateLimit: RedisOptions & RedisOptionsSource;
|
||||||
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
|
sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
|
||||||
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
|
sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
|
||||||
perChannelMaxNoteCacheCount: number;
|
perChannelMaxNoteCacheCount: number;
|
||||||
|
@ -345,6 +347,7 @@ export function loadConfig(): Config {
|
||||||
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
|
redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
|
||||||
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
|
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
|
||||||
redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis,
|
redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis,
|
||||||
|
redisForRateLimit: config.redisForRateLimit ? convertRedisOptions(config.redisForRateLimit, host) : redis,
|
||||||
sentryForBackend: config.sentryForBackend,
|
sentryForBackend: config.sentryForBackend,
|
||||||
sentryForFrontend: config.sentryForFrontend,
|
sentryForFrontend: config.sentryForFrontend,
|
||||||
id: config.id,
|
id: config.id,
|
||||||
|
@ -535,7 +538,7 @@ function applyEnvOverrides(config: Source) {
|
||||||
_apply_top(['db', ['host', 'port', 'db', 'user', 'pass', 'disableCache']]);
|
_apply_top(['db', ['host', 'port', 'db', 'user', 'pass', 'disableCache']]);
|
||||||
_apply_top(['dbSlaves', Array.from((config.dbSlaves ?? []).keys()), ['host', 'port', 'db', 'user', 'pass']]);
|
_apply_top(['dbSlaves', Array.from((config.dbSlaves ?? []).keys()), ['host', 'port', 'db', 'user', 'pass']]);
|
||||||
_apply_top([
|
_apply_top([
|
||||||
['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines', 'redisForReactions'],
|
['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines', 'redisForReactions', 'redisForRateLimit'],
|
||||||
['host', 'port', 'username', 'pass', 'db', 'prefix'],
|
['host', 'port', 'username', 'pass', 'db', 'prefix'],
|
||||||
]);
|
]);
|
||||||
_apply_top(['fulltextSearch', 'provider']);
|
_apply_top(['fulltextSearch', 'provider']);
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const DI = {
|
||||||
redisForSub: Symbol('redisForSub'),
|
redisForSub: Symbol('redisForSub'),
|
||||||
redisForTimelines: Symbol('redisForTimelines'),
|
redisForTimelines: Symbol('redisForTimelines'),
|
||||||
redisForReactions: Symbol('redisForReactions'),
|
redisForReactions: Symbol('redisForReactions'),
|
||||||
|
redisForRateLimit: Symbol('redisForRateLimit'),
|
||||||
|
|
||||||
//#region Repositories
|
//#region Repositories
|
||||||
usersRepository: Symbol('usersRepository'),
|
usersRepository: Symbol('usersRepository'),
|
||||||
|
|
|
@ -39,6 +39,7 @@ The first call is read-only, while the others perform at least one write operati
|
||||||
Two integer keys are stored per client/subject, and both expire together after the maximum duration of the limit.
|
Two integer keys are stored per client/subject, and both expire together after the maximum duration of the limit.
|
||||||
While performance has not been formally tested, it's expected that SkRateLimiterService has an impact roughly on par with the legacy RateLimiterService.
|
While performance has not been formally tested, it's expected that SkRateLimiterService has an impact roughly on par with the legacy RateLimiterService.
|
||||||
Redis memory usage should be notably lower due to the reduced number of keys and avoidance of set / array constructions.
|
Redis memory usage should be notably lower due to the reduced number of keys and avoidance of set / array constructions.
|
||||||
|
If redis load does become a concern, then a dedicated node can be assigned via the `redisForRateLimit` config setting.
|
||||||
|
|
||||||
## Concurrency and Multi-Node Correctness
|
## Concurrency and Multi-Node Correctness
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ export class SkRateLimiterService {
|
||||||
@Inject('TimeService')
|
@Inject('TimeService')
|
||||||
private readonly timeService: TimeService,
|
private readonly timeService: TimeService,
|
||||||
|
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redisForRateLimit)
|
||||||
private readonly redisClient: Redis.Redis,
|
private readonly redisClient: Redis.Redis,
|
||||||
|
|
||||||
@Inject('RoleService')
|
@Inject('RoleService')
|
||||||
|
|
Loading…
Add table
Reference in a new issue