diff options
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/account/account.ts | 67 | ||||
-rw-r--r-- | server/models/activitypub/actor.ts | 108 | ||||
-rw-r--r-- | server/models/model-cache.ts | 91 | ||||
-rw-r--r-- | server/models/video/video.ts | 50 |
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 | |||
32 | import { AccountBlocklistModel } from './account-blocklist' | 32 | import { AccountBlocklistModel } from './account-blocklist' |
33 | import { ServerBlocklistModel } from '../server/server-blocklist' | 33 | import { ServerBlocklistModel } from '../server/server-blocklist' |
34 | import { ActorFollowModel } from '../activitypub/actor-follow' | 34 | import { ActorFollowModel } from '../activitypub/actor-follow' |
35 | import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models' | 35 | import { MAccountActor, MAccountAP, MAccountDefault, MAccountFormattable, MAccountSummaryFormattable } from '../../typings/models' |
36 | import * as Bluebird from 'bluebird' | 36 | import * as Bluebird from 'bluebird' |
37 | import { ModelCache } from '@server/models/model-cache' | ||
37 | 38 | ||
38 | export enum ScopeNames { | 39 | export 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' |
49 | import * as Bluebird from 'bluebird' | 49 | import * as Bluebird from 'bluebird' |
50 | import { Op, Transaction, literal } from 'sequelize' | 50 | import { Op, Transaction, literal } from 'sequelize' |
51 | import { ModelCache } from '@server/models/model-cache' | ||
51 | 52 | ||
52 | enum ScopeNames { | 53 | enum 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 @@ | |||
1 | import { Model } from 'sequelize-typescript' | ||
2 | import * as Bluebird from 'bluebird' | ||
3 | import { logger } from '@server/helpers/logger' | ||
4 | |||
5 | type ModelCacheType = | ||
6 | 'local-account-name' | ||
7 | | 'local-actor-name' | ||
8 | | 'local-actor-url' | ||
9 | | 'load-video-immutable-id' | ||
10 | | 'load-video-immutable-url' | ||
11 | |||
12 | type DeleteKey = | ||
13 | 'video' | ||
14 | |||
15 | class 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 | |||
89 | export { | ||
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' | |||
132 | import { VideoFile } from '@shared/models/videos/video-file.model' | 132 | import { VideoFile } from '@shared/models/videos/video-file.model' |
133 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 133 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
134 | import validator from 'validator' | 134 | import validator from 'validator' |
135 | import { ModelCache } from '@server/models/model-cache' | ||
135 | 136 | ||
136 | export enum ScopeNames { | 137 | export 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: { |