enhance(backend): improve cache
This commit is contained in:
parent
7f3afac0a2
commit
ecaf152b4a
21 changed files with 184 additions and 89 deletions
|
@ -1,7 +1,7 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import Redis from 'ioredis';
|
import Redis from 'ioredis';
|
||||||
import type { UsersRepository } from '@/models/index.js';
|
import type { UserProfile, UsersRepository } from '@/models/index.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
|
||||||
import type { LocalUser, User } from '@/models/entities/User.js';
|
import type { LocalUser, User } from '@/models/entities/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
@ -10,13 +10,18 @@ import { StreamMessages } from '@/server/api/stream/types.js';
|
||||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserCacheService implements OnApplicationShutdown {
|
export class CacheService implements OnApplicationShutdown {
|
||||||
public userByIdCache: MemoryKVCache<User>;
|
public userByIdCache: MemoryKVCache<User>;
|
||||||
public localUserByNativeTokenCache: MemoryKVCache<LocalUser | null>;
|
public localUserByNativeTokenCache: MemoryKVCache<LocalUser | null>;
|
||||||
public localUserByIdCache: MemoryKVCache<LocalUser>;
|
public localUserByIdCache: MemoryKVCache<LocalUser>;
|
||||||
public uriPersonCache: MemoryKVCache<User | null>;
|
public uriPersonCache: MemoryKVCache<User | null>;
|
||||||
|
public userProfileCache: RedisKVCache<UserProfile>;
|
||||||
|
public userMutingsCache: RedisKVCache<string[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.redis)
|
||||||
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
@Inject(DI.redisSubscriber)
|
@Inject(DI.redisSubscriber)
|
||||||
private redisSubscriber: Redis.Redis,
|
private redisSubscriber: Redis.Redis,
|
||||||
|
|
||||||
|
@ -31,6 +36,8 @@ export class UserCacheService implements OnApplicationShutdown {
|
||||||
this.localUserByNativeTokenCache = new MemoryKVCache<LocalUser | null>(Infinity);
|
this.localUserByNativeTokenCache = new MemoryKVCache<LocalUser | null>(Infinity);
|
||||||
this.localUserByIdCache = new MemoryKVCache<LocalUser>(Infinity);
|
this.localUserByIdCache = new MemoryKVCache<LocalUser>(Infinity);
|
||||||
this.uriPersonCache = new MemoryKVCache<User | null>(Infinity);
|
this.uriPersonCache = new MemoryKVCache<User | null>(Infinity);
|
||||||
|
this.userProfileCache = new RedisKVCache<UserProfile>(this.redisClient, 'userProfile', 1000 * 60 * 60 * 24, 1000 * 60);
|
||||||
|
this.userMutingsCache = new RedisKVCache<string[]>(this.redisClient, 'userMutings', 1000 * 60 * 60 * 24, 1000 * 60);
|
||||||
|
|
||||||
this.redisSubscriber.on('message', this.onMessage);
|
this.redisSubscriber.on('message', this.onMessage);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +59,7 @@ export class UserCacheService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.userEntityService.isLocalUser(user)) {
|
if (this.userEntityService.isLocalUser(user)) {
|
||||||
this.localUserByNativeTokenCache.set(user.token, user);
|
this.localUserByNativeTokenCache.set(user.token!, user);
|
||||||
this.localUserByIdCache.set(user.id, user);
|
this.localUserByIdCache.set(user.id, user);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -77,7 +84,7 @@ export class UserCacheService implements OnApplicationShutdown {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public findById(userId: User['id']) {
|
public findUserById(userId: User['id']) {
|
||||||
return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId }));
|
return this.userByIdCache.fetch(userId, () => this.usersRepository.findOneByOrFail({ id: userId }));
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ import { S3Service } from './S3Service.js';
|
||||||
import { SignupService } from './SignupService.js';
|
import { SignupService } from './SignupService.js';
|
||||||
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
|
import { TwoFactorAuthenticationService } from './TwoFactorAuthenticationService.js';
|
||||||
import { UserBlockingService } from './UserBlockingService.js';
|
import { UserBlockingService } from './UserBlockingService.js';
|
||||||
import { UserCacheService } from './UserCacheService.js';
|
import { CacheService } from './CacheService.js';
|
||||||
import { UserFollowingService } from './UserFollowingService.js';
|
import { UserFollowingService } from './UserFollowingService.js';
|
||||||
import { UserKeypairStoreService } from './UserKeypairStoreService.js';
|
import { UserKeypairStoreService } from './UserKeypairStoreService.js';
|
||||||
import { UserListService } from './UserListService.js';
|
import { UserListService } from './UserListService.js';
|
||||||
|
@ -159,7 +159,7 @@ const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
|
||||||
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
|
const $SignupService: Provider = { provide: 'SignupService', useExisting: SignupService };
|
||||||
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
|
const $TwoFactorAuthenticationService: Provider = { provide: 'TwoFactorAuthenticationService', useExisting: TwoFactorAuthenticationService };
|
||||||
const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
|
const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
|
||||||
const $UserCacheService: Provider = { provide: 'UserCacheService', useExisting: UserCacheService };
|
const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
|
||||||
const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
|
const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
|
||||||
const $UserKeypairStoreService: Provider = { provide: 'UserKeypairStoreService', useExisting: UserKeypairStoreService };
|
const $UserKeypairStoreService: Provider = { provide: 'UserKeypairStoreService', useExisting: UserKeypairStoreService };
|
||||||
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
|
const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
|
||||||
|
@ -282,7 +282,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
SignupService,
|
SignupService,
|
||||||
TwoFactorAuthenticationService,
|
TwoFactorAuthenticationService,
|
||||||
UserBlockingService,
|
UserBlockingService,
|
||||||
UserCacheService,
|
CacheService,
|
||||||
UserFollowingService,
|
UserFollowingService,
|
||||||
UserKeypairStoreService,
|
UserKeypairStoreService,
|
||||||
UserListService,
|
UserListService,
|
||||||
|
@ -399,7 +399,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$SignupService,
|
$SignupService,
|
||||||
$TwoFactorAuthenticationService,
|
$TwoFactorAuthenticationService,
|
||||||
$UserBlockingService,
|
$UserBlockingService,
|
||||||
$UserCacheService,
|
$CacheService,
|
||||||
$UserFollowingService,
|
$UserFollowingService,
|
||||||
$UserKeypairStoreService,
|
$UserKeypairStoreService,
|
||||||
$UserListService,
|
$UserListService,
|
||||||
|
@ -517,7 +517,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
SignupService,
|
SignupService,
|
||||||
TwoFactorAuthenticationService,
|
TwoFactorAuthenticationService,
|
||||||
UserBlockingService,
|
UserBlockingService,
|
||||||
UserCacheService,
|
CacheService,
|
||||||
UserFollowingService,
|
UserFollowingService,
|
||||||
UserKeypairStoreService,
|
UserKeypairStoreService,
|
||||||
UserListService,
|
UserListService,
|
||||||
|
@ -633,7 +633,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||||
$SignupService,
|
$SignupService,
|
||||||
$TwoFactorAuthenticationService,
|
$TwoFactorAuthenticationService,
|
||||||
$UserBlockingService,
|
$UserBlockingService,
|
||||||
$UserCacheService,
|
$CacheService,
|
||||||
$UserFollowingService,
|
$UserFollowingService,
|
||||||
$UserKeypairStoreService,
|
$UserKeypairStoreService,
|
||||||
$UserListService,
|
$UserListService,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import type { LocalUser } from '@/models/entities/User.js';
|
import type { LocalUser } from '@/models/entities/User.js';
|
||||||
import type { UsersRepository } from '@/models/index.js';
|
import type { UsersRepository } from '@/models/index.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryCache } from '@/misc/cache.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
@ -11,7 +11,7 @@ const ACTOR_USERNAME = 'instance.actor' as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InstanceActorService {
|
export class InstanceActorService {
|
||||||
private cache: MemoryKVCache<LocalUser>;
|
private cache: MemoryCache<LocalUser>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
|
@ -19,12 +19,12 @@ export class InstanceActorService {
|
||||||
|
|
||||||
private createSystemUserService: CreateSystemUserService,
|
private createSystemUserService: CreateSystemUserService,
|
||||||
) {
|
) {
|
||||||
this.cache = new MemoryKVCache<LocalUser>(Infinity);
|
this.cache = new MemoryCache<LocalUser>(Infinity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getInstanceActor(): Promise<LocalUser> {
|
public async getInstanceActor(): Promise<LocalUser> {
|
||||||
const cached = this.cache.get(null);
|
const cached = this.cache.get();
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
|
@ -33,11 +33,11 @@ export class InstanceActorService {
|
||||||
}) as LocalUser | undefined;
|
}) as LocalUser | undefined;
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
this.cache.set(null, user);
|
this.cache.set(user);
|
||||||
return user;
|
return user;
|
||||||
} else {
|
} else {
|
||||||
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser;
|
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser;
|
||||||
this.cache.set(null, created);
|
this.cache.set(created);
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js
|
||||||
import { checkWordMute } from '@/misc/check-word-mute.js';
|
import { checkWordMute } from '@/misc/check-word-mute.js';
|
||||||
import type { Channel } from '@/models/entities/Channel.js';
|
import type { Channel } from '@/models/entities/Channel.js';
|
||||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryCache } from '@/misc/cache.js';
|
||||||
import type { UserProfile } from '@/models/entities/UserProfile.js';
|
import type { UserProfile } from '@/models/entities/UserProfile.js';
|
||||||
import { RelayService } from '@/core/RelayService.js';
|
import { RelayService } from '@/core/RelayService.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
|
@ -47,7 +47,7 @@ import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
|
||||||
const mutedWordsCache = new MemoryKVCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
|
const mutedWordsCache = new MemoryCache<{ userId: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
|
||||||
|
|
||||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||||
|
|
||||||
|
@ -473,7 +473,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
this.incNotesCountOfUser(user);
|
this.incNotesCountOfUser(user);
|
||||||
|
|
||||||
// Word mute
|
// Word mute
|
||||||
mutedWordsCache.fetch(null, () => this.userProfilesRepository.find({
|
mutedWordsCache.fetch(() => this.userProfilesRepository.find({
|
||||||
where: {
|
where: {
|
||||||
enableWordMute: true,
|
enableWordMute: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Redis from 'ioredis';
|
||||||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { MutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
import type { MutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||||
import type { User } from '@/models/entities/User.js';
|
import type { User } from '@/models/entities/User.js';
|
||||||
import type { Notification } from '@/models/entities/Notification.js';
|
import type { Notification } from '@/models/entities/Notification.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
@ -12,6 +12,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { PushNotificationService } from '@/core/PushNotificationService.js';
|
import { PushNotificationService } from '@/core/PushNotificationService.js';
|
||||||
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
|
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NotificationService implements OnApplicationShutdown {
|
export class NotificationService implements OnApplicationShutdown {
|
||||||
|
@ -35,6 +36,7 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private pushNotificationService: PushNotificationService,
|
private pushNotificationService: PushNotificationService,
|
||||||
|
private cacheService: CacheService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +51,6 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
'+',
|
'+',
|
||||||
'-',
|
'-',
|
||||||
'COUNT', 1);
|
'COUNT', 1);
|
||||||
console.log('latestNotificationIdsRes', latestNotificationIdsRes);
|
|
||||||
const latestNotificationId = latestNotificationIdsRes[0]?.[0];
|
const latestNotificationId = latestNotificationIdsRes[0]?.[0];
|
||||||
|
|
||||||
if (latestNotificationId == null) return;
|
if (latestNotificationId == null) return;
|
||||||
|
@ -72,9 +73,8 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
type: Notification['type'],
|
type: Notification['type'],
|
||||||
data: Partial<Notification>,
|
data: Partial<Notification>,
|
||||||
): Promise<Notification | null> {
|
): Promise<Notification | null> {
|
||||||
// TODO: Cache
|
const profile = await this.cacheService.userProfileCache.fetch(notifieeId, () => this.userProfilesRepository.findOneByOrFail({ userId: notifieeId }));
|
||||||
const profile = await this.userProfilesRepository.findOneBy({ userId: notifieeId });
|
const isMuted = profile.mutingNotificationTypes.includes(type);
|
||||||
const isMuted = profile?.mutingNotificationTypes.includes(type);
|
|
||||||
if (isMuted) return null;
|
if (isMuted) return null;
|
||||||
|
|
||||||
if (data.notifierId) {
|
if (data.notifierId) {
|
||||||
|
@ -82,12 +82,8 @@ export class NotificationService implements OnApplicationShutdown {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: cache
|
const mutings = await this.cacheService.userMutingsCache.fetch(notifieeId, () => this.mutingsRepository.findBy({ muterId: notifieeId }).then(xs => xs.map(x => x.muteeId)));
|
||||||
const mutings = await this.mutingsRepository.findOneBy({
|
if (mutings.includes(data.notifierId)) {
|
||||||
muterId: notifieeId,
|
|
||||||
muteeId: data.notifierId,
|
|
||||||
});
|
|
||||||
if (mutings) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { IsNull } from 'typeorm';
|
||||||
import type { LocalUser, User } from '@/models/entities/User.js';
|
import type { LocalUser, User } from '@/models/entities/User.js';
|
||||||
import type { RelaysRepository, UsersRepository } from '@/models/index.js';
|
import type { RelaysRepository, UsersRepository } from '@/models/index.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryCache } from '@/misc/cache.js';
|
||||||
import type { Relay } from '@/models/entities/Relay.js';
|
import type { Relay } from '@/models/entities/Relay.js';
|
||||||
import { QueueService } from '@/core/QueueService.js';
|
import { QueueService } from '@/core/QueueService.js';
|
||||||
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
|
||||||
|
@ -16,7 +16,7 @@ const ACTOR_USERNAME = 'relay.actor' as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RelayService {
|
export class RelayService {
|
||||||
private relaysCache: MemoryKVCache<Relay[]>;
|
private relaysCache: MemoryCache<Relay[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
|
@ -30,7 +30,7 @@ export class RelayService {
|
||||||
private createSystemUserService: CreateSystemUserService,
|
private createSystemUserService: CreateSystemUserService,
|
||||||
private apRendererService: ApRendererService,
|
private apRendererService: ApRendererService,
|
||||||
) {
|
) {
|
||||||
this.relaysCache = new MemoryKVCache<Relay[]>(1000 * 60 * 10);
|
this.relaysCache = new MemoryCache<Relay[]>(1000 * 60 * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -109,7 +109,7 @@ export class RelayService {
|
||||||
public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise<void> {
|
public async deliverToRelays(user: { id: User['id']; host: null; }, activity: any): Promise<void> {
|
||||||
if (activity == null) return;
|
if (activity == null) return;
|
||||||
|
|
||||||
const relays = await this.relaysCache.fetch(null, () => this.relaysRepository.findBy({
|
const relays = await this.relaysCache.fetch(() => this.relaysRepository.findBy({
|
||||||
status: 'accepted',
|
status: 'accepted',
|
||||||
}));
|
}));
|
||||||
if (relays.length === 0) return;
|
if (relays.length === 0) return;
|
||||||
|
|
|
@ -2,12 +2,12 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||||
import Redis from 'ioredis';
|
import Redis from 'ioredis';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
|
import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache, MemoryCache } from '@/misc/cache.js';
|
||||||
import type { User } from '@/models/entities/User.js';
|
import type { User } from '@/models/entities/User.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { UserCacheService } from '@/core/UserCacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { RoleCondFormulaValue } from '@/models/entities/Role.js';
|
import type { RoleCondFormulaValue } from '@/models/entities/Role.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||||
|
@ -57,7 +57,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RoleService implements OnApplicationShutdown {
|
export class RoleService implements OnApplicationShutdown {
|
||||||
private rolesCache: MemoryKVCache<Role[]>;
|
private rolesCache: MemoryCache<Role[]>;
|
||||||
private roleAssignmentByUserIdCache: MemoryKVCache<RoleAssignment[]>;
|
private roleAssignmentByUserIdCache: MemoryKVCache<RoleAssignment[]>;
|
||||||
|
|
||||||
public static AlreadyAssignedError = class extends Error {};
|
public static AlreadyAssignedError = class extends Error {};
|
||||||
|
@ -77,14 +77,14 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
||||||
|
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
private userCacheService: UserCacheService,
|
private cacheService: CacheService,
|
||||||
private userEntityService: UserEntityService,
|
private userEntityService: UserEntityService,
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
) {
|
) {
|
||||||
//this.onMessage = this.onMessage.bind(this);
|
//this.onMessage = this.onMessage.bind(this);
|
||||||
|
|
||||||
this.rolesCache = new MemoryKVCache<Role[]>(Infinity);
|
this.rolesCache = new MemoryCache<Role[]>(Infinity);
|
||||||
this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(Infinity);
|
this.roleAssignmentByUserIdCache = new MemoryKVCache<RoleAssignment[]>(Infinity);
|
||||||
|
|
||||||
this.redisSubscriber.on('message', this.onMessage);
|
this.redisSubscriber.on('message', this.onMessage);
|
||||||
|
@ -98,7 +98,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
const { type, body } = obj.message as StreamMessages['internal']['payload'];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'roleCreated': {
|
case 'roleCreated': {
|
||||||
const cached = this.rolesCache.get(null);
|
const cached = this.rolesCache.get();
|
||||||
if (cached) {
|
if (cached) {
|
||||||
cached.push({
|
cached.push({
|
||||||
...body,
|
...body,
|
||||||
|
@ -110,7 +110,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'roleUpdated': {
|
case 'roleUpdated': {
|
||||||
const cached = this.rolesCache.get(null);
|
const cached = this.rolesCache.get();
|
||||||
if (cached) {
|
if (cached) {
|
||||||
const i = cached.findIndex(x => x.id === body.id);
|
const i = cached.findIndex(x => x.id === body.id);
|
||||||
if (i > -1) {
|
if (i > -1) {
|
||||||
|
@ -125,9 +125,9 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'roleDeleted': {
|
case 'roleDeleted': {
|
||||||
const cached = this.rolesCache.get(null);
|
const cached = this.rolesCache.get();
|
||||||
if (cached) {
|
if (cached) {
|
||||||
this.rolesCache.set(null, cached.filter(x => x.id !== body.id));
|
this.rolesCache.set(cached.filter(x => x.id !== body.id));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -214,9 +214,9 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
// 期限切れのロールを除外
|
// 期限切れのロールを除外
|
||||||
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
|
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
|
||||||
const assignedRoleIds = assigns.map(x => x.roleId);
|
const assignedRoleIds = assigns.map(x => x.roleId);
|
||||||
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
|
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||||
const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id));
|
const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id));
|
||||||
const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null;
|
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
|
||||||
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
|
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
|
||||||
return [...assignedRoles, ...matchedCondRoles];
|
return [...assignedRoles, ...matchedCondRoles];
|
||||||
}
|
}
|
||||||
|
@ -231,11 +231,11 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
// 期限切れのロールを除外
|
// 期限切れのロールを除外
|
||||||
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
|
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
|
||||||
const assignedRoleIds = assigns.map(x => x.roleId);
|
const assignedRoleIds = assigns.map(x => x.roleId);
|
||||||
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
|
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||||
const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id));
|
const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id));
|
||||||
const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional'));
|
const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional'));
|
||||||
if (badgeCondRoles.length > 0) {
|
if (badgeCondRoles.length > 0) {
|
||||||
const user = roles.some(r => r.target === 'conditional') ? await this.userCacheService.findById(userId) : null;
|
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
|
||||||
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, r.condFormula));
|
const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, r.condFormula));
|
||||||
return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
|
return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
|
||||||
} else {
|
} else {
|
||||||
|
@ -301,7 +301,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
|
public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
|
||||||
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
|
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||||
const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator);
|
const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator);
|
||||||
const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
|
const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
|
||||||
roleId: In(moderatorRoles.map(r => r.id)),
|
roleId: In(moderatorRoles.map(r => r.id)),
|
||||||
|
@ -321,7 +321,7 @@ export class RoleService implements OnApplicationShutdown {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getAdministratorIds(): Promise<User['id'][]> {
|
public async getAdministratorIds(): Promise<User['id'][]> {
|
||||||
const roles = await this.rolesCache.fetch(null, () => this.rolesRepository.findBy({}));
|
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
|
||||||
const administratorRoles = roles.filter(r => r.isAdministrator);
|
const administratorRoles = roles.filter(r => r.isAdministrator);
|
||||||
const assigns = administratorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
|
const assigns = administratorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({
|
||||||
roleId: In(administratorRoles.map(r => r.id)),
|
roleId: In(administratorRoles.map(r => r.id)),
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type { NotesRepository, UserPublickeysRepository, UsersRepository } from
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
import type { UserPublickey } from '@/models/entities/UserPublickey.js';
|
import type { UserPublickey } from '@/models/entities/UserPublickey.js';
|
||||||
import { UserCacheService } from '@/core/UserCacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import type { Note } from '@/models/entities/Note.js';
|
import type { Note } from '@/models/entities/Note.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { RemoteUser, User } from '@/models/entities/User.js';
|
import { RemoteUser, User } from '@/models/entities/User.js';
|
||||||
|
@ -47,7 +47,7 @@ export class ApDbResolverService {
|
||||||
@Inject(DI.userPublickeysRepository)
|
@Inject(DI.userPublickeysRepository)
|
||||||
private userPublickeysRepository: UserPublickeysRepository,
|
private userPublickeysRepository: UserPublickeysRepository,
|
||||||
|
|
||||||
private userCacheService: UserCacheService,
|
private cacheService: CacheService,
|
||||||
private apPersonService: ApPersonService,
|
private apPersonService: ApPersonService,
|
||||||
) {
|
) {
|
||||||
this.publicKeyCache = new MemoryKVCache<UserPublickey | null>(Infinity);
|
this.publicKeyCache = new MemoryKVCache<UserPublickey | null>(Infinity);
|
||||||
|
@ -107,11 +107,11 @@ export class ApDbResolverService {
|
||||||
if (parsed.local) {
|
if (parsed.local) {
|
||||||
if (parsed.type !== 'users') return null;
|
if (parsed.type !== 'users') return null;
|
||||||
|
|
||||||
return await this.userCacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
|
return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
|
||||||
id: parsed.id,
|
id: parsed.id,
|
||||||
}).then(x => x ?? undefined)) ?? null;
|
}).then(x => x ?? undefined)) ?? null;
|
||||||
} else {
|
} else {
|
||||||
return await this.userCacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
|
return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
|
||||||
uri: parsed.uri,
|
uri: parsed.uri,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ export class ApDbResolverService {
|
||||||
if (key == null) return null;
|
if (key == null) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: await this.userCacheService.findById(key.userId) as RemoteUser,
|
user: await this.cacheService.findUserById(key.userId) as RemoteUser,
|
||||||
key,
|
key,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { Config } from '@/config.js';
|
||||||
import type { RemoteUser } from '@/models/entities/User.js';
|
import type { RemoteUser } from '@/models/entities/User.js';
|
||||||
import { User } from '@/models/entities/User.js';
|
import { User } from '@/models/entities/User.js';
|
||||||
import { truncate } from '@/misc/truncate.js';
|
import { truncate } from '@/misc/truncate.js';
|
||||||
import type { UserCacheService } from '@/core/UserCacheService.js';
|
import type { CacheService } from '@/core/CacheService.js';
|
||||||
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
|
@ -54,7 +54,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
private metaService: MetaService;
|
private metaService: MetaService;
|
||||||
private federatedInstanceService: FederatedInstanceService;
|
private federatedInstanceService: FederatedInstanceService;
|
||||||
private fetchInstanceMetadataService: FetchInstanceMetadataService;
|
private fetchInstanceMetadataService: FetchInstanceMetadataService;
|
||||||
private userCacheService: UserCacheService;
|
private cacheService: CacheService;
|
||||||
private apResolverService: ApResolverService;
|
private apResolverService: ApResolverService;
|
||||||
private apNoteService: ApNoteService;
|
private apNoteService: ApNoteService;
|
||||||
private apImageService: ApImageService;
|
private apImageService: ApImageService;
|
||||||
|
@ -97,7 +97,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
//private metaService: MetaService,
|
//private metaService: MetaService,
|
||||||
//private federatedInstanceService: FederatedInstanceService,
|
//private federatedInstanceService: FederatedInstanceService,
|
||||||
//private fetchInstanceMetadataService: FetchInstanceMetadataService,
|
//private fetchInstanceMetadataService: FetchInstanceMetadataService,
|
||||||
//private userCacheService: UserCacheService,
|
//private cacheService: CacheService,
|
||||||
//private apResolverService: ApResolverService,
|
//private apResolverService: ApResolverService,
|
||||||
//private apNoteService: ApNoteService,
|
//private apNoteService: ApNoteService,
|
||||||
//private apImageService: ApImageService,
|
//private apImageService: ApImageService,
|
||||||
|
@ -118,7 +118,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
this.metaService = this.moduleRef.get('MetaService');
|
this.metaService = this.moduleRef.get('MetaService');
|
||||||
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
|
this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
|
||||||
this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
|
this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
|
||||||
this.userCacheService = this.moduleRef.get('UserCacheService');
|
this.cacheService = this.moduleRef.get('CacheService');
|
||||||
this.apResolverService = this.moduleRef.get('ApResolverService');
|
this.apResolverService = this.moduleRef.get('ApResolverService');
|
||||||
this.apNoteService = this.moduleRef.get('ApNoteService');
|
this.apNoteService = this.moduleRef.get('ApNoteService');
|
||||||
this.apImageService = this.moduleRef.get('ApImageService');
|
this.apImageService = this.moduleRef.get('ApImageService');
|
||||||
|
@ -207,14 +207,14 @@ export class ApPersonService implements OnModuleInit {
|
||||||
public async fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
|
public async fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
|
||||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||||
|
|
||||||
const cached = this.userCacheService.uriPersonCache.get(uri);
|
const cached = this.cacheService.uriPersonCache.get(uri);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
|
|
||||||
// URIがこのサーバーを指しているならデータベースからフェッチ
|
// URIがこのサーバーを指しているならデータベースからフェッチ
|
||||||
if (uri.startsWith(this.config.url + '/')) {
|
if (uri.startsWith(this.config.url + '/')) {
|
||||||
const id = uri.split('/').pop();
|
const id = uri.split('/').pop();
|
||||||
const u = await this.usersRepository.findOneBy({ id });
|
const u = await this.usersRepository.findOneBy({ id });
|
||||||
if (u) this.userCacheService.uriPersonCache.set(uri, u);
|
if (u) this.cacheService.uriPersonCache.set(uri, u);
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ export class ApPersonService implements OnModuleInit {
|
||||||
const exist = await this.usersRepository.findOneBy({ uri });
|
const exist = await this.usersRepository.findOneBy({ uri });
|
||||||
|
|
||||||
if (exist) {
|
if (exist) {
|
||||||
this.userCacheService.uriPersonCache.set(uri, exist);
|
this.cacheService.uriPersonCache.set(uri, exist);
|
||||||
return exist;
|
return exist;
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
|
@ -54,6 +54,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||||
public async pack(
|
public async pack(
|
||||||
src: Notification,
|
src: Notification,
|
||||||
meId: User['id'],
|
meId: User['id'],
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
options: {
|
options: {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
|
@ -255,7 +255,6 @@ export class UserEntityService implements OnModuleInit {
|
||||||
'+',
|
'+',
|
||||||
'-',
|
'-',
|
||||||
'COUNT', 1);
|
'COUNT', 1);
|
||||||
console.log('latestNotificationIdsRes', latestNotificationIdsRes);
|
|
||||||
const latestNotificationId = latestNotificationIdsRes[0]?.[0];
|
const latestNotificationId = latestNotificationIdsRes[0]?.[0];
|
||||||
|
|
||||||
return latestNotificationId != null && (latestReadNotificationId == null || latestReadNotificationId < latestNotificationId);
|
return latestNotificationId != null && (latestReadNotificationId == null || latestReadNotificationId < latestNotificationId);
|
||||||
|
|
|
@ -1,9 +1,94 @@
|
||||||
|
import Redis from 'ioredis';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
|
||||||
|
// redis通すとDateのインスタンスはstringに変換されるので
|
||||||
|
type Serialized<T> = {
|
||||||
|
[K in keyof T]:
|
||||||
|
T[K] extends Date
|
||||||
|
? string
|
||||||
|
: T[K] extends (Date | null)
|
||||||
|
? (string | null)
|
||||||
|
: T[K] extends Record<string, any>
|
||||||
|
? Serialized<T[K]>
|
||||||
|
: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
export class RedisKVCache<T> {
|
||||||
|
private redisClient: Redis.Redis;
|
||||||
|
private name: string;
|
||||||
|
private lifetime: number;
|
||||||
|
private memoryCache: MemoryKVCache<T>;
|
||||||
|
|
||||||
|
constructor(redisClient: RedisKVCache<never>['redisClient'], name: RedisKVCache<never>['name'], lifetime: RedisKVCache<never>['lifetime'], memoryCacheLifetime: number) {
|
||||||
|
this.redisClient = redisClient;
|
||||||
|
this.name = name;
|
||||||
|
this.lifetime = lifetime;
|
||||||
|
this.memoryCache = new MemoryKVCache(memoryCacheLifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async set(key: string, value: T): Promise<void> {
|
||||||
|
this.memoryCache.set(key, value);
|
||||||
|
if (this.lifetime === Infinity) {
|
||||||
|
await this.redisClient.set(
|
||||||
|
`kvcache:${this.name}:${key}`,
|
||||||
|
JSON.stringify(value),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await this.redisClient.set(
|
||||||
|
`kvcache:${this.name}:${key}`,
|
||||||
|
JSON.stringify(value),
|
||||||
|
'ex', Math.round(this.lifetime / 1000),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async get(key: string): Promise<Serialized<T> | T | undefined> {
|
||||||
|
const memoryCached = this.memoryCache.get(key);
|
||||||
|
if (memoryCached !== undefined) return memoryCached;
|
||||||
|
|
||||||
|
const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`);
|
||||||
|
if (cached == null) return undefined;
|
||||||
|
return JSON.parse(cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public async delete(key: string): Promise<void> {
|
||||||
|
this.memoryCache.delete(key);
|
||||||
|
await this.redisClient.del(`kvcache:${this.name}:${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
|
||||||
|
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: Serialized<T> | T) => boolean): Promise<Serialized<T> | T> {
|
||||||
|
const cachedValue = await this.get(key);
|
||||||
|
if (cachedValue !== undefined) {
|
||||||
|
if (validator) {
|
||||||
|
if (validator(cachedValue)) {
|
||||||
|
// Cache HIT
|
||||||
|
return cachedValue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Cache HIT
|
||||||
|
return cachedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache MISS
|
||||||
|
const value = await fetcher();
|
||||||
|
this.set(key, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
|
// TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
|
||||||
|
|
||||||
export class MemoryKVCache<T> {
|
export class MemoryKVCache<T> {
|
||||||
public cache: Map<string | null, { date: number; value: T; }>;
|
public cache: Map<string, { date: number; value: T; }>;
|
||||||
private lifetime: number;
|
private lifetime: number;
|
||||||
|
|
||||||
constructor(lifetime: MemoryKVCache<never>['lifetime']) {
|
constructor(lifetime: MemoryKVCache<never>['lifetime']) {
|
||||||
|
@ -12,7 +97,7 @@ export class MemoryKVCache<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public set(key: string | null, value: T): void {
|
public set(key: string, value: T): void {
|
||||||
this.cache.set(key, {
|
this.cache.set(key, {
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
value,
|
value,
|
||||||
|
@ -20,7 +105,7 @@ export class MemoryKVCache<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public get(key: string | null): T | undefined {
|
public get(key: string): T | undefined {
|
||||||
const cached = this.cache.get(key);
|
const cached = this.cache.get(key);
|
||||||
if (cached == null) return undefined;
|
if (cached == null) return undefined;
|
||||||
if ((Date.now() - cached.date) > this.lifetime) {
|
if ((Date.now() - cached.date) > this.lifetime) {
|
||||||
|
@ -31,7 +116,7 @@ export class MemoryKVCache<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public delete(key: string | null) {
|
public delete(key: string) {
|
||||||
this.cache.delete(key);
|
this.cache.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +125,7 @@ export class MemoryKVCache<T> {
|
||||||
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
|
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async fetch(key: string | null, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
|
public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
|
||||||
const cachedValue = this.get(key);
|
const cachedValue = this.get(key);
|
||||||
if (cachedValue !== undefined) {
|
if (cachedValue !== undefined) {
|
||||||
if (validator) {
|
if (validator) {
|
||||||
|
@ -65,7 +150,7 @@ export class MemoryKVCache<T> {
|
||||||
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
|
* optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async fetchMaybe(key: string | null, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
|
public async fetchMaybe(key: string, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
|
||||||
const cachedValue = this.get(key);
|
const cachedValue = this.get(key);
|
||||||
if (cachedValue !== undefined) {
|
if (cachedValue !== undefined) {
|
||||||
if (validator) {
|
if (validator) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { MetaService } from '@/core/MetaService.js';
|
||||||
import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
||||||
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
|
||||||
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
|
import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryCache } from '@/misc/cache.js';
|
||||||
import type { Instance } from '@/models/entities/Instance.js';
|
import type { Instance } from '@/models/entities/Instance.js';
|
||||||
import InstanceChart from '@/core/chart/charts/instance.js';
|
import InstanceChart from '@/core/chart/charts/instance.js';
|
||||||
import ApRequestChart from '@/core/chart/charts/ap-request.js';
|
import ApRequestChart from '@/core/chart/charts/ap-request.js';
|
||||||
|
@ -22,7 +22,7 @@ import type { DeliverJobData } from '../types.js';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeliverProcessorService {
|
export class DeliverProcessorService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
private suspendedHostsCache: MemoryKVCache<Instance[]>;
|
private suspendedHostsCache: MemoryCache<Instance[]>;
|
||||||
private latest: string | null;
|
private latest: string | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -46,7 +46,7 @@ export class DeliverProcessorService {
|
||||||
private queueLoggerService: QueueLoggerService,
|
private queueLoggerService: QueueLoggerService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
|
this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
|
||||||
this.suspendedHostsCache = new MemoryKVCache<Instance[]>(1000 * 60 * 60);
|
this.suspendedHostsCache = new MemoryCache<Instance[]>(1000 * 60 * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -60,14 +60,14 @@ export class DeliverProcessorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// isSuspendedなら中断
|
// isSuspendedなら中断
|
||||||
let suspendedHosts = this.suspendedHostsCache.get(null);
|
let suspendedHosts = this.suspendedHostsCache.get();
|
||||||
if (suspendedHosts == null) {
|
if (suspendedHosts == null) {
|
||||||
suspendedHosts = await this.instancesRepository.find({
|
suspendedHosts = await this.instancesRepository.find({
|
||||||
where: {
|
where: {
|
||||||
isSuspended: true,
|
isSuspended: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.suspendedHostsCache.set(null, suspendedHosts);
|
this.suspendedHostsCache.set(suspendedHosts);
|
||||||
}
|
}
|
||||||
if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) {
|
if (suspendedHosts.map(x => x.host).includes(this.utilityService.toPuny(host))) {
|
||||||
return 'skip (suspended)';
|
return 'skip (suspended)';
|
||||||
|
|
|
@ -4,7 +4,7 @@ import type { NotesRepository, UsersRepository } from '@/models/index.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryCache } from '@/misc/cache.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import NotesChart from '@/core/chart/charts/notes.js';
|
import NotesChart from '@/core/chart/charts/notes.js';
|
||||||
|
@ -118,17 +118,17 @@ export class NodeinfoServerService {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const cache = new MemoryKVCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
|
const cache = new MemoryCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
|
||||||
|
|
||||||
fastify.get(nodeinfo2_1path, async (request, reply) => {
|
fastify.get(nodeinfo2_1path, async (request, reply) => {
|
||||||
const base = await cache.fetch(null, () => nodeinfo2());
|
const base = await cache.fetch(() => nodeinfo2());
|
||||||
|
|
||||||
reply.header('Cache-Control', 'public, max-age=600');
|
reply.header('Cache-Control', 'public, max-age=600');
|
||||||
return { version: '2.1', ...base };
|
return { version: '2.1', ...base };
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.get(nodeinfo2_0path, async (request, reply) => {
|
fastify.get(nodeinfo2_0path, async (request, reply) => {
|
||||||
const base = await cache.fetch(null, () => nodeinfo2());
|
const base = await cache.fetch(() => nodeinfo2());
|
||||||
|
|
||||||
delete (base as any).software.repository;
|
delete (base as any).software.repository;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import type { LocalUser } from '@/models/entities/User.js';
|
||||||
import type { AccessToken } from '@/models/entities/AccessToken.js';
|
import type { AccessToken } from '@/models/entities/AccessToken.js';
|
||||||
import { MemoryKVCache } from '@/misc/cache.js';
|
import { MemoryKVCache } from '@/misc/cache.js';
|
||||||
import type { App } from '@/models/entities/App.js';
|
import type { App } from '@/models/entities/App.js';
|
||||||
import { UserCacheService } from '@/core/UserCacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import isNativeToken from '@/misc/is-native-token.js';
|
import isNativeToken from '@/misc/is-native-token.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export class AuthenticateService {
|
||||||
@Inject(DI.appsRepository)
|
@Inject(DI.appsRepository)
|
||||||
private appsRepository: AppsRepository,
|
private appsRepository: AppsRepository,
|
||||||
|
|
||||||
private userCacheService: UserCacheService,
|
private cacheService: CacheService,
|
||||||
) {
|
) {
|
||||||
this.appCache = new MemoryKVCache<App>(Infinity);
|
this.appCache = new MemoryKVCache<App>(Infinity);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ export class AuthenticateService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNativeToken(token)) {
|
if (isNativeToken(token)) {
|
||||||
const user = await this.userCacheService.localUserByNativeTokenCache.fetch(token,
|
const user = await this.cacheService.localUserByNativeTokenCache.fetch(token,
|
||||||
() => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>);
|
() => this.usersRepository.findOneBy({ token }) as Promise<LocalUser | null>);
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
@ -67,7 +67,7 @@ export class AuthenticateService {
|
||||||
lastUsedAt: new Date(),
|
lastUsedAt: new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await this.userCacheService.localUserByIdCache.fetch(accessToken.userId,
|
const user = await this.cacheService.localUserByIdCache.fetch(accessToken.userId,
|
||||||
() => this.usersRepository.findOneBy({
|
() => this.usersRepository.findOneBy({
|
||||||
id: accessToken.userId,
|
id: accessToken.userId,
|
||||||
}) as Promise<LocalUser>);
|
}) as Promise<LocalUser>);
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id });
|
const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id });
|
||||||
const oldToken = freshUser.token;
|
const oldToken = freshUser.token!;
|
||||||
|
|
||||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
|
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { AccountUpdateService } from '@/core/AccountUpdateService.js';
|
||||||
import { HashtagService } from '@/core/HashtagService.js';
|
import { HashtagService } from '@/core/HashtagService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -152,6 +153,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private accountUpdateService: AccountUpdateService,
|
private accountUpdateService: AccountUpdateService,
|
||||||
private hashtagService: HashtagService,
|
private hashtagService: HashtagService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
|
private cacheService: CacheService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, _user, token) => {
|
super(meta, paramDef, async (ps, _user, token) => {
|
||||||
const user = await this.usersRepository.findOneByOrFail({ id: _user.id });
|
const user = await this.usersRepository.findOneByOrFail({ id: _user.id });
|
||||||
|
@ -276,9 +278,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
includeSecrets: isSecure,
|
includeSecrets: isSecure,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updatedProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
|
||||||
|
|
||||||
|
this.cacheService.userProfileCache.set(user.id, updatedProfile);
|
||||||
|
|
||||||
// Publish meUpdated event
|
// Publish meUpdated event
|
||||||
this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj);
|
this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj);
|
||||||
this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneByOrFail({ userId: user.id }));
|
this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', updatedProfile);
|
||||||
|
|
||||||
// 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
|
// 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
|
||||||
if (user.isLocked && ps.isLocked === false) {
|
if (user.isLocked && ps.isLocked === false) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import type { Muting } from '@/models/entities/Muting.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { GetterService } from '@/server/api/GetterService.js';
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -65,6 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private getterService: GetterService,
|
private getterService: GetterService,
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
|
private cacheService: CacheService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
const muter = me;
|
const muter = me;
|
||||||
|
@ -103,6 +105,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
muteeId: mutee.id,
|
muteeId: mutee.id,
|
||||||
} as Muting);
|
} as Muting);
|
||||||
|
|
||||||
|
this.cacheService.userMutingsCache.delete(muter.id);
|
||||||
this.globalEventService.publishUserEvent(me.id, 'mute', mutee);
|
this.globalEventService.publishUserEvent(me.id, 'mute', mutee);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,8 +92,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
muterId: muter.id,
|
muterId: muter.id,
|
||||||
muteeId: mutee.id,
|
muteeId: mutee.id,
|
||||||
} as RenoteMuting);
|
} as RenoteMuting);
|
||||||
|
|
||||||
// publishUserEvent(user.id, 'mute', mutee);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import type { EventEmitter } from 'events';
|
||||||
//#region Stream type-body definitions
|
//#region Stream type-body definitions
|
||||||
export interface InternalStreamTypes {
|
export interface InternalStreamTypes {
|
||||||
userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; };
|
userChangeSuspendedState: { id: User['id']; isSuspended: User['isSuspended']; };
|
||||||
userTokenRegenerated: { id: User['id']; oldToken: User['token']; newToken: User['token']; };
|
userTokenRegenerated: { id: User['id']; oldToken: string; newToken: string; };
|
||||||
remoteUserUpdated: { id: User['id']; };
|
remoteUserUpdated: { id: User['id']; };
|
||||||
follow: { followerId: User['id']; followeeId: User['id']; };
|
follow: { followerId: User['id']; followeeId: User['id']; };
|
||||||
unfollow: { followerId: User['id']; followeeId: User['id']; };
|
unfollow: { followerId: User['id']; followeeId: User['id']; };
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { Role, RolesRepository, RoleAssignmentsRepository, UsersRepository,
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
import { genAid } from '@/misc/id/aid.js';
|
import { genAid } from '@/misc/id/aid.js';
|
||||||
import { UserCacheService } from '@/core/UserCacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import { sleep } from '../utils.js';
|
import { sleep } from '../utils.js';
|
||||||
|
@ -65,7 +65,7 @@ describe('RoleService', () => {
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
RoleService,
|
RoleService,
|
||||||
UserCacheService,
|
CacheService,
|
||||||
IdService,
|
IdService,
|
||||||
GlobalEventService,
|
GlobalEventService,
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue