]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Try to speed up AP update transaction
authorChocobozzz <me@florianbigard.com>
Tue, 8 Jun 2021 15:29:45 +0000 (17:29 +0200)
committerChocobozzz <me@florianbigard.com>
Tue, 8 Jun 2021 15:41:48 +0000 (17:41 +0200)
server/helpers/database-utils.ts
server/lib/activitypub/actors/get.ts
server/lib/activitypub/actors/updater.ts
server/lib/activitypub/process/process-view.ts
server/lib/activitypub/videos/shared/abstract-builder.ts
server/lib/activitypub/videos/shared/trackers.ts
server/lib/activitypub/videos/updater.ts
server/models/video/video.ts

index 7befa2c4955b934f397c6ccaf7781fe795e4c8c5..240b18033717f7faa44eed36466d499cf5f0c290 100644 (file)
@@ -58,7 +58,7 @@ function transactionRetryer <T> (func: (err: any, data: T) => any) {
 
         errorFilter: err => {
           const willRetry = (err.name === 'SequelizeDatabaseError')
-          logger.debug('Maybe retrying the transaction function.', { willRetry, err })
+          logger.debug('Maybe retrying the transaction function.', { willRetry, err, tags: [ 'sql', 'retry' ] })
           return willRetry
         }
       },
@@ -68,6 +68,8 @@ function transactionRetryer <T> (func: (err: any, data: T) => any) {
   })
 }
 
+// ---------------------------------------------------------------------------
+
 function updateInstanceWithAnother <M, T extends U, U extends Model<M>> (instanceToUpdate: T, baseInstance: U) {
   const obj = baseInstance.toJSON()
 
@@ -82,12 +84,6 @@ function resetSequelizeInstance (instance: Model<any>, savedFields: object) {
   })
 }
 
