Optimize SQL requests of watch page API endpoints
authorChocobozzz <me@florianbigard.com>
Tue, 18 Sep 2018 10:00:49 +0000 (12:00 +0200)
committerChocobozzz <me@florianbigard.com>
Wed, 19 Sep 2018 07:54:37 +0000 (09:54 +0200)
15 files changed:
scripts/create-import-video-file-job.ts
scripts/create-transcoding-job.ts
scripts/prune-storage.ts
server/controllers/api/users/me.ts
server/controllers/api/videos/comment.ts
server/helpers/custom-validators/videos.ts
server/lib/cache/videos-caption-cache.ts
server/lib/cache/videos-preview-cache.ts
server/lib/client-html.ts
server/lib/job-queue/handlers/video-file.ts
server/lib/job-queue/handlers/video-import.ts
server/middlewares/validators/users.ts
server/middlewares/validators/video-captions.ts
server/middlewares/validators/video-comments.ts
server/models/video/video.ts

index 2b636014a6636b228cfb7b54fe9bceb610d17f8d..c8c6c642977c8719aa0aa38c7accf7aecbe0efcb 100644 (file)
@@ -25,7 +25,7 @@ run()
 async function run () {
   await initDatabaseModels(true)
 
-  const video = await VideoModel.loadByUUID(program['video'])
+  const video = await VideoModel.loadByUUIDWithFile(program['video'])
   if (!video) throw new Error('Video not found.')
   if (video.isOwned() === false) throw new Error('Cannot import files of a non owned video.')
 
index 3ea30f98e642701d5bf85e60387308c1c0fa88c1..7e5b687bbc2c22520fa5969d79ea29d2c71e5b32 100755 (executable)
@@ -28,7 +28,7 @@ run()
 async function run () {
   await initDatabaseModels(true)
 
-  const video = await VideoModel.loadByUUID(program['video'])
+  const video = await VideoModel.loadByUUIDWithFile(program['video'])
   if (!video) throw new Error('Video not found.')
 
   const dataInput = {
index 5722838685d2277da1a112a14051482786e982da..b00f2093426ddcfac3f909afe9a8f5bdffc9606d 100755 (executable)
@@ -56,7 +56,7 @@ async function pruneDirectory (directory: string) {
     const uuid = getUUIDFromFilename(file)
     let video: VideoModel
 
-    if (uuid) video = await VideoModel.loadByUUID(uuid)
+    if (uuid) video = await VideoModel.loadByUUIDWithFile(uuid)
 
     if (!uuid || !video) toDelete.push(join(directory, file))
   }
index e886d4b2ad8e34935b6e1881476145ae3f1a19af..113563c3971a3f036e0b09f58a4f986c853322f0 100644 (file)
@@ -293,7 +293,7 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons
 }
 
 async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const videoId = +req.params.videoId
+  const videoId = res.locals.video.id
   const accountId = +res.locals.oauth.token.User.Account.id
 
   const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)
index e35247829b249c7ae62899908b0373a31a8c6967..8d0692b2b051d54e8eb2741a536cd2f77c4871f7 100644 (file)
@@ -86,7 +86,7 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo
   let resultList: ResultList<VideoCommentModel>
 
   if (video.commentsEnabled === true) {
-    resultList = await VideoCommentModel.listThreadCommentsForApi(res.locals.video.id, res.locals.videoCommentThread.id)
+    resultList = await VideoCommentModel.listThreadCommentsForApi(video.id, res.locals.videoCommentThread.id)
   } else {
     resultList = {
       total: 0,
index edafba6e2d17378506d69d1702be8cf39e30574a..dd207c7876d470cf2bada623eb9fb5d537919637 100644 (file)
@@ -152,13 +152,15 @@ function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: Use
   return true
 }
 
-async function isVideoExist (id: string, res: Response) {
+async function isVideoExist (id: string, res: Response, fetchType: 'all' | 'only-video' | 'id' | 'none' = 'all') {
   let video: VideoModel | null
 
-  if (validator.isInt(id)) {
-    video = await VideoModel.loadAndPopulateAccountAndServerAndTags(+id)
-  } else { // UUID
-    video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(id)
+  if (fetchType === 'all') {
+    video = await VideoModel.loadAndPopulateAccountAndServerAndTags(id)
+  } else if (fetchType === 'only-video') {
+    video = await VideoModel.load(id)
+  } else if (fetchType === 'id' || fetchType === 'none') {
+    video = await VideoModel.loadOnlyId(id)
   }
 
   if (video === null) {
@@ -169,7 +171,7 @@ async function isVideoExist (id: string, res: Response) {
     return false
   }
 
-  res.locals.video = video
+  if (fetchType !== 'none') res.locals.video = video
   return true
 }
 
index 380d42b2cd8342f57ac90ce467bace4379e5b5bf..f240affbca34cff893e30be1cd90e6495ebdf1cb 100644 (file)
@@ -38,7 +38,7 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
     if (videoCaption.isOwned()) throw new Error('Cannot load remote caption of owned video.')
 
     // Used to fetch the path
-    const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(videoId)
+    const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
     if (!video) return undefined
 
     const remoteStaticPath = videoCaption.getCaptionStaticPath()
index 22b6d9cb0db82ecafd74747062ca0db8879fbe36..a5d6f5b627be39c8f619cfccc8fb96401787c85c 100644 (file)
@@ -16,7 +16,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
   }
 
   async getFilePath (videoUUID: string) {
-    const video = await VideoModel.loadByUUID(videoUUID)
+    const video = await VideoModel.loadByUUIDWithFile(videoUUID)
     if (!video) return undefined
 
     if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName())
@@ -25,7 +25,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
   }
 
   protected async loadRemoteFile (key: string) {
-    const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(key)
+    const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(key)
     if (!video) return undefined
 
     if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.')
index b1088c0968ca990578fd832db9300a9cbadfbc6a..fc013e0c3bc2601af26d7cf7e171886c62c9ba44 100644 (file)
@@ -39,10 +39,8 @@ export class ClientHtml {
     let videoPromise: Bluebird<VideoModel>
 
     // Let Angular application handle errors
-    if (validator.isUUID(videoId, 4)) {
-      videoPromise = VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(videoId)
-    } else if (validator.isInt(videoId)) {
-      videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(+videoId)
+    if (validator.isInt(videoId) || validator.isUUID(videoId, 4)) {
+      videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
     } else {
       return ClientHtml.getIndexHTML(req, res)
     }
index 2c9ca8e12383b7aed3a9becffe4091c642b97923..1463c93fc843e660197c9469bcd65db31575d650 100644 (file)
@@ -26,7 +26,7 @@ async function processVideoFileImport (job: Bull.Job) {
   const payload = job.data as VideoFileImportPayload
   logger.info('Processing video file import in job %d.', job.id)
 
-  const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(payload.videoUUID)
+  const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoUUID)
   // No video, maybe deleted?
   if (!video) {
     logger.info('Do not process job %d, video does not exist.', job.id)
@@ -43,7 +43,7 @@ async function processVideoFile (job: Bull.Job) {
   const payload = job.data as VideoFilePayload
   logger.info('Processing video file in job %d.', job.id)
 
-  const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(payload.videoUUID)
+  const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoUUID)
   // No video, maybe deleted?
   if (!video) {
     logger.info('Do not process job %d, video does not exist.', job.id)
@@ -69,7 +69,7 @@ async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) {
 
   return sequelizeTypescript.transaction(async t => {
     // Maybe the video changed in database, refresh it
-    let videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid, t)
+    let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
     // Video does not exist anymore
     if (!videoDatabase) return undefined
 
@@ -99,7 +99,7 @@ async function onVideoFileOptimizerSuccess (video: VideoModel, isNewVideo: boole
 
   return sequelizeTypescript.transaction(async t => {
     // Maybe the video changed in database, refresh it
-    const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid, t)
+    const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
     // Video does not exist anymore
     if (!videoDatabase) return undefined
 
index ebcb2090cd42a725ab5e10c4da489533186aa778..9e14e57e6f7b5bd4032537b1f8438130f32da195 100644 (file)
@@ -183,7 +183,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
       const videoUpdated = await video.save({ transaction: t })
 
       // Now we can federate the video (reload from database, we need more attributes)
-      const videoForFederation = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid, t)
+      const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
       await federateVideoIfNeeded(videoForFederation, true, t)
 
       // Update video import object
index d13c50c84af1f3f5e9248cfc3173000eeccc8d17..d3ba1ae232afa41d2ef6ddea9873ff9c3f0ba723 100644 (file)
@@ -172,7 +172,7 @@ const usersVideoRatingValidator = [
     logger.debug('Checking usersVideoRating parameters', { parameters: req.params })
 
     if (areValidationErrors(req, res)) return
-    if (!await isVideoExist(req.params.videoId, res)) return
+    if (!await isVideoExist(req.params.videoId, res, 'id')) return
 
     return next()
   }
index 4f393ea84e06f83aa018ad9f257a63327c3806b6..51ffd7f3ce698fffab17295c61b1cb5abca2d71c 100644 (file)
@@ -58,7 +58,7 @@ const listVideoCaptionsValidator = [
     logger.debug('Checking listVideoCaptions parameters', { parameters: req.params })
 
     if (areValidationErrors(req, res)) return
-    if (!await isVideoExist(req.params.videoId, res)) return
+    if (!await isVideoExist(req.params.videoId, res, 'id')) return
 
     return next()
   }
index 227bc1fca8f5bd798aeb2818e43f0e671680995a..4b15eed23006255fa657186f5b6b06f66ddb37c6 100644 (file)
@@ -17,7 +17,7 @@ const listVideoCommentThreadsValidator = [
     logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params })
 
     if (areValidationErrors(req, res)) return
-    if (!await isVideoExist(req.params.videoId, res)) return
+    if (!await isVideoExist(req.params.videoId, res, 'only-video')) return
 
     return next()
   }
@@ -31,7 +31,7 @@ const listVideoThreadCommentsValidator = [
     logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params })
 
     if (areValidationErrors(req, res)) return
-    if (!await isVideoExist(req.params.videoId, res)) return
+    if (!await isVideoExist(req.params.videoId, res, 'only-video')) return
     if (!await isVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return
 
     return next()
index ce856aed2c9ce7926e2ce3a0f5b562ba3494a98a..c7cd2890ca593f123408be4e0795a093c80acef1 100644 (file)
@@ -91,6 +91,7 @@ import {
   videoModelToFormattedDetailsJSON,
   videoModelToFormattedJSON
 } from './video-format-utils'
+import * as validator from 'validator'
 
 // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
 const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -466,6 +467,7 @@ type AvailableForListIDsOptions = {
         required: false,
         include: [
           {
+            attributes: [ 'fileUrl' ],
             model: () => VideoRedundancyModel.unscoped(),
             required: false
           }
@@ -1062,44 +1064,34 @@ export class VideoModel extends Model<VideoModel> {
     return VideoModel.getAvailableForApi(query, queryOptions)
   }
 
-  static load (id: number, t?: Sequelize.Transaction) {
-    return VideoModel.findById(id, { transaction: t })
-  }
-
-  static loadWithFile (id: number, t?: Sequelize.Transaction, logging?: boolean) {
-    return VideoModel.scope(ScopeNames.WITH_FILES)
-                     .findById(id, { transaction: t, logging })
-  }
-
-  static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) {
-    const query: IFindOptions<VideoModel> = {
-      where: {
-        url
-      }
+  static load (id: number | string, t?: Sequelize.Transaction) {
+    const where = VideoModel.buildWhereIdOrUUID(id)
+    const options = {
+      where,
+      transaction: t
     }
 
-    if (t !== undefined) query.transaction = t
-
-    return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query)
+    return VideoModel.findOne(options)
   }
 
-  static loadAndPopulateAccountAndServerAndTags (id: number) {
+  static loadOnlyId (id: number | string, t?: Sequelize.Transaction) {
+    const where = VideoModel.buildWhereIdOrUUID(id)
+
     const options = {
-      order: [ [ 'Tags', 'name', 'ASC' ] ]
+      attributes: [ 'id' ],
+      where,
+      transaction: t
     }
 
-    return VideoModel
-      .scope([
-        ScopeNames.WITH_TAGS,
-        ScopeNames.WITH_BLACKLISTED,
-        ScopeNames.WITH_FILES,
-        ScopeNames.WITH_ACCOUNT_DETAILS,
-        ScopeNames.WITH_SCHEDULED_UPDATE
-      ])
-      .findById(id, options)
+    return VideoModel.findOne(options)
+  }
+
+  static loadWithFile (id: number, t?: Sequelize.Transaction, logging?: boolean) {
+    return VideoModel.scope(ScopeNames.WITH_FILES)
+                     .findById(id, { transaction: t, logging })
   }
 
-  static loadByUUID (uuid: string) {
+  static loadByUUIDWithFile (uuid: string) {
     const options = {
       where: {
         uuid
@@ -1111,12 +1103,24 @@ export class VideoModel extends Model<VideoModel> {
       .findOne(options)
   }
 
-  static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string, t?: Sequelize.Transaction) {
+  static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) {
+    const query: IFindOptions<VideoModel> = {
+      where: {
+        url
+      }
+    }
+
+    if (t !== undefined) query.transaction = t
+
+    return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query)
+  }
+
+  static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction) {
+    const where = VideoModel.buildWhereIdOrUUID(id)
+
     const options = {
       order: [ [ 'Tags', 'name', 'ASC' ] ],
-      where: {
-        uuid
-      },
+      where,
       transaction: t
     }
 
@@ -1277,6 +1281,10 @@ export class VideoModel extends Model<VideoModel> {
     return VIDEO_STATES[ id ] || 'Unknown'
   }
 
+  static buildWhereIdOrUUID (id: number | string) {
+    return validator.isInt('' + id) ? { id } : { uuid: id }
+  }
+
   getOriginalFile () {
     if (Array.isArray(this.VideoFiles) === false) return undefined