diff options
author | Chocobozzz <me@florianbigard.com> | 2022-06-17 14:34:37 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-06-17 14:56:16 +0200 |
commit | aa2ce188d102ab38452df316d06286040b5d9075 (patch) | |
tree | 100e639f21e9798d811639aa2e20abfa4c8d2bbf | |
parent | fba911e2c89708a166636e3a93fcd8fcbc3de7e1 (diff) | |
download | PeerTube-aa2ce188d102ab38452df316d06286040b5d9075.tar.gz PeerTube-aa2ce188d102ab38452df316d06286040b5d9075.tar.zst PeerTube-aa2ce188d102ab38452df316d06286040b5d9075.zip |
Optimize view endpoint
-rw-r--r-- | scripts/simulate-many-viewers.ts | 10 | ||||
-rw-r--r-- | server/controllers/api/videos/view.ts | 2 | ||||
-rw-r--r-- | server/initializers/constants.ts | 6 | ||||
-rw-r--r-- | server/lib/video.ts | 24 | ||||
-rw-r--r-- | server/lib/views/shared/video-viewer-counters.ts | 8 | ||||
-rw-r--r-- | server/lib/views/shared/video-viewer-stats.ts | 6 | ||||
-rw-r--r-- | server/lib/views/shared/video-views.ts | 17 | ||||
-rw-r--r-- | server/lib/views/video-views-manager.ts | 4 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-view.ts | 13 |
9 files changed, 58 insertions, 32 deletions
diff --git a/scripts/simulate-many-viewers.ts b/scripts/simulate-many-viewers.ts index fb666c318..a993e175a 100644 --- a/scripts/simulate-many-viewers.ts +++ b/scripts/simulate-many-viewers.ts | |||
@@ -46,10 +46,12 @@ async function prepare () { | |||
46 | } | 46 | } |
47 | } | 47 | } |
48 | 48 | ||
49 | const env = { PRODUCTION_CONSTANTS: 'true' } | ||
50 | |||
49 | servers = await Promise.all([ | 51 | servers = await Promise.all([ |
50 | createSingleServer(1, config, { nodeArgs: [ '--inspect' ] }), | 52 | createSingleServer(1, config, { env, nodeArgs: [ '--inspect' ] }), |
51 | createSingleServer(2, config), | 53 | createSingleServer(2, config, { env }), |
52 | createSingleServer(3, config) | 54 | createSingleServer(3, config, { env }) |
53 | ]) | 55 | ]) |
54 | 56 | ||
55 | await setAccessTokensToServers(servers) | 57 | await setAccessTokensToServers(servers) |
@@ -81,7 +83,7 @@ async function runViewers () { | |||
81 | 83 | ||
82 | await Bluebird.map(viewers, viewer => { | 84 | await Bluebird.map(viewers, viewer => { |
83 | return servers[0].views.simulateView({ id: videoId, xForwardedFor: viewer.xForwardedFor }) | 85 | return servers[0].views.simulateView({ id: videoId, xForwardedFor: viewer.xForwardedFor }) |
84 | }, { concurrency: 100 }) | 86 | }, { concurrency: 500 }) |
85 | 87 | ||
86 | console.log('Finished to run views in %d seconds.', (new Date().getTime() - before) / 1000) | 88 | console.log('Finished to run views in %d seconds.', (new Date().getTime() - before) / 1000) |
87 | 89 | ||
diff --git a/server/controllers/api/videos/view.ts b/server/controllers/api/videos/view.ts index db1091f2d..dee1ec67c 100644 --- a/server/controllers/api/videos/view.ts +++ b/server/controllers/api/videos/view.ts | |||
@@ -26,7 +26,7 @@ export { | |||
26 | // --------------------------------------------------------------------------- | 26 | // --------------------------------------------------------------------------- |
27 | 27 | ||
28 | async function viewVideo (req: express.Request, res: express.Response) { | 28 | async function viewVideo (req: express.Request, res: express.Response) { |
29 | const video = res.locals.onlyVideo | 29 | const video = res.locals.onlyImmutableVideo |
30 | 30 | ||
31 | const body = req.body as VideoView | 31 | const body = req.body as VideoView |
32 | 32 | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 75ccbc458..52007d212 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -734,12 +734,14 @@ const VIDEO_LIVE = { | |||
734 | const MEMOIZE_TTL = { | 734 | const MEMOIZE_TTL = { |
735 | OVERVIEWS_SAMPLE: 1000 * 3600 * 4, // 4 hours | 735 | OVERVIEWS_SAMPLE: 1000 * 3600 * 4, // 4 hours |
736 | INFO_HASH_EXISTS: 1000 * 3600 * 12, // 12 hours | 736 | INFO_HASH_EXISTS: 1000 * 3600 * 12, // 12 hours |
737 | VIDEO_DURATION: 1000 * 10, // 10 seconds | ||
737 | LIVE_ABLE_TO_UPLOAD: 1000 * 60, // 1 minute | 738 | LIVE_ABLE_TO_UPLOAD: 1000 * 60, // 1 minute |
738 | LIVE_CHECK_SOCKET_HEALTH: 1000 * 60 // 1 minute | 739 | LIVE_CHECK_SOCKET_HEALTH: 1000 * 60 // 1 minute |
739 | } | 740 | } |
740 | 741 | ||
741 | const MEMOIZE_LENGTH = { | 742 | const MEMOIZE_LENGTH = { |
742 | INFO_HASH_EXISTS: 200 | 743 | INFO_HASH_EXISTS: 200, |
744 | VIDEO_DURATION: 200 | ||
743 | } | 745 | } |
744 | 746 | ||
745 | const QUEUE_CONCURRENCY = { | 747 | const QUEUE_CONCURRENCY = { |
@@ -812,7 +814,7 @@ const STATS_TIMESERIE = { | |||
812 | // --------------------------------------------------------------------------- | 814 | // --------------------------------------------------------------------------- |
813 | 815 | ||
814 | // Special constants for a test instance | 816 | // Special constants for a test instance |
815 | if (isTestInstance() === true) { | 817 | if (isTestInstance() === true && process.env.PRODUCTION_CONSTANTS !== 'true') { |
816 | PRIVATE_RSA_KEY_SIZE = 1024 | 818 | PRIVATE_RSA_KEY_SIZE = 1024 |
817 | 819 | ||
818 | ACTOR_FOLLOW_SCORE.BASE = 20 | 820 | ACTOR_FOLLOW_SCORE.BASE = 20 |
diff --git a/server/lib/video.ts b/server/lib/video.ts index a98e45c60..86718abbe 100644 --- a/server/lib/video.ts +++ b/server/lib/video.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { UploadFiles } from 'express' | 1 | import { UploadFiles } from 'express' |
2 | import { Transaction } from 'sequelize/types' | 2 | import { Transaction } from 'sequelize/types' |
3 | import { DEFAULT_AUDIO_RESOLUTION, JOB_PRIORITY } from '@server/initializers/constants' | 3 | import { DEFAULT_AUDIO_RESOLUTION, JOB_PRIORITY, MEMOIZE_LENGTH, MEMOIZE_TTL } from '@server/initializers/constants' |
4 | import { TagModel } from '@server/models/video/tag' | 4 | import { TagModel } from '@server/models/video/tag' |
5 | import { VideoModel } from '@server/models/video/video' | 5 | import { VideoModel } from '@server/models/video/video' |
6 | import { VideoJobInfoModel } from '@server/models/video/video-job-info' | 6 | import { VideoJobInfoModel } from '@server/models/video/video-job-info' |
@@ -10,6 +10,7 @@ import { ThumbnailType, VideoCreate, VideoPrivacy, VideoState, VideoTranscodingP | |||
10 | import { CreateJobOptions, JobQueue } from './job-queue/job-queue' | 10 | import { CreateJobOptions, JobQueue } from './job-queue/job-queue' |
11 | import { updateVideoMiniatureFromExisting } from './thumbnail' | 11 | import { updateVideoMiniatureFromExisting } from './thumbnail' |
12 | import { CONFIG } from '@server/initializers/config' | 12 | import { CONFIG } from '@server/initializers/config' |
13 | import memoizee from 'memoizee' | ||
13 | 14 | ||
14 | function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> { | 15 | function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> { |
15 | return { | 16 | return { |
@@ -150,6 +151,24 @@ async function addMoveToObjectStorageJob (options: { | |||
150 | 151 | ||
151 | // --------------------------------------------------------------------------- | 152 | // --------------------------------------------------------------------------- |
152 | 153 | ||
154 | async function getVideoDuration (videoId: number | string) { | ||
155 | const video = await VideoModel.load(videoId) | ||
156 | |||
157 | const duration = video.isLive | ||
158 | ? undefined | ||
159 | : video.duration | ||
160 | |||
161 | return { duration, isLive: video.isLive } | ||
162 | } | ||
163 | |||
164 | const getCachedVideoDuration = memoizee(getVideoDuration, { | ||
165 | promise: true, | ||
166 | max: MEMOIZE_LENGTH.VIDEO_DURATION, | ||
167 | maxAge: MEMOIZE_TTL.VIDEO_DURATION | ||
168 | }) | ||
169 | |||
170 | // --------------------------------------------------------------------------- | ||
171 | |||
153 | export { | 172 | export { |
154 | buildLocalVideoFromReq, | 173 | buildLocalVideoFromReq, |
155 | buildVideoThumbnailsFromReq, | 174 | buildVideoThumbnailsFromReq, |
@@ -157,5 +176,6 @@ export { | |||
157 | addOptimizeOrMergeAudioJob, | 176 | addOptimizeOrMergeAudioJob, |
158 | addTranscodingJob, | 177 | addTranscodingJob, |
159 | addMoveToObjectStorageJob, | 178 | addMoveToObjectStorageJob, |
160 | getTranscodingJobPriority | 179 | getTranscodingJobPriority, |
180 | getCachedVideoDuration | ||
161 | } | 181 | } |
diff --git a/server/lib/views/shared/video-viewer-counters.ts b/server/lib/views/shared/video-viewer-counters.ts index 999ab7d8d..587621320 100644 --- a/server/lib/views/shared/video-viewer-counters.ts +++ b/server/lib/views/shared/video-viewer-counters.ts | |||
@@ -5,7 +5,7 @@ import { sendView } from '@server/lib/activitypub/send/send-view' | |||
5 | import { PeerTubeSocket } from '@server/lib/peertube-socket' | 5 | import { PeerTubeSocket } from '@server/lib/peertube-socket' |
6 | import { getServerActor } from '@server/models/application/application' | 6 | import { getServerActor } from '@server/models/application/application' |
7 | import { VideoModel } from '@server/models/video/video' | 7 | import { VideoModel } from '@server/models/video/video' |
8 | import { MVideo } from '@server/types/models' | 8 | import { MVideo, MVideoImmutable } from '@server/types/models' |
9 | import { buildUUID, sha256 } from '@shared/extra-utils' | 9 | import { buildUUID, sha256 } from '@shared/extra-utils' |
10 | 10 | ||
11 | const lTags = loggerTagsFactory('views') | 11 | const lTags = loggerTagsFactory('views') |
@@ -33,7 +33,7 @@ export class VideoViewerCounters { | |||
33 | // --------------------------------------------------------------------------- | 33 | // --------------------------------------------------------------------------- |
34 | 34 | ||
35 | async addLocalViewer (options: { | 35 | async addLocalViewer (options: { |
36 | video: MVideo | 36 | video: MVideoImmutable |
37 | ip: string | 37 | ip: string |
38 | }) { | 38 | }) { |
39 | const { video, ip } = options | 39 | const { video, ip } = options |
@@ -86,7 +86,7 @@ export class VideoViewerCounters { | |||
86 | // --------------------------------------------------------------------------- | 86 | // --------------------------------------------------------------------------- |
87 | 87 | ||
88 | private async addViewerToVideo (options: { | 88 | private async addViewerToVideo (options: { |
89 | video: MVideo | 89 | video: MVideoImmutable |
90 | viewerId: string | 90 | viewerId: string |
91 | viewerExpires?: Date | 91 | viewerExpires?: Date |
92 | }) { | 92 | }) { |
@@ -162,7 +162,7 @@ export class VideoViewerCounters { | |||
162 | return sha256(this.salt + '-' + ip + '-' + videoUUID) | 162 | return sha256(this.salt + '-' + ip + '-' + videoUUID) |
163 | } | 163 | } |
164 | 164 | ||
165 | private async federateViewerIfNeeded (video: MVideo, viewer: Viewer) { | 165 | private async federateViewerIfNeeded (video: MVideoImmutable, viewer: Viewer) { |
166 | // Federate the viewer if it's been a "long" time we did not | 166 | // Federate the viewer if it's been a "long" time we did not |
167 | const now = new Date().getTime() | 167 | const now = new Date().getTime() |
168 | const federationLimit = now - (VIEW_LIFETIME.VIEWER_COUNTER / 2) | 168 | const federationLimit = now - (VIEW_LIFETIME.VIEWER_COUNTER / 2) |
diff --git a/server/lib/views/shared/video-viewer-stats.ts b/server/lib/views/shared/video-viewer-stats.ts index a9ba25b47..a56c20559 100644 --- a/server/lib/views/shared/video-viewer-stats.ts +++ b/server/lib/views/shared/video-viewer-stats.ts | |||
@@ -10,7 +10,7 @@ import { Redis } from '@server/lib/redis' | |||
10 | import { VideoModel } from '@server/models/video/video' | 10 | import { VideoModel } from '@server/models/video/video' |
11 | import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer' | 11 | import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer' |
12 | import { LocalVideoViewerWatchSectionModel } from '@server/models/view/local-video-viewer-watch-section' | 12 | import { LocalVideoViewerWatchSectionModel } from '@server/models/view/local-video-viewer-watch-section' |
13 | import { MVideo } from '@server/types/models' | 13 | import { MVideo, MVideoImmutable } from '@server/types/models' |
14 | import { VideoViewEvent } from '@shared/models' | 14 | import { VideoViewEvent } from '@shared/models' |
15 | 15 | ||
16 | const lTags = loggerTagsFactory('views') | 16 | const lTags = loggerTagsFactory('views') |
@@ -41,7 +41,7 @@ export class VideoViewerStats { | |||
41 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
42 | 42 | ||
43 | async addLocalViewer (options: { | 43 | async addLocalViewer (options: { |
44 | video: MVideo | 44 | video: MVideoImmutable |
45 | currentTime: number | 45 | currentTime: number |
46 | ip: string | 46 | ip: string |
47 | viewEvent?: VideoViewEvent | 47 | viewEvent?: VideoViewEvent |
@@ -64,7 +64,7 @@ export class VideoViewerStats { | |||
64 | // --------------------------------------------------------------------------- | 64 | // --------------------------------------------------------------------------- |
65 | 65 | ||
66 | private async updateLocalViewerStats (options: { | 66 | private async updateLocalViewerStats (options: { |
67 | video: MVideo | 67 | video: MVideoImmutable |
68 | ip: string | 68 | ip: string |
69 | currentTime: number | 69 | currentTime: number |
70 | viewEvent?: VideoViewEvent | 70 | viewEvent?: VideoViewEvent |
diff --git a/server/lib/views/shared/video-views.ts b/server/lib/views/shared/video-views.ts index 275f7a014..e563287e1 100644 --- a/server/lib/views/shared/video-views.ts +++ b/server/lib/views/shared/video-views.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
2 | import { sendView } from '@server/lib/activitypub/send/send-view' | 2 | import { sendView } from '@server/lib/activitypub/send/send-view' |
3 | import { getCachedVideoDuration } from '@server/lib/video' | ||
3 | import { getServerActor } from '@server/models/application/application' | 4 | import { getServerActor } from '@server/models/application/application' |
4 | import { MVideo } from '@server/types/models' | 5 | import { MVideo, MVideoImmutable } from '@server/types/models' |
5 | import { buildUUID } from '@shared/extra-utils' | 6 | import { buildUUID } from '@shared/extra-utils' |
6 | import { Redis } from '../../redis' | 7 | import { Redis } from '../../redis' |
7 | 8 | ||
@@ -10,7 +11,7 @@ const lTags = loggerTagsFactory('views') | |||
10 | export class VideoViews { | 11 | export class VideoViews { |
11 | 12 | ||
12 | async addLocalView (options: { | 13 | async addLocalView (options: { |
13 | video: MVideo | 14 | video: MVideoImmutable |
14 | ip: string | 15 | ip: string |
15 | watchTime: number | 16 | watchTime: number |
16 | }) { | 17 | }) { |
@@ -18,7 +19,7 @@ export class VideoViews { | |||
18 | 19 | ||
19 | logger.debug('Adding local view to video %s.', video.uuid, { watchTime, ...lTags(video.uuid) }) | 20 | logger.debug('Adding local view to video %s.', video.uuid, { watchTime, ...lTags(video.uuid) }) |
20 | 21 | ||
21 | if (!this.hasEnoughWatchTime(video, watchTime)) return false | 22 | if (!await this.hasEnoughWatchTime(video, watchTime)) return false |
22 | 23 | ||
23 | const viewExists = await Redis.Instance.doesVideoIPViewExist(ip, video.uuid) | 24 | const viewExists = await Redis.Instance.doesVideoIPViewExist(ip, video.uuid) |
24 | if (viewExists) return false | 25 | if (viewExists) return false |
@@ -46,7 +47,7 @@ export class VideoViews { | |||
46 | 47 | ||
47 | // --------------------------------------------------------------------------- | 48 | // --------------------------------------------------------------------------- |
48 | 49 | ||
49 | private async addView (video: MVideo) { | 50 | private async addView (video: MVideoImmutable) { |
50 | const promises: Promise<any>[] = [] | 51 | const promises: Promise<any>[] = [] |
51 | 52 | ||
52 | if (video.isOwned()) { | 53 | if (video.isOwned()) { |
@@ -58,10 +59,12 @@ export class VideoViews { | |||
58 | await Promise.all(promises) | 59 | await Promise.all(promises) |
59 | } | 60 | } |
60 | 61 | ||
61 | private hasEnoughWatchTime (video: MVideo, watchTime: number) { | 62 | private async hasEnoughWatchTime (video: MVideoImmutable, watchTime: number) { |
62 | if (video.isLive || video.duration >= 30) return watchTime >= 30 | 63 | const { duration, isLive } = await getCachedVideoDuration(video.id) |
64 | |||
65 | if (isLive || duration >= 30) return watchTime >= 30 | ||
63 | 66 | ||
64 | // Check more than 50% of the video is watched | 67 | // Check more than 50% of the video is watched |
65 | return video.duration / watchTime < 2 | 68 | return duration / watchTime < 2 |
66 | } | 69 | } |
67 | } | 70 | } |
diff --git a/server/lib/views/video-views-manager.ts b/server/lib/views/video-views-manager.ts index ea3b35c6c..86758e8d8 100644 --- a/server/lib/views/video-views-manager.ts +++ b/server/lib/views/video-views-manager.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 1 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
2 | import { MVideo } from '@server/types/models' | 2 | import { MVideo, MVideoImmutable } from '@server/types/models' |
3 | import { VideoViewEvent } from '@shared/models' | 3 | import { VideoViewEvent } from '@shared/models' |
4 | import { VideoViewerCounters, VideoViewerStats, VideoViews } from './shared' | 4 | import { VideoViewerCounters, VideoViewerStats, VideoViews } from './shared' |
5 | 5 | ||
@@ -41,7 +41,7 @@ export class VideoViewsManager { | |||
41 | } | 41 | } |
42 | 42 | ||
43 | async processLocalView (options: { | 43 | async processLocalView (options: { |
44 | video: MVideo | 44 | video: MVideoImmutable |
45 | currentTime: number | 45 | currentTime: number |
46 | ip: string | null | 46 | ip: string | null |
47 | viewEvent?: VideoViewEvent | 47 | viewEvent?: VideoViewEvent |
diff --git a/server/middlewares/validators/videos/video-view.ts b/server/middlewares/validators/videos/video-view.ts index 7a4994e8a..2edcd140f 100644 --- a/server/middlewares/validators/videos/video-view.ts +++ b/server/middlewares/validators/videos/video-view.ts | |||
@@ -6,6 +6,7 @@ import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | |||
6 | import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' | 6 | import { exists, isIdValid, isIntOrNull, toIntOrNull } from '../../../helpers/custom-validators/misc' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' | 8 | import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' |
9 | import { getCachedVideoDuration } from '@server/lib/video' | ||
9 | 10 | ||
10 | const getVideoLocalViewerValidator = [ | 11 | const getVideoLocalViewerValidator = [ |
11 | param('localViewerId') | 12 | param('localViewerId') |
@@ -42,20 +43,18 @@ const videoViewValidator = [ | |||
42 | logger.debug('Checking videoView parameters', { parameters: req.body }) | 43 | logger.debug('Checking videoView parameters', { parameters: req.body }) |
43 | 44 | ||
44 | if (areValidationErrors(req, res)) return | 45 | if (areValidationErrors(req, res)) return |
45 | if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return | 46 | if (!await doesVideoExist(req.params.videoId, res, 'only-immutable-attributes')) return |
46 | 47 | ||
47 | const video = res.locals.onlyVideo | 48 | const video = res.locals.onlyImmutableVideo |
48 | const videoDuration = video.isLive | 49 | const { duration } = await getCachedVideoDuration(video.id) |
49 | ? undefined | ||
50 | : video.duration | ||
51 | 50 | ||
52 | if (!exists(req.body.currentTime)) { // TODO: remove in a few versions, introduced in 4.2 | 51 | if (!exists(req.body.currentTime)) { // TODO: remove in a few versions, introduced in 4.2 |
53 | req.body.currentTime = Math.min(videoDuration ?? 0, 30) | 52 | req.body.currentTime = Math.min(duration ?? 0, 30) |
54 | } | 53 | } |
55 | 54 | ||
56 | const currentTime: number = req.body.currentTime | 55 | const currentTime: number = req.body.currentTime |
57 | 56 | ||
58 | if (!isVideoTimeValid(currentTime, videoDuration)) { | 57 | if (!isVideoTimeValid(currentTime, duration)) { |
59 | return res.fail({ | 58 | return res.fail({ |
60 | status: HttpStatusCode.BAD_REQUEST_400, | 59 | status: HttpStatusCode.BAD_REQUEST_400, |
61 | message: 'Current time is invalid' | 60 | message: 'Current time is invalid' |