-function afterCommitIfTransaction (t: Transaction, fn: Function) {
-  if (t) return t.afterCommit(() => fn())
-
-  return fn()
-}
-
 function deleteNonExistingModels <T extends { hasSameUniqueKeysThan (other: T): boolean } & Pick<Model, 'destroy'>> (
   fromDatabase: T[],
   newModels: T[],
@@ -111,6 +107,18 @@ function setAsUpdated (table: string, id: number, transaction?: Transaction) {
 
 // ---------------------------------------------------------------------------
 
+function runInReadCommittedTransaction <T> (fn: (t: Transaction) => Promise<T>) {
+  return sequelizeTypescript.transaction(t => fn(t))
+}
+
+function afterCommitIfTransaction (t: Transaction, fn: Function) {
+  if (t) return t.afterCommit(() => fn())
+
+  return fn()
+}
+
+// ---------------------------------------------------------------------------
+
 export {
   resetSequelizeInstance,
   retryTransactionWrapper,
@@ -118,5 +126,6 @@ export {
   updateInstanceWithAnother,
   afterCommitIfTransaction,
   deleteNonExistingModels,
-  setAsUpdated
+  setAsUpdated,
+  runInReadCommittedTransaction
 }
index c7b49d6e48382602c457504a6c626184ff39c2c9..de93aa9643f70689a20cd1a85e23175c3921d282 100644 (file)
@@ -56,7 +56,7 @@ async function getOrCreateAPActor (
   if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor
   if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor
 
-  const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType)
+  const { actor: actorRefreshed, refreshed } = await refreshActorIfNeeded(actor, fetchType)
   if (!actorRefreshed) throw new Error('Actor ' + actor.url + ' does not exist anymore.')
 
   await scheduleOutboxFetchIfNeeded(actor, created, refreshed, updateCollections)
index 471688f11feec66a825567c78b6a87286608cb18..fb880a76796b5e6e4424caaf60918f9e80fdccd7 100644 (file)
@@ -1,6 +1,5 @@
-import { resetSequelizeInstance } from '@server/helpers/database-utils'
+import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/helpers/database-utils'
 import { logger } from '@server/helpers/logger'
-import { sequelizeTypescript } from '@server/initializers/database'
 import { VideoChannelModel } from '@server/models/video/video-channel'
 import { MAccount, MActor, MActorFull, MChannel } from '@server/types/models'
 import { ActivityPubActor, ActorImageType } from '@shared/models'
@@ -32,22 +31,23 @@ export class APActorUpdater {
     const bannerInfo = getImageInfoFromObject(this.actorObject, ActorImageType.BANNER)
 
     try {
-      await sequelizeTypescript.transaction(async t => {
-        await this.updateActorInstance(this.actor, this.actorObject)
+      await this.updateActorInstance(this.actor, this.actorObject)
 
-        await updateActorImageInstance(this.actor, ActorImageType.AVATAR, avatarInfo, t)
-        await updateActorImageInstance(this.actor, ActorImageType.BANNER, bannerInfo, t)
-
-        await this.actor.save({ transaction: t })
-
-        this.accountOrChannel.name = this.actorObject.name || this.actorObject.preferredUsername
-        this.accountOrChannel.description = this.actorObject.summary
+      this.accountOrChannel.name = this.actorObject.name || this.actorObject.preferredUsername
+      this.accountOrChannel.description = this.actorObject.summary
 
-        if (this.accountOrChannel instanceof VideoChannelModel) this.accountOrChannel.support = this.actorObject.support
+      if (this.accountOrChannel instanceof VideoChannelModel) this.accountOrChannel.support = this.actorObject.support
 
+      await runInReadCommittedTransaction(async t => {
+        await this.actor.save({ transaction: t })
         await this.accountOrChannel.save({ transaction: t })
       })
 
+      await runInReadCommittedTransaction(async t => {
+        await updateActorImageInstance(this.actor, ActorImageType.AVATAR, avatarInfo, t)
+        await updateActorImageInstance(this.actor, ActorImageType.BANNER, bannerInfo, t)
+      })
+
       logger.info('Remote account %s updated', this.actorObject.url)
     } catch (err) {
       if (this.actor !== undefined && this.actorFieldsSave !== undefined) {
index c2d41dd2845fe2e629416795eb81863e07d3e91c..0a0231a3afb46e870559e747daae096e365df0a9 100644 (file)
@@ -24,12 +24,11 @@ async function processCreateView (activity: ActivityView | ActivityCreate, byAct
     ? activity.object
     : (activity.object as ViewObject).object
 
-  const options = {
+  const { video } = await getOrCreateAPVideo({
     videoObject,
-    fetchType: 'only-video' as 'only-video',
-    allowRefresh: false as false
-  }
-  const { video } = await getOrCreateAPVideo(options)
+    fetchType: 'only-video',
+    allowRefresh: false
+  })
 
   if (!video.isLive) {
     await Redis.Instance.addVideoView(video.id)
index 0b58ddb33f4df12b49b6540294af6d179455803d..22280fce123797119195e66cb9217311f32cbcb7 100644 (file)
@@ -49,7 +49,7 @@ export abstract class APVideoAbstractBuilder {
     })
   }
 
-  protected async setPreview (video: MVideoFullLight, t: Transaction) {
+  protected async setPreview (video: MVideoFullLight, t?: Transaction) {
     // Don't fetch the preview that could be big, create a placeholder instead
     const previewIcon = getPreviewFromIcons(this.videoObject)
     if (!previewIcon) return
index fcb2a50917ac73a7d7686962a3b33008d40a9e9c..1c5fc4f84dc9730ce2f5cf8a3a4ea38cc9a7bb12 100644 (file)
@@ -28,7 +28,7 @@ function getTrackerUrls (object: VideoObject, video: MVideoWithHost) {
 async function setVideoTrackers (options: {
   video: MVideo
   trackers: string[]
-  transaction?: Transaction
+  transaction: Transaction
 }) {
   const { video, trackers, transaction } = options
 
index 3339611fc02405a121270fdcfa0203fb152fadd6..e17e5fdc2f89d51ddcc272c7f7c19ac03f2fd7e2 100644 (file)
@@ -1,7 +1,6 @@
 import { Transaction } from 'sequelize/types'
-import { resetSequelizeInstance } from '@server/helpers/database-utils'
+import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/helpers/database-utils'
 import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger'
-import { sequelizeTypescript } from '@server/initializers/database'
 import { Notifier } from '@server/lib/notifier'
 import { PeerTubeSocket } from '@server/lib/peertube-socket'
 import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist'
@@ -48,24 +47,26 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
 
       const thumbnailModel = await this.tryToGenerateThumbnail(this.video)
 
-      const videoUpdated = await sequelizeTypescript.transaction(async t => {
-        this.checkChannelUpdateOrThrow(channelActor)
+      this.checkChannelUpdateOrThrow(channelActor)
 
-        const videoUpdated = await this.updateVideo(channelActor.VideoChannel, t, overrideTo)
+      const videoUpdated = await this.updateVideo(channelActor.VideoChannel, undefined, overrideTo)
 
-        if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
+      if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel)
 
-        await this.setPreview(videoUpdated, t)
+      await runInReadCommittedTransaction(async t => {
         await this.setWebTorrentFiles(videoUpdated, t)
         await this.setStreamingPlaylists(videoUpdated, t)
-        await this.setTags(videoUpdated, t)
-        await this.setTrackers(videoUpdated, t)
-        await this.setCaptions(videoUpdated, t)
-        await this.setOrDeleteLive(videoUpdated, t)
-
-        return videoUpdated
       })
 
+      await Promise.all([
+        runInReadCommittedTransaction(t => this.setTags(videoUpdated, t)),
+        runInReadCommittedTransaction(t => this.setTrackers(videoUpdated, t)),
+        this.setOrDeleteLive(videoUpdated),
+        this.setPreview(videoUpdated)
+      ])
+
+      await runInReadCommittedTransaction(t => this.setCaptions(videoUpdated, t))
+
       await autoBlacklistVideoIfNeeded({
         video: videoUpdated,
         user: undefined,
@@ -103,7 +104,7 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
     }
   }
 
-  private updateVideo (channel: MChannelId, transaction: Transaction, overrideTo?: string[]) {
+  private updateVideo (channel: MChannelId, transaction?: Transaction, overrideTo?: string[]) {
     const to = overrideTo || this.videoObject.to
     const videoData = getVideoAttributesFromObject(channel, this.videoObject, to)
     this.video.name = videoData.name
@@ -140,7 +141,9 @@ export class APVideoUpdater extends APVideoAbstractBuilder {
     await this.insertOrReplaceCaptions(videoUpdated, t)
   }
 
-  private async setOrDeleteLive (videoUpdated: MVideoFullLight, transaction: Transaction) {
+  private async setOrDeleteLive (videoUpdated: MVideoFullLight, transaction?: Transaction) {
+    if (!this.video.isLive) return
+
     if (this.video.isLive) return this.insertOrReplaceLive(videoUpdated, transaction)
 
     // Delete existing live if it exists
index 8f561116be70622f49eb898c1e6af7968d16dfd9..44aaa24efe3b02c55185b12fc512249e6deb5595 100644 (file)
@@ -1886,7 +1886,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
     return Array.isArray(this.VideoFiles) === true && this.VideoFiles.length !== 0
   }
 
-  async addAndSaveThumbnail (thumbnail: MThumbnail, transaction: Transaction) {
+  async addAndSaveThumbnail (thumbnail: MThumbnail, transaction?: Transaction) {
     thumbnail.videoId = this.id
 
     const savedThumbnail = await thumbnail.save({ transaction })