From 0ffd6d32c13b3b59f96a212ebfd324ba06cbdf1f Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 4 Feb 2020 11:26:51 +0100 Subject: [PATCH] Use a singleton for model cache --- server/models/account/account.ts | 67 ++++++++++++------------ server/models/activitypub/actor.ts | 82 ++++++++++++++---------------- server/models/model-cache.ts | 54 ++++++++++++++++++++ 3 files changed, 124 insertions(+), 79 deletions(-) create mode 100644 server/models/model-cache.ts diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 0905a0fb2..a0081f259 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -32,8 +32,9 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ import { AccountBlocklistModel } from './account-blocklist' import { ServerBlocklistModel } from '../server/server-blocklist' import { ActorFollowModel } from '../activitypub/actor-follow' -import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models' +import { MAccountActor, MAccountAP, MAccountDefault, MAccountFormattable, MAccountSummaryFormattable } from '../../typings/models' import * as Bluebird from 'bluebird' +import { ModelCache } from '@server/models/model-cache' export enum ScopeNames { SUMMARY = 'SUMMARY' @@ -218,8 +219,6 @@ export class AccountModel extends Model { }) BlockedAccounts: AccountBlocklistModel[] - private static cache: { [ id: string ]: any } = {} - @BeforeDestroy static async sendDeleteIfOwned (instance: AccountModel, options) { if (!instance.Actor) { @@ -247,45 +246,43 @@ export class AccountModel extends Model { } static loadLocalByName (name: string): Bluebird { - // The server actor never change, so we can easily cache it - if (name === SERVER_ACTOR_NAME && AccountModel.cache[name]) { - return Bluebird.resolve(AccountModel.cache[name]) - } - - const query = { - where: { - [Op.or]: [ - { - userId: { - [Op.ne]: null + const fun = () => { + const query = { + where: { + [Op.or]: [ + { + userId: { + [Op.ne]: null + } + }, + { + applicationId: { + [Op.ne]: null + } } - }, + ] + }, + include: [ { - applicationId: { - [Op.ne]: null + model: ActorModel, + required: true, + where: { + preferredUsername: name } } ] - }, - include: [ - { - model: ActorModel, - required: true, - where: { - preferredUsername: name - } - } - ] + } + + return AccountModel.findOne(query) } - return AccountModel.findOne(query) - .then(account => { - if (name === SERVER_ACTOR_NAME) { - AccountModel.cache[name] = account - } - - return account - }) + return ModelCache.Instance.doCache({ + cacheType: 'local-account-name', + key: name, + fun, + // The server actor never change, so we can easily cache it + whitelist: () => name === SERVER_ACTOR_NAME + }) } static loadByNameAndHost (name: string, host: string): Bluebird { diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 00e8dc954..9e8303a7b 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts @@ -48,6 +48,7 @@ import { } from '../../typings/models' import * as Bluebird from 'bluebird' import { Op, Transaction, literal } from 'sequelize' +import { ModelCache } from '@server/models/model-cache' enum ScopeNames { FULL = 'FULL' @@ -276,9 +277,6 @@ export class ActorModel extends Model { }) VideoChannel: VideoChannelModel - private static localNameCache: { [ id: string ]: any } = {} - private static localUrlCache: { [ id: string ]: any } = {} - static load (id: number): Bluebird { return ActorModel.unscoped().findByPk(id) } @@ -345,54 +343,50 @@ export class ActorModel extends Model { } static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird { - // The server actor never change, so we can easily cache it - if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localNameCache[preferredUsername]) { - return Bluebird.resolve(ActorModel.localNameCache[preferredUsername]) + const fun = () => { + const query = { + where: { + preferredUsername, + serverId: null + }, + transaction + } + + return ActorModel.scope(ScopeNames.FULL) + .findOne(query) } - const query = { - where: { - preferredUsername, - serverId: null - }, - transaction - } - - return ActorModel.scope(ScopeNames.FULL) - .findOne(query) - .then(actor => { - if (preferredUsername === SERVER_ACTOR_NAME) { - ActorModel.localNameCache[preferredUsername] = actor - } - - return actor - }) + return ModelCache.Instance.doCache({ + cacheType: 'local-actor-name', + key: preferredUsername, + // The server actor never change, so we can easily cache it + whitelist: () => preferredUsername === SERVER_ACTOR_NAME, + fun + }) } static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Bluebird { - // The server actor never change, so we can easily cache it - if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localUrlCache[preferredUsername]) { - return Bluebird.resolve(ActorModel.localUrlCache[preferredUsername]) + const fun = () => { + const query = { + attributes: [ 'url' ], + where: { + preferredUsername, + serverId: null + }, + transaction + } + + return ActorModel.unscoped() + .findOne(query) } - const query = { - attributes: [ 'url' ], - where: { - preferredUsername, - serverId: null - }, - transaction - } - - return ActorModel.unscoped() - .findOne(query) - .then(actor => { - if (preferredUsername === SERVER_ACTOR_NAME) { - ActorModel.localUrlCache[preferredUsername] = actor - } - - return actor - }) + return ModelCache.Instance.doCache({ + cacheType: 'local-actor-name', + key: preferredUsername, + // The server actor never change, so we can easily cache it + whitelist: () => preferredUsername === SERVER_ACTOR_NAME, + fun + }) } static loadByNameAndHost (preferredUsername: string, host: string): Bluebird { diff --git a/server/models/model-cache.ts b/server/models/model-cache.ts new file mode 100644 index 000000000..bfa163b6b --- /dev/null +++ b/server/models/model-cache.ts @@ -0,0 +1,54 @@ +import { Model } from 'sequelize-typescript' +import * as Bluebird from 'bluebird' +import { logger } from '@server/helpers/logger' + +type ModelCacheType = + 'local-account-name' + | 'local-actor-name' + | 'local-actor-url' + +class ModelCache { + + private static instance: ModelCache + + private readonly localCache: { [id in ModelCacheType]: Map } = { + 'local-account-name': new Map(), + 'local-actor-name': new Map(), + 'local-actor-url': new Map() + } + + private constructor () { + } + + static get Instance () { + return this.instance || (this.instance = new this()) + } + + doCache (options: { + cacheType: ModelCacheType + key: string + fun: () => Bluebird + whitelist?: () => boolean + }) { + const { cacheType, key, fun, whitelist } = options + + if (whitelist && whitelist() !== true) return fun() + + const cache = this.localCache[cacheType] + + if (cache.has(key)) { + logger.debug('Model cache hit for %s -> %s.', cacheType, key) + return Bluebird.resolve(cache.get(key)) + } + + return fun().then(m => { + if (!whitelist || whitelist()) cache.set(key, m) + + return m + }) + } +} + +export { + ModelCache +}