mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 12:36:57 +00:00
implement no-op caches for testing
This commit is contained in:
parent
1d06ac4824
commit
2e486f02ff
3 changed files with 244 additions and 15 deletions
|
@ -28,7 +28,7 @@ export interface CachedTranslation {
|
||||||
text: string | undefined;
|
text: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CachedTranslationEntity {
|
export interface CachedTranslationEntity {
|
||||||
l?: string;
|
l?: string;
|
||||||
t?: string;
|
t?: string;
|
||||||
u?: number;
|
u?: number;
|
||||||
|
@ -46,8 +46,8 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
public userBlockedCache: QuantumKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
public userBlockedCache: QuantumKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
|
||||||
public renoteMutingsCache: QuantumKVCache<Set<string>>;
|
public renoteMutingsCache: QuantumKVCache<Set<string>>;
|
||||||
public userFollowingsCache: QuantumKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
|
public userFollowingsCache: QuantumKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
|
||||||
private readonly userFollowStatsCache = new MemoryKVCache<FollowStats>(1000 * 60 * 10); // 10 minutes
|
protected userFollowStatsCache = new MemoryKVCache<FollowStats>(1000 * 60 * 10); // 10 minutes
|
||||||
private readonly translationsCache: RedisKVCache<CachedTranslationEntity>;
|
protected translationsCache: RedisKVCache<CachedTranslationEntity>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.redis)
|
@Inject(DI.redis)
|
||||||
|
@ -467,6 +467,22 @@ export class CacheService implements OnApplicationShutdown {
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public clear(): void {
|
||||||
|
this.userByIdCache.clear();
|
||||||
|
this.localUserByNativeTokenCache.clear();
|
||||||
|
this.localUserByIdCache.clear();
|
||||||
|
this.uriPersonCache.clear();
|
||||||
|
this.userProfileCache.clear();
|
||||||
|
this.userMutingsCache.clear();
|
||||||
|
this.userBlockingCache.clear();
|
||||||
|
this.userBlockedCache.clear();
|
||||||
|
this.renoteMutingsCache.clear();
|
||||||
|
this.userFollowingsCache.clear();
|
||||||
|
this.userFollowStatsCache.clear();
|
||||||
|
this.translationsCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.internalEventService.off('userChangeSuspendedState', this.onUserEvent);
|
this.internalEventService.off('userChangeSuspendedState', this.onUserEvent);
|
||||||
|
|
|
@ -11,9 +11,9 @@ import { InternalEventTypes } from '@/core/GlobalEventService.js';
|
||||||
export class RedisKVCache<T> {
|
export class RedisKVCache<T> {
|
||||||
private readonly lifetime: number;
|
private readonly lifetime: number;
|
||||||
private readonly memoryCache: MemoryKVCache<T>;
|
private readonly memoryCache: MemoryKVCache<T>;
|
||||||
private readonly fetcher: (key: string) => Promise<T>;
|
public readonly fetcher: (key: string) => Promise<T>;
|
||||||
private readonly toRedisConverter: (value: T) => string;
|
public readonly toRedisConverter: (value: T) => string;
|
||||||
private readonly fromRedisConverter: (value: string) => T | undefined;
|
public readonly fromRedisConverter: (value: string) => T | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
|
@ -101,6 +101,11 @@ export class RedisKVCache<T> {
|
||||||
// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
|
// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public clear() {
|
||||||
|
this.memoryCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public gc() {
|
public gc() {
|
||||||
this.memoryCache.gc();
|
this.memoryCache.gc();
|
||||||
|
@ -125,16 +130,17 @@ export class RedisSingleCache<T> {
|
||||||
opts: {
|
opts: {
|
||||||
lifetime: number;
|
lifetime: number;
|
||||||
memoryCacheLifetime: number;
|
memoryCacheLifetime: number;
|
||||||
fetcher: RedisSingleCache<T>['fetcher'];
|
fetcher?: RedisSingleCache<T>['fetcher'];
|
||||||
toRedisConverter: RedisSingleCache<T>['toRedisConverter'];
|
toRedisConverter?: RedisSingleCache<T>['toRedisConverter'];
|
||||||
fromRedisConverter: RedisSingleCache<T>['fromRedisConverter'];
|
fromRedisConverter?: RedisSingleCache<T>['fromRedisConverter'];
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
this.lifetime = opts.lifetime;
|
this.lifetime = opts.lifetime;
|
||||||
this.memoryCache = new MemorySingleCache(opts.memoryCacheLifetime);
|
this.memoryCache = new MemorySingleCache(opts.memoryCacheLifetime);
|
||||||
this.fetcher = opts.fetcher;
|
|
||||||
this.toRedisConverter = opts.toRedisConverter;
|
this.fetcher = opts.fetcher ?? (() => { throw new Error('fetch not supported - use get/set directly'); });
|
||||||
this.fromRedisConverter = opts.fromRedisConverter;
|
this.toRedisConverter = opts.toRedisConverter ?? ((value) => JSON.stringify(value));
|
||||||
|
this.fromRedisConverter = opts.fromRedisConverter ?? ((value) => JSON.parse(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -417,6 +423,8 @@ export class MemorySingleCache<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO move to separate file
|
||||||
|
|
||||||
export interface QuantumKVOpts<T> {
|
export interface QuantumKVOpts<T> {
|
||||||
/**
|
/**
|
||||||
* Memory cache lifetime in milliseconds.
|
* Memory cache lifetime in milliseconds.
|
||||||
|
@ -452,9 +460,9 @@ export interface QuantumKVOpts<T> {
|
||||||
export class QuantumKVCache<T> implements Iterable<[key: string, value: T]> {
|
export class QuantumKVCache<T> implements Iterable<[key: string, value: T]> {
|
||||||
private readonly memoryCache: MemoryKVCache<T>;
|
private readonly memoryCache: MemoryKVCache<T>;
|
||||||
|
|
||||||
private readonly fetcher: QuantumKVOpts<T>['fetcher'];
|
public readonly fetcher: QuantumKVOpts<T>['fetcher'];
|
||||||
private readonly onSet: QuantumKVOpts<T>['onSet'];
|
public readonly onSet: QuantumKVOpts<T>['onSet'];
|
||||||
private readonly onDelete: QuantumKVOpts<T>['onDelete'];
|
public readonly onDelete: QuantumKVOpts<T>['onDelete'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param internalEventService Service bus to synchronize events.
|
* @param internalEventService Service bus to synchronize events.
|
||||||
|
@ -676,6 +684,15 @@ export class QuantumKVCache<T> implements Iterable<[key: string, value: T]> {
|
||||||
* Does not send any events or update other processes.
|
* Does not send any events or update other processes.
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
|
public clear() {
|
||||||
|
this.memoryCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes expired cache entries from the local view.
|
||||||
|
* Does not send any events or update other processes.
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
public gc() {
|
public gc() {
|
||||||
this.memoryCache.gc();
|
this.memoryCache.gc();
|
||||||
}
|
}
|
||||||
|
|
196
packages/backend/test/misc/noOpCaches.ts
Normal file
196
packages/backend/test/misc/noOpCaches.ts
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Redis from 'ioredis';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { FakeInternalEventService } from './FakeInternalEventService.js';
|
||||||
|
import type { BlockingsRepository, FollowingsRepository, MiFollowing, MiUser, MiUserProfile, MutingsRepository, RenoteMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import type { MiLocalUser } from '@/models/User.js';
|
||||||
|
import { MemoryKVCache, MemorySingleCache, QuantumKVCache, QuantumKVOpts, RedisKVCache, RedisSingleCache } from '@/misc/cache.js';
|
||||||
|
import { CacheService, CachedTranslationEntity, FollowStats } from '@/core/CacheService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
|
||||||
|
export function noOpRedis() {
|
||||||
|
return {
|
||||||
|
set: () => Promise.resolve(),
|
||||||
|
get: () => Promise.resolve(null),
|
||||||
|
del: () => Promise.resolve(),
|
||||||
|
on: () => {},
|
||||||
|
off: () => {},
|
||||||
|
} as unknown as Redis.Redis;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NoOpCacheService extends CacheService {
|
||||||
|
public readonly fakeRedis: {
|
||||||
|
[K in keyof Redis.Redis]: Redis.Redis[K];
|
||||||
|
};
|
||||||
|
public readonly fakeInternalEventService: FakeInternalEventService;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
@Inject(DI.userProfilesRepository)
|
||||||
|
userProfilesRepository: UserProfilesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.mutingsRepository)
|
||||||
|
mutingsRepository: MutingsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.blockingsRepository)
|
||||||
|
blockingsRepository: BlockingsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.renoteMutingsRepository)
|
||||||
|
renoteMutingsRepository: RenoteMutingsRepository,
|
||||||
|
|
||||||
|
@Inject(DI.followingsRepository)
|
||||||
|
followingsRepository: FollowingsRepository,
|
||||||
|
|
||||||
|
@Inject(UserEntityService)
|
||||||
|
userEntityService: UserEntityService,
|
||||||
|
) {
|
||||||
|
const fakeRedis = noOpRedis();
|
||||||
|
const fakeInternalEventService = new FakeInternalEventService();
|
||||||
|
|
||||||
|
super(
|
||||||
|
fakeRedis,
|
||||||
|
fakeRedis,
|
||||||
|
usersRepository,
|
||||||
|
userProfilesRepository,
|
||||||
|
mutingsRepository,
|
||||||
|
blockingsRepository,
|
||||||
|
renoteMutingsRepository,
|
||||||
|
followingsRepository,
|
||||||
|
userEntityService,
|
||||||
|
fakeInternalEventService,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.fakeRedis = fakeRedis;
|
||||||
|
this.fakeInternalEventService = fakeInternalEventService;
|
||||||
|
|
||||||
|
// Override caches
|
||||||
|
this.userByIdCache = new NoOpMemoryKVCache<MiUser>();
|
||||||
|
this.localUserByNativeTokenCache = new NoOpMemoryKVCache<MiLocalUser | null>();
|
||||||
|
this.localUserByIdCache = new NoOpMemoryKVCache<MiLocalUser>();
|
||||||
|
this.uriPersonCache = new NoOpMemoryKVCache<MiUser | null>();
|
||||||
|
this.userProfileCache = new NoOpQuantumKVCache<MiUserProfile>({
|
||||||
|
internalEventService: fakeInternalEventService,
|
||||||
|
fetcher: this.userProfileCache.fetcher,
|
||||||
|
onSet: this.userProfileCache.onSet,
|
||||||
|
onDelete: this.userProfileCache.onDelete,
|
||||||
|
});
|
||||||
|
this.userMutingsCache = new NoOpQuantumKVCache<Set<string>>({
|
||||||
|
internalEventService: fakeInternalEventService,
|
||||||
|
fetcher: this.userMutingsCache.fetcher,
|
||||||
|
onSet: this.userMutingsCache.onSet,
|
||||||
|
onDelete: this.userMutingsCache.onDelete,
|
||||||
|
});
|
||||||
|
this.userBlockingCache = new NoOpQuantumKVCache<Set<string>>({
|
||||||
|
internalEventService: fakeInternalEventService,
|
||||||
|
fetcher: this.userBlockingCache.fetcher,
|
||||||
|
onSet: this.userBlockingCache.onSet,
|
||||||
|
onDelete: this.userBlockingCache.onDelete,
|
||||||
|
});
|
||||||
|
this.userBlockedCache = new NoOpQuantumKVCache<Set<string>>({
|
||||||
|
internalEventService: fakeInternalEventService,
|
||||||
|
fetcher: this.userBlockedCache.fetcher,
|
||||||
|
onSet: this.userBlockedCache.onSet,
|
||||||
|
onDelete: this.userBlockedCache.onDelete,
|
||||||
|
});
|
||||||
|
this.renoteMutingsCache = new NoOpQuantumKVCache<Set<string>>({
|
||||||
|
internalEventService: fakeInternalEventService,
|
||||||
|
fetcher: this.renoteMutingsCache.fetcher,
|
||||||
|
onSet: this.renoteMutingsCache.onSet,
|
||||||
|
onDelete: this.renoteMutingsCache.onDelete,
|
||||||
|
});
|
||||||
|
this.userFollowingsCache = new NoOpQuantumKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>({
|
||||||
|
internalEventService: fakeInternalEventService,
|
||||||
|
fetcher: this.userFollowingsCache.fetcher,
|
||||||
|
onSet: this.userFollowingsCache.onSet,
|
||||||
|
onDelete: this.userFollowingsCache.onDelete,
|
||||||
|
});
|
||||||
|
this.userFollowStatsCache = new NoOpMemoryKVCache<FollowStats>();
|
||||||
|
this.translationsCache = new NoOpRedisKVCache<CachedTranslationEntity>({
|
||||||
|
redis: fakeRedis,
|
||||||
|
fetcher: this.translationsCache.fetcher,
|
||||||
|
toRedisConverter: this.translationsCache.toRedisConverter,
|
||||||
|
fromRedisConverter: this.translationsCache.fromRedisConverter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NoOpMemoryKVCache<T> extends MemoryKVCache<T> {
|
||||||
|
constructor() {
|
||||||
|
super(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NoOpMemorySingleCache<T> extends MemorySingleCache<T> {
|
||||||
|
constructor() {
|
||||||
|
super(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NoOpRedisKVCache<T> extends RedisKVCache<T> {
|
||||||
|
constructor(opts?: {
|
||||||
|
redis?: Redis.Redis;
|
||||||
|
fetcher?: RedisKVCache<T>['fetcher'];
|
||||||
|
toRedisConverter?: RedisKVCache<T>['toRedisConverter'];
|
||||||
|
fromRedisConverter?: RedisKVCache<T>['fromRedisConverter'];
|
||||||
|
}) {
|
||||||
|
super(
|
||||||
|
opts?.redis ?? noOpRedis(),
|
||||||
|
'no-op',
|
||||||
|
{
|
||||||
|
lifetime: -1,
|
||||||
|
memoryCacheLifetime: -1,
|
||||||
|
fetcher: opts?.fetcher,
|
||||||
|
toRedisConverter: opts?.toRedisConverter,
|
||||||
|
fromRedisConverter: opts?.fromRedisConverter,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NoOpRedisSingleCache<T> extends RedisSingleCache<T> {
|
||||||
|
constructor(opts?: {
|
||||||
|
fakeRedis?: Redis.Redis;
|
||||||
|
fetcher?: RedisSingleCache<T>['fetcher'];
|
||||||
|
toRedisConverter?: RedisSingleCache<T>['toRedisConverter'];
|
||||||
|
fromRedisConverter?: RedisSingleCache<T>['fromRedisConverter'];
|
||||||
|
}) {
|
||||||
|
super(
|
||||||
|
opts?.fakeRedis ?? noOpRedis(),
|
||||||
|
'no-op',
|
||||||
|
{
|
||||||
|
lifetime: -1,
|
||||||
|
memoryCacheLifetime: -1,
|
||||||
|
fetcher: opts?.fetcher,
|
||||||
|
toRedisConverter: opts?.toRedisConverter,
|
||||||
|
fromRedisConverter: opts?.fromRedisConverter,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NoOpQuantumKVCache<T> extends QuantumKVCache<T> {
|
||||||
|
constructor(opts: {
|
||||||
|
internalEventService?: FakeInternalEventService,
|
||||||
|
fetcher: QuantumKVOpts<T>['fetcher'],
|
||||||
|
onSet?: QuantumKVOpts<T>['onSet'],
|
||||||
|
onDelete?: QuantumKVOpts<T>['onDelete'],
|
||||||
|
}) {
|
||||||
|
super(
|
||||||
|
opts.internalEventService ?? new FakeInternalEventService(),
|
||||||
|
'no-op',
|
||||||
|
{
|
||||||
|
lifetime: -1,
|
||||||
|
fetcher: opts.fetcher,
|
||||||
|
onSet: opts.onSet,
|
||||||
|
onDelete: opts.onDelete,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue