diff options
author | Chocobozzz <me@florianbigard.com> | 2018-09-14 11:52:23 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-09-14 11:52:23 +0200 |
commit | 7348b1fd84dee869b3c36554aea6797f09d4ceed (patch) | |
tree | 46f6800a92f659dd989d0f38c1b682a61fd2315a /server | |
parent | 2b62cccd75e9025fb66148bcb1feea2a458ee8e4 (diff) | |
download | PeerTube-7348b1fd84dee869b3c36554aea6797f09d4ceed.tar.gz PeerTube-7348b1fd84dee869b3c36554aea6797f09d4ceed.tar.zst PeerTube-7348b1fd84dee869b3c36554aea6797f09d4ceed.zip |
Speed up overviews route
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/overviews.ts | 26 | ||||
-rw-r--r-- | server/helpers/utils.ts | 24 | ||||
-rw-r--r-- | server/initializers/constants.ts | 5 | ||||
-rw-r--r-- | server/lib/schedulers/videos-redundancy-scheduler.ts | 2 | ||||
-rw-r--r-- | server/models/video/video.ts | 27 |
5 files changed, 44 insertions, 40 deletions
diff --git a/server/controllers/api/overviews.ts b/server/controllers/api/overviews.ts index da941c0ac..cc3cc54a7 100644 --- a/server/controllers/api/overviews.ts +++ b/server/controllers/api/overviews.ts | |||
@@ -4,8 +4,9 @@ import { VideoModel } from '../../models/video/video' | |||
4 | import { asyncMiddleware } from '../../middlewares' | 4 | import { asyncMiddleware } from '../../middlewares' |
5 | import { TagModel } from '../../models/video/tag' | 5 | import { TagModel } from '../../models/video/tag' |
6 | import { VideosOverview } from '../../../shared/models/overviews' | 6 | import { VideosOverview } from '../../../shared/models/overviews' |
7 | import { OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers' | 7 | import { MEMOIZE_TTL, OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers' |
8 | import { cacheRoute } from '../../middlewares/cache' | 8 | import { cacheRoute } from '../../middlewares/cache' |
9 | import * as memoizee from 'memoizee' | ||
9 | 10 | ||
10 | const overviewsRouter = express.Router() | 11 | const overviewsRouter = express.Router() |
11 | 12 | ||
@@ -23,10 +24,17 @@ export { overviewsRouter } | |||
23 | // This endpoint could be quite long, but we cache it | 24 | // This endpoint could be quite long, but we cache it |
24 | async function getVideosOverview (req: express.Request, res: express.Response) { | 25 | async function getVideosOverview (req: express.Request, res: express.Response) { |
25 | const attributes = await buildSamples() | 26 | const attributes = await buildSamples() |
27 | |||
28 | const [ categories, channels, tags ] = await Promise.all([ | ||
29 | Promise.all(attributes.categories.map(c => getVideosByCategory(c, res))), | ||
30 | Promise.all(attributes.channels.map(c => getVideosByChannel(c, res))), | ||
31 | Promise.all(attributes.tags.map(t => getVideosByTag(t, res))) | ||
32 | ]) | ||
33 | |||
26 | const result: VideosOverview = { | 34 | const result: VideosOverview = { |
27 | categories: await Promise.all(attributes.categories.map(c => getVideosByCategory(c, res))), | 35 | categories, |
28 | channels: await Promise.all(attributes.channels.map(c => getVideosByChannel(c, res))), | 36 | channels, |
29 | tags: await Promise.all(attributes.tags.map(t => getVideosByTag(t, res))) | 37 | tags |
30 | } | 38 | } |
31 | 39 | ||
32 | // Cleanup our object | 40 | // Cleanup our object |
@@ -37,7 +45,7 @@ async function getVideosOverview (req: express.Request, res: express.Response) { | |||
37 | return res.json(result) | 45 | return res.json(result) |
38 | } | 46 | } |
39 | 47 | ||
40 | async function buildSamples () { | 48 | const buildSamples = memoizee(async function () { |
41 | const [ categories, channels, tags ] = await Promise.all([ | 49 | const [ categories, channels, tags ] = await Promise.all([ |
42 | VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT), | 50 | VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT), |
43 | VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD ,OVERVIEWS.VIDEOS.SAMPLES_COUNT), | 51 | VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD ,OVERVIEWS.VIDEOS.SAMPLES_COUNT), |
@@ -45,7 +53,7 @@ async function buildSamples () { | |||
45 | ]) | 53 | ]) |
46 | 54 | ||
47 | return { categories, channels, tags } | 55 | return { categories, channels, tags } |
48 | } | 56 | }, { maxAge: MEMOIZE_TTL.OVERVIEWS_SAMPLE }) |
49 | 57 | ||
50 | async function getVideosByTag (tag: string, res: express.Response) { | 58 | async function getVideosByTag (tag: string, res: express.Response) { |
51 | const videos = await getVideos(res, { tagsOneOf: [ tag ] }) | 59 | const videos = await getVideos(res, { tagsOneOf: [ tag ] }) |
@@ -84,14 +92,16 @@ async function getVideos ( | |||
84 | res: express.Response, | 92 | res: express.Response, |
85 | where: { videoChannelId?: number, tagsOneOf?: string[], categoryOneOf?: number[] } | 93 | where: { videoChannelId?: number, tagsOneOf?: string[], categoryOneOf?: number[] } |
86 | ) { | 94 | ) { |
87 | const { data } = await VideoModel.listForApi(Object.assign({ | 95 | const query = Object.assign({ |
88 | start: 0, | 96 | start: 0, |
89 | count: 10, | 97 | count: 10, |
90 | sort: '-createdAt', | 98 | sort: '-createdAt', |
91 | includeLocalVideos: true, | 99 | includeLocalVideos: true, |
92 | nsfw: buildNSFWFilter(res), | 100 | nsfw: buildNSFWFilter(res), |
93 | withFiles: false | 101 | withFiles: false |
94 | }, where)) | 102 | }, where) |
103 | |||
104 | const { data } = await VideoModel.listForApi(query, false) | ||
95 | 105 | ||
96 | return data.map(d => d.toFormattedJSON()) | 106 | return data.map(d => d.toFormattedJSON()) |
97 | } | 107 | } |
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index a1ed8e72d..a42474417 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import { ResultList } from '../../shared' | 1 | import { ResultList } from '../../shared' |
2 | import { CONFIG } from '../initializers' | 2 | import { CONFIG } from '../initializers' |
3 | import { ActorModel } from '../models/activitypub/actor' | ||
4 | import { ApplicationModel } from '../models/application/application' | 3 | import { ApplicationModel } from '../models/application/application' |
5 | import { pseudoRandomBytesPromise, sha256 } from './core-utils' | 4 | import { pseudoRandomBytesPromise, sha256 } from './core-utils' |
6 | import { logger } from './logger' | 5 | import { logger } from './logger' |
7 | import { join } from 'path' | 6 | import { join } from 'path' |
8 | import { Instance as ParseTorrent } from 'parse-torrent' | 7 | import { Instance as ParseTorrent } from 'parse-torrent' |
9 | import { remove } from 'fs-extra' | 8 | import { remove } from 'fs-extra' |
9 | import * as memoizee from 'memoizee' | ||
10 | 10 | ||
11 | function deleteFileAsync (path: string) { | 11 | function deleteFileAsync (path: string) { |
12 | remove(path) | 12 | remove(path) |
@@ -36,24 +36,12 @@ function getFormattedObjects<U, T extends FormattableToJSON> (objects: T[], obje | |||
36 | } as ResultList<U> | 36 | } as ResultList<U> |
37 | } | 37 | } |
38 | 38 | ||
39 | async function getServerActor () { | 39 | const getServerActor = memoizee(async function () { |
40 | if (getServerActor.serverActor === undefined) { | 40 | const application = await ApplicationModel.load() |
41 | const application = await ApplicationModel.load() | 41 | if (!application) throw Error('Could not load Application from database.') |
42 | if (!application) throw Error('Could not load Application from database.') | ||
43 | 42 | ||
44 | getServerActor.serverActor = application.Account.Actor | 43 | return application.Account.Actor |
45 | } | 44 | }) |
46 | |||
47 | if (!getServerActor.serverActor) { | ||
48 | logger.error('Cannot load server actor.') | ||
49 | process.exit(0) | ||
50 | } | ||
51 | |||
52 | return Promise.resolve(getServerActor.serverActor) | ||
53 | } | ||
54 | namespace getServerActor { | ||
55 | export let serverActor: ActorModel | ||
56 | } | ||
57 | 45 | ||
58 | function generateVideoTmpPath (target: string | ParseTorrent) { | 46 | function generateVideoTmpPath (target: string | ParseTorrent) { |
59 | const id = typeof target === 'string' ? target : target.infoHash | 47 | const id = typeof target === 'string' ? target : target.infoHash |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 5f7bcbd48..9cccb0919 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -592,6 +592,10 @@ const CACHE = { | |||
592 | } | 592 | } |
593 | } | 593 | } |
594 | 594 | ||
595 | const MEMOIZE_TTL = { | ||
596 | OVERVIEWS_SAMPLE: 1000 * 3600 * 4 // 4 hours | ||
597 | } | ||
598 | |||
595 | const REDUNDANCY = { | 599 | const REDUNDANCY = { |
596 | VIDEOS: { | 600 | VIDEOS: { |
597 | EXPIRES_AFTER_MS: 48 * 3600 * 1000, // 2 days | 601 | EXPIRES_AFTER_MS: 48 * 3600 * 1000, // 2 days |
@@ -708,6 +712,7 @@ export { | |||
708 | VIDEO_ABUSE_STATES, | 712 | VIDEO_ABUSE_STATES, |
709 | JOB_REQUEST_TIMEOUT, | 713 | JOB_REQUEST_TIMEOUT, |
710 | USER_PASSWORD_RESET_LIFETIME, | 714 | USER_PASSWORD_RESET_LIFETIME, |
715 | MEMOIZE_TTL, | ||
711 | USER_EMAIL_VERIFY_LIFETIME, | 716 | USER_EMAIL_VERIFY_LIFETIME, |
712 | IMAGE_MIMETYPE_EXT, | 717 | IMAGE_MIMETYPE_EXT, |
713 | OVERVIEWS, | 718 | OVERVIEWS, |
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index 8b91d750b..7079600a9 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts | |||
@@ -81,7 +81,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
81 | } | 81 | } |
82 | 82 | ||
83 | if (cache.strategy === 'recently-added') { | 83 | if (cache.strategy === 'recently-added') { |
84 | const minViews = (cache as RecentlyAddedStrategy).minViews | 84 | const minViews = cache.minViews |
85 | return VideoRedundancyModel.findRecentlyAddedToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR, minViews) | 85 | return VideoRedundancyModel.findRecentlyAddedToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR, minViews) |
86 | } | 86 | } |
87 | } | 87 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 23d1dedd6..b7d3f184f 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -929,7 +929,7 @@ export class VideoModel extends Model<VideoModel> { | |||
929 | videoChannelId?: number, | 929 | videoChannelId?: number, |
930 | actorId?: number | 930 | actorId?: number |
931 | trendingDays?: number | 931 | trendingDays?: number |
932 | }) { | 932 | }, countVideos = true) { |
933 | const query: IFindOptions<VideoModel> = { | 933 | const query: IFindOptions<VideoModel> = { |
934 | offset: options.start, | 934 | offset: options.start, |
935 | limit: options.count, | 935 | limit: options.count, |
@@ -962,7 +962,7 @@ export class VideoModel extends Model<VideoModel> { | |||
962 | trendingDays | 962 | trendingDays |
963 | } | 963 | } |
964 | 964 | ||
965 | return VideoModel.getAvailableForApi(query, queryOptions) | 965 | return VideoModel.getAvailableForApi(query, queryOptions, countVideos) |
966 | } | 966 | } |
967 | 967 | ||
968 | static async searchAndPopulateAccountAndServer (options: { | 968 | static async searchAndPopulateAccountAndServer (options: { |
@@ -1164,7 +1164,14 @@ export class VideoModel extends Model<VideoModel> { | |||
1164 | } | 1164 | } |
1165 | 1165 | ||
1166 | // threshold corresponds to how many video the field should have to be returned | 1166 | // threshold corresponds to how many video the field should have to be returned |
1167 | static getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) { | 1167 | static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) { |
1168 | const actorId = (await getServerActor()).id | ||
1169 | |||
1170 | const scopeOptions = { | ||
1171 | actorId, | ||
1172 | includeLocalVideos: true | ||
1173 | } | ||
1174 | |||
1168 | const query: IFindOptions<VideoModel> = { | 1175 | const query: IFindOptions<VideoModel> = { |
1169 | attributes: [ field ], | 1176 | attributes: [ field ], |
1170 | limit: count, | 1177 | limit: count, |
@@ -1172,17 +1179,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1172 | having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), { | 1179 | having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), { |
1173 | [ Sequelize.Op.gte ]: threshold | 1180 | [ Sequelize.Op.gte ]: threshold |
1174 | }) as any, // FIXME: typings | 1181 | }) as any, // FIXME: typings |
1175 | where: { | ||
1176 | [ field ]: { | ||
1177 | [ Sequelize.Op.not ]: null | ||
1178 | }, | ||
1179 | privacy: VideoPrivacy.PUBLIC, | ||
1180 | state: VideoState.PUBLISHED | ||
1181 | }, | ||
1182 | order: [ this.sequelize.random() ] | 1182 | order: [ this.sequelize.random() ] |
1183 | } | 1183 | } |
1184 | 1184 | ||
1185 | return VideoModel.findAll(query) | 1185 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) |
1186 | .findAll(query) | ||
1186 | .then(rows => rows.map(r => r[ field ])) | 1187 | .then(rows => rows.map(r => r[ field ])) |
1187 | } | 1188 | } |
1188 | 1189 | ||
@@ -1210,7 +1211,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1210 | return {} | 1211 | return {} |
1211 | } | 1212 | } |
1212 | 1213 | ||
1213 | private static async getAvailableForApi (query: IFindOptions<VideoModel>, options: AvailableForListIDsOptions) { | 1214 | private static async getAvailableForApi (query: IFindOptions<VideoModel>, options: AvailableForListIDsOptions, countVideos = true) { |
1214 | const idsScope = { | 1215 | const idsScope = { |
1215 | method: [ | 1216 | method: [ |
1216 | ScopeNames.AVAILABLE_FOR_LIST_IDS, options | 1217 | ScopeNames.AVAILABLE_FOR_LIST_IDS, options |
@@ -1227,7 +1228,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1227 | } | 1228 | } |
1228 | 1229 | ||
1229 | const [ count, rowsId ] = await Promise.all([ | 1230 | const [ count, rowsId ] = await Promise.all([ |
1230 | VideoModel.scope(countScope).count(countQuery), | 1231 | countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve(undefined), |
1231 | VideoModel.scope(idsScope).findAll(query) | 1232 | VideoModel.scope(idsScope).findAll(query) |
1232 | ]) | 1233 | ]) |
1233 | const ids = rowsId.map(r => r.id) | 1234 | const ids = rowsId.map(r => r.id) |