aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
Diffstat (limited to 'server/models')
-rw-r--r--server/models/account/account.ts67
-rw-r--r--server/models/activitypub/actor.ts108
-rw-r--r--server/models/model-cache.ts91
-rw-r--r--server/models/video/video.ts50
4 files changed, 238 insertions, 78 deletions
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
32import { AccountBlocklistModel } from './account-blocklist' 32import { AccountBlocklistModel } from './account-blocklist'
33import { ServerBlocklistModel } from '../server/server-blocklist' 33import { ServerBlocklistModel } from '../server/server-blocklist'
34import { ActorFollowModel } from '../activitypub/actor-follow' 34import { ActorFollowModel } from '../activitypub/actor-follow'
35import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models' 35import { MAccountActor, MAccountAP, MAccountDefault, MAccountFormattable, MAccountSummaryFormattable } from '../../typings/models'
36import * as Bluebird from 'bluebird' 36import * as Bluebird from 'bluebird'
37import { ModelCache } from '@server/models/model-cache'
37 38
38export enum ScopeNames { 39export enum ScopeNames {
39 SUMMARY = 'SUMMARY' 40 SUMMARY = 'SUMMARY'
@@ -218,8 +219,6 @@ export class AccountModel extends Model<AccountModel> {
218 }) 219 })
219 BlockedAccounts: AccountBlocklistModel[] 220 BlockedAccounts: AccountBlocklistModel[]
220 221
221 private static cache: { [ id: string ]: any } = {}
222
223 @BeforeDestroy 222 @BeforeDestroy
224 static async sendDeleteIfOwned (instance: AccountModel, options) { 223 static async sendDeleteIfOwned (instance: AccountModel, options) {
225 if (!instance.Actor) { 224 if (!instance.Actor) {
@@ -247,45 +246,43 @@ export class AccountModel extends Model<AccountModel> {
247 } 246 }
248 247
249 static loadLocalByName (name: string): Bluebird<MAccountDefault> { 248 static loadLocalByName (name: string): Bluebird<MAccountDefault> {
250 // The server actor never change, so we can easily cache it 249 const fun = () => {
251 if (name === SERVER_ACTOR_NAME && AccountModel.cache[name]) { 250 const query = {
252 return Bluebird.resolve(AccountModel.cache[name]) 251 where: {
253 } 252 [Op.or]: [
254 253 {
255 const query = { 254 userId: {
256 where: { 255 [Op.ne]: null
257 [Op.or]: [ 256 }
258 { 257 },
259 userId: { 258 {
260 [Op.ne]: null 259 applicationId: {
260 [Op.ne]: null
261 }
261 } 262 }
262 }, 263 ]
264 },
265 include: [
263 { 266 {
264 applicationId: { 267 model: ActorModel,
265 [Op.ne]: null 268 required: true,
269 where: {
270 preferredUsername: name
266 } 271 }
267 } 272 }
268 ] 273 ]
269 }, 274 }
270 include: [
271 {
272 model: ActorModel,
273 required: true,
274 where: {
275 preferredUsername: name
276 }
277 }
278 ]
279 }
280 275
281 return AccountModel.findOne(query) 276 return AccountModel.findOne(query)
282 .then(account => { 277 }
283 if (name === SERVER_ACTOR_NAME) {
284 AccountModel.cache[name] = account
285 }
286 278
287 return account 279 return ModelCache.Instance.doCache({
288 }) 280 cacheType: 'local-account-name',
281 key: name,
282 fun,
283 // The server actor never change, so we can easily cache it
284 whitelist: () => name === SERVER_ACTOR_NAME
285 })
289 } 286 }
290 287
291 static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> { 288 static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> {
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 00e8dc954..e547d2c0c 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -48,6 +48,7 @@ import {
48} from '../../typings/models' 48} from '../../typings/models'
49import * as Bluebird from 'bluebird' 49import * as Bluebird from 'bluebird'
50import { Op, Transaction, literal } from 'sequelize' 50import { Op, Transaction, literal } from 'sequelize'
51import { ModelCache } from '@server/models/model-cache'
51 52
52enum ScopeNames { 53enum ScopeNames {
53 FULL = 'FULL' 54 FULL = 'FULL'
@@ -276,9 +277,6 @@ export class ActorModel extends Model<ActorModel> {
276 }) 277 })
277 VideoChannel: VideoChannelModel 278 VideoChannel: VideoChannelModel
278 279
279 private static localNameCache: { [ id: string ]: any } = {}
280 private static localUrlCache: { [ id: string ]: any } = {}
281
282 static load (id: number): Bluebird<MActor> { 280 static load (id: number): Bluebird<MActor> {
283 return ActorModel.unscoped().findByPk(id) 281 return ActorModel.unscoped().findByPk(id)
284 } 282 }
@@ -345,54 +343,50 @@ export class ActorModel extends Model<ActorModel> {
345 } 343 }
346 344
347 static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> { 345 static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> {
348 // The server actor never change, so we can easily cache it 346 const fun = () => {
349 if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localNameCache[preferredUsername]) { 347 const query = {
350 return Bluebird.resolve(ActorModel.localNameCache[preferredUsername]) 348 where: {
351 } 349 preferredUsername,
350 serverId: null
351 },
352 transaction
353 }
352 354
353 const query = { 355 return ActorModel.scope(ScopeNames.FULL)
354 where: { 356 .findOne(query)
355 preferredUsername,
356 serverId: null
357 },
358 transaction
359 } 357 }
360 358
361 return ActorModel.scope(ScopeNames.FULL) 359 return ModelCache.Instance.doCache({
362 .findOne(query) 360 cacheType: 'local-actor-name',
363 .then(actor => { 361 key: preferredUsername,
364 if (preferredUsername === SERVER_ACTOR_NAME) { 362 // The server actor never change, so we can easily cache it
365 ActorModel.localNameCache[preferredUsername] = actor 363 whitelist: () => preferredUsername === SERVER_ACTOR_NAME,
366 } 364 fun
367 365 })
368 return actor
369 })
370 } 366 }
371 367
372 static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorUrl> { 368 static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorUrl> {
373 // The server actor never change, so we can easily cache it 369 const fun = () => {
374 if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localUrlCache[preferredUsername]) { 370 const query = {
375 return Bluebird.resolve(ActorModel.localUrlCache[preferredUsername]) 371 attributes: [ 'url' ],
376 } 372 where: {
373 preferredUsername,
374 serverId: null
375 },
376 transaction
377 }
377 378
378 const query = { 379 return ActorModel.unscoped()
379 attributes: [ 'url' ], 380 .findOne(query)
380 where: {
381 preferredUsername,
382 serverId: null
383 },
384 transaction
385 } 381 }
386 382
387 return ActorModel.unscoped() 383 return ModelCache.Instance.doCache({
388 .findOne(query) 384 cacheType: 'local-actor-name',
389 .then(actor => { 385 key: preferredUsername,
390 if (preferredUsername === SERVER_ACTOR_NAME) { 386 // The server actor never change, so we can easily cache it
391 ActorModel.localUrlCache[preferredUsername] = actor 387 whitelist: () => preferredUsername === SERVER_ACTOR_NAME,
392 } 388 fun
393 389 })
394 return actor
395 })
396 } 390 }
397 391
398 static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> { 392 static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> {
@@ -468,6 +462,36 @@ export class ActorModel extends Model<ActorModel> {
468 }, { where, transaction }) 462 }, { where, transaction })
469 } 463 }
470 464
465 static loadAccountActorByVideoId (videoId: number): Bluebird<MActor> {
466 const query = {
467 include: [
468 {
469 attributes: [ 'id' ],
470 model: AccountModel.unscoped(),
471 required: true,
472 include: [
473 {
474 attributes: [ 'id', 'accountId' ],
475 model: VideoChannelModel.unscoped(),
476 required: true,
477 include: [
478 {
479 attributes: [ 'id', 'channelId' ],
480 model: VideoModel.unscoped(),
481 where: {
482 id: videoId
483 }
484 }
485 ]
486 }
487 ]
488 }
489 ]
490 }
491
492 return ActorModel.unscoped().findOne(query)
493 }
494
471 getSharedInbox (this: MActorWithInboxes) { 495 getSharedInbox (this: MActorWithInboxes) {
472 return this.sharedInboxUrl || this.inboxUrl 496 return this.sharedInboxUrl || this.inboxUrl
473 } 497 }
diff --git a/server/models/model-cache.ts b/server/models/model-cache.ts
new file mode 100644
index 000000000..a87f99aa2
--- /dev/null
+++ b/server/models/model-cache.ts
@@ -0,0 +1,91 @@
1import { Model } from 'sequelize-typescript'
2import * as Bluebird from 'bluebird'
3import { logger } from '@server/helpers/logger'
4
5type ModelCacheType =
6 'local-account-name'
7 | 'local-actor-name'
8 | 'local-actor-url'
9 | 'load-video-immutable-id'
10 | 'load-video-immutable-url'
11
12type DeleteKey =
13 'video'
14
15class ModelCache {
16
17 private static instance: ModelCache
18
19 private readonly localCache: { [id in ModelCacheType]: Map<string, any> } = {
20 'local-account-name': new Map(),
21 'local-actor-name': new Map(),
22 'local-actor-url': new Map(),
23 'load-video-immutable-id': new Map(),
24 'load-video-immutable-url': new Map()
25 }
26
27 private readonly deleteIds: {
28 [deleteKey in DeleteKey]: Map<number, { cacheType: ModelCacheType, key: string }[]>
29 } = {
30 video: new Map()
31 }
32
33 private constructor () {
34 }
35
36 static get Instance () {
37 return this.instance || (this.instance = new this())
38 }
39
40 doCache<T extends Model> (options: {
41 cacheType: ModelCacheType
42 key: string
43 fun: () => Bluebird<T>
44 whitelist?: () => boolean
45 deleteKey?: DeleteKey
46 }) {
47 const { cacheType, key, fun, whitelist, deleteKey } = options
48
49 if (whitelist && whitelist() !== true) return fun()
50
51 const cache = this.localCache[cacheType]
52
53 if (cache.has(key)) {
54 logger.debug('Model cache hit for %s -> %s.', cacheType, key)
55 return Bluebird.resolve<T>(cache.get(key))
56 }
57
58 return fun().then(m => {
59 if (!m) return m
60
61 if (!whitelist || whitelist()) cache.set(key, m)
62
63 if (deleteKey) {
64 const map = this.deleteIds[deleteKey]
65 if (!map.has(m.id)) map.set(m.id, [])
66
67 const a = map.get(m.id)
68 a.push({ cacheType, key })
69 }
70
71 return m
72 })
73 }
74
75 invalidateCache (deleteKey: DeleteKey, modelId: number) {
76 const map = this.deleteIds[deleteKey]
77
78 if (!map.has(modelId)) return
79
80 for (const toDelete of map.get(modelId)) {
81 logger.debug('Removing %s -> %d of model cache %s -> %s.', deleteKey, modelId, toDelete.cacheType, toDelete.key)
82 this.localCache[toDelete.cacheType].delete(toDelete.key)
83 }
84
85 map.delete(modelId)
86 }
87}
88
89export {
90 ModelCache
91}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 1ec8d717e..5964526a9 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -120,7 +120,7 @@ import {
120 MVideoFormattableDetails, 120 MVideoFormattableDetails,
121 MVideoForUser, 121 MVideoForUser,
122 MVideoFullLight, 122 MVideoFullLight,
123 MVideoIdThumbnail, 123 MVideoIdThumbnail, MVideoImmutable,
124 MVideoThumbnail, 124 MVideoThumbnail,
125 MVideoThumbnailBlacklist, 125 MVideoThumbnailBlacklist,
126 MVideoWithAllFiles, 126 MVideoWithAllFiles,
@@ -132,6 +132,7 @@ import { MThumbnail } from '../../typings/models/video/thumbnail'
132import { VideoFile } from '@shared/models/videos/video-file.model' 132import { VideoFile } from '@shared/models/videos/video-file.model'
133import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 133import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
134import validator from 'validator' 134import validator from 'validator'
135import { ModelCache } from '@server/models/model-cache'
135 136
136export enum ScopeNames { 137export enum ScopeNames {
137 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', 138 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS',
@@ -144,6 +145,7 @@ export enum ScopeNames {
144 WITH_USER_HISTORY = 'WITH_USER_HISTORY', 145 WITH_USER_HISTORY = 'WITH_USER_HISTORY',
145 WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', 146 WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS',
146 WITH_USER_ID = 'WITH_USER_ID', 147 WITH_USER_ID = 'WITH_USER_ID',
148 WITH_IMMUTABLE_ATTRIBUTES = 'WITH_IMMUTABLE_ATTRIBUTES',
147 WITH_THUMBNAILS = 'WITH_THUMBNAILS' 149 WITH_THUMBNAILS = 'WITH_THUMBNAILS'
148} 150}
149 151
@@ -187,6 +189,9 @@ export type AvailableForListIDsOptions = {
187} 189}
188 190
189@Scopes(() => ({ 191@Scopes(() => ({
192 [ScopeNames.WITH_IMMUTABLE_ATTRIBUTES]: {
193 attributes: [ 'id', 'url', 'uuid', 'remote' ]
194 },
190 [ScopeNames.FOR_API]: (options: ForAPIOptions) => { 195 [ScopeNames.FOR_API]: (options: ForAPIOptions) => {
191 const query: FindOptions = { 196 const query: FindOptions = {
192 include: [ 197 include: [
@@ -1074,6 +1079,11 @@ export class VideoModel extends Model<VideoModel> {
1074 return undefined 1079 return undefined
1075 } 1080 }
1076 1081
1082 @BeforeDestroy
1083 static invalidateCache (instance: VideoModel) {
1084 ModelCache.Instance.invalidateCache('video', instance.id)
1085 }
1086
1077 static listLocal (): Bluebird<MVideoWithAllFiles[]> { 1087 static listLocal (): Bluebird<MVideoWithAllFiles[]> {
1078 const query = { 1088 const query = {
1079 where: { 1089 where: {
@@ -1468,6 +1478,24 @@ export class VideoModel extends Model<VideoModel> {
1468 ]).findOne(options) 1478 ]).findOne(options)
1469 } 1479 }
1470 1480
1481 static loadImmutableAttributes (id: number | string, t?: Transaction): Bluebird<MVideoImmutable> {
1482 const fun = () => {
1483 const query = {
1484 where: buildWhereIdOrUUID(id),
1485 transaction: t
1486 }
1487
1488 return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query)
1489 }
1490
1491 return ModelCache.Instance.doCache({
1492 cacheType: 'load-video-immutable-id',
1493 key: '' + id,
1494 deleteKey: 'video',
1495 fun
1496 })
1497 }
1498
1471 static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { 1499 static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> {
1472 const where = buildWhereIdOrUUID(id) 1500 const where = buildWhereIdOrUUID(id)
1473 const options = { 1501 const options = {
@@ -1531,6 +1559,26 @@ export class VideoModel extends Model<VideoModel> {
1531 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) 1559 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query)
1532 } 1560 }
1533 1561
1562 static loadByUrlImmutableAttributes (url: string, transaction?: Transaction): Bluebird<MVideoImmutable> {
1563 const fun = () => {
1564 const query: FindOptions = {
1565 where: {
1566 url
1567 },
1568 transaction
1569 }
1570
1571 return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query)
1572 }
1573
1574 return ModelCache.Instance.doCache({
1575 cacheType: 'load-video-immutable-url',
1576 key: url,
1577 deleteKey: 'video',
1578 fun
1579 })
1580 }
1581
1534 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { 1582 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> {
1535 const query: FindOptions = { 1583 const query: FindOptions = {
1536 where: { 1584 where: {