1
0
Fork 0

Merge branch 'release/4.2.0' into develop

This commit is contained in:
Chocobozzz 2022-06-17 14:17:06 +02:00
commit fba911e2c8
No known key found for this signature in database
GPG key ID: 583A612D890159BE
12 changed files with 154 additions and 69 deletions

View file

@ -25,6 +25,7 @@ export class JobsComponent extends RestTable implements OnInit {
'activitypub-follow', 'activitypub-follow',
'activitypub-http-broadcast', 'activitypub-http-broadcast',
'activitypub-http-broadcast-parallel',
'activitypub-http-fetcher', 'activitypub-http-fetcher',
'activitypub-http-unicast', 'activitypub-http-unicast',
'activitypub-refresher', 'activitypub-refresher',

View file

@ -0,0 +1,89 @@
import Bluebird from 'bluebird'
import { wait } from '@shared/core-utils'
import {
createSingleServer,
doubleFollow,
killallServers,
PeerTubeServer,
setAccessTokensToServers,
waitJobs
} from '@shared/server-commands'
let servers: PeerTubeServer[]
const viewers: { xForwardedFor: string }[] = []
let videoId: string
run()
.then(() => process.exit(0))
.catch(err => console.error(err))
.finally(() => killallServers(servers))
async function run () {
await prepare()
while (true) {
await runViewers()
}
}
async function prepare () {
console.log('Preparing servers...')
const config = {
log: {
level: 'info'
},
rates_limit: {
api: {
max: 5_000_000
}
},
views: {
videos: {
local_buffer_update_interval: '30 minutes',
ip_view_expiration: '1 hour'
}
}
}
servers = await Promise.all([
createSingleServer(1, config, { nodeArgs: [ '--inspect' ] }),
createSingleServer(2, config),
createSingleServer(3, config)
])
await setAccessTokensToServers(servers)
await doubleFollow(servers[0], servers[1])
await doubleFollow(servers[0], servers[2])
const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
videoId = uuid
await waitJobs(servers)
const THOUSAND_VIEWERS = 2
for (let i = 2; i < 252; i++) {
for (let j = 2; j < 6; j++) {
for (let k = 2; k < THOUSAND_VIEWERS + 2; k++) {
viewers.push({ xForwardedFor: `0.${k}.${j}.${i},127.0.0.1` })
}
}
}
console.log('Servers preparation finished.')
}
async function runViewers () {
console.log('Will run views of %d viewers.', viewers.length)
const before = new Date().getTime()
await Bluebird.map(viewers, viewer => {
return servers[0].views.simulateView({ id: videoId, xForwardedFor: viewer.xForwardedFor })
}, { concurrency: 100 })
console.log('Finished to run views in %d seconds.', (new Date().getTime() - before) / 1000)
await wait(5000)
}

View file

@ -139,6 +139,7 @@ const REMOTE_SCHEME = {
const JOB_ATTEMPTS: { [id in JobType]: number } = { const JOB_ATTEMPTS: { [id in JobType]: number } = {
'activitypub-http-broadcast': 1, 'activitypub-http-broadcast': 1,
'activitypub-http-broadcast-parallel': 1,
'activitypub-http-unicast': 1, 'activitypub-http-unicast': 1,
'activitypub-http-fetcher': 2, 'activitypub-http-fetcher': 2,
'activitypub-follow': 5, 'activitypub-follow': 5,
@ -159,6 +160,7 @@ const JOB_ATTEMPTS: { [id in JobType]: number } = {
// Excluded keys are jobs that can be configured by admins // Excluded keys are jobs that can be configured by admins
const JOB_CONCURRENCY: { [id in Exclude<JobType, 'video-transcoding' | 'video-import'>]: number } = { const JOB_CONCURRENCY: { [id in Exclude<JobType, 'video-transcoding' | 'video-import'>]: number } = {
'activitypub-http-broadcast': 1, 'activitypub-http-broadcast': 1,
'activitypub-http-broadcast-parallel': 30,
'activitypub-http-unicast': 10, 'activitypub-http-unicast': 10,
'activitypub-http-fetcher': 3, 'activitypub-http-fetcher': 3,
'activitypub-cleaner': 1, 'activitypub-cleaner': 1,
@ -176,6 +178,7 @@ const JOB_CONCURRENCY: { [id in Exclude<JobType, 'video-transcoding' | 'video-im
} }
const JOB_TTL: { [id in JobType]: number } = { const JOB_TTL: { [id in JobType]: number } = {
'activitypub-http-broadcast': 60000 * 10, // 10 minutes 'activitypub-http-broadcast': 60000 * 10, // 10 minutes
'activitypub-http-broadcast-parallel': 60000 * 10, // 10 minutes
'activitypub-http-unicast': 60000 * 10, // 10 minutes 'activitypub-http-unicast': 60000 * 10, // 10 minutes
'activitypub-http-fetcher': 1000 * 3600 * 10, // 10 hours 'activitypub-http-fetcher': 1000 * 3600 * 10, // 10 hours
'activitypub-follow': 60000 * 10, // 10 minutes 'activitypub-follow': 60000 * 10, // 10 minutes
@ -371,7 +374,7 @@ const VIEW_LIFETIME = {
VIEWER_STATS: 60000 * 60 // 1 hour VIEWER_STATS: 60000 * 60 // 1 hour
} }
const MAX_LOCAL_VIEWER_WATCH_SECTIONS = 10 const MAX_LOCAL_VIEWER_WATCH_SECTIONS = 100
let CONTACT_FORM_LIFETIME = 60000 * 60 // 1 hour let CONTACT_FORM_LIFETIME = 60000 * 60 // 1 hour

View file

@ -23,7 +23,7 @@ async function createOrUpdateLocalVideoViewer (watchAction: WatchActionObject, v
: null, : null,
videoId: video.id videoId: video.id
}) }, { transaction: t })
await LocalVideoViewerWatchSectionModel.bulkCreateSections({ await LocalVideoViewerWatchSectionModel.bulkCreateSections({
localVideoViewerId: localVideoViewer.id, localVideoViewerId: localVideoViewer.id,
@ -31,7 +31,9 @@ async function createOrUpdateLocalVideoViewer (watchAction: WatchActionObject, v
watchSections: watchAction.watchSections.map(s => ({ watchSections: watchAction.watchSections.map(s => ({
start: s.startTimestamp, start: s.startTimestamp,
end: s.endTimestamp end: s.endTimestamp
})) })),
transaction: t
}) })
} }

View file

@ -26,7 +26,7 @@ async function sendView (options: {
return buildViewActivity({ url, byActor, video, audience, type }) return buildViewActivity({ url, byActor, video, audience, type })
} }
return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction, contextType: 'View' }) return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction, contextType: 'View', parallelizable: true })
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View file

@ -3,7 +3,7 @@ import { ACTIVITY_PUB } from '@server/initializers/constants'
import { ActorModel } from '@server/models/actor/actor' import { ActorModel } from '@server/models/actor/actor'
import { VideoModel } from '@server/models/video/video' import { VideoModel } from '@server/models/video/video'
import { VideoShareModel } from '@server/models/video/video-share' import { VideoShareModel } from '@server/models/video/video-share'
import { MActorFollowersUrl, MActorLight, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '@server/types/models' import { MActorFollowersUrl, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '@server/types/models'
import { ActivityAudience } from '@shared/models' import { ActivityAudience } from '@shared/models'
function getOriginVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[] = []): ActivityAudience { function getOriginVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[] = []): ActivityAudience {
@ -51,13 +51,13 @@ function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[
} }
async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) { async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) {
const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) const actors = await VideoShareModel.listActorIdsAndFollowerUrlsByShare(video.id, t)
const videoAll = video as VideoModel const videoAll = video as VideoModel
const videoActor = videoAll.VideoChannel?.Account const videoActor = videoAll.VideoChannel?.Account
? videoAll.VideoChannel.Account.Actor ? videoAll.VideoChannel.Account.Actor
: await ActorModel.loadFromAccountByVideoId(video.id, t) : await ActorModel.loadAccountActorFollowerUrlByVideoId(video.id, t)
actors.push(videoActor) actors.push(videoActor)

View file

@ -15,17 +15,18 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
byActor: MActorLight byActor: MActorLight
video: MVideoImmutable | MVideoAccountLight video: MVideoImmutable | MVideoAccountLight
contextType: ContextType contextType: ContextType
parallelizable?: boolean
transaction?: Transaction transaction?: Transaction
}) { }) {
const { byActor, video, transaction, contextType } = options const { byActor, video, transaction, contextType, parallelizable } = options
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction)
// Send to origin // Send to origin
if (video.isOwned() === false) { if (video.isOwned() === false) {
return sendVideoActivityToOrigin(activityBuilder, options) return sendVideoActivityToOrigin(activityBuilder, options)
} }
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction)
// Send to followers // Send to followers
const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo) const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo)
const activity = activityBuilder(audience) const activity = activityBuilder(audience)
@ -38,6 +39,7 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
toFollowersOf: actorsInvolvedInVideo, toFollowersOf: actorsInvolvedInVideo,
transaction, transaction,
actorsException, actorsException,
parallelizable,
contextType contextType
}) })
} }
@ -130,9 +132,10 @@ async function broadcastToFollowers (options: {
transaction: Transaction transaction: Transaction
contextType: ContextType contextType: ContextType
parallelizable?: boolean
actorsException?: MActorWithInboxes[] actorsException?: MActorWithInboxes[]
}) { }) {
const { data, byActor, toFollowersOf, transaction, contextType, actorsException = [] } = options const { data, byActor, toFollowersOf, transaction, contextType, actorsException = [], parallelizable } = options
const uris = await computeFollowerUris(toFollowersOf, actorsException, transaction) const uris = await computeFollowerUris(toFollowersOf, actorsException, transaction)
@ -141,6 +144,7 @@ async function broadcastToFollowers (options: {
uris, uris,
data, data,
byActor, byActor,
parallelizable,
contextType contextType
}) })
}) })
@ -173,8 +177,9 @@ function broadcastTo (options: {
data: any data: any
byActor: MActorId byActor: MActorId
contextType: ContextType contextType: ContextType
parallelizable?: boolean // default to false
}) { }) {
const { uris, data, byActor, contextType } = options const { uris, data, byActor, contextType, parallelizable } = options
if (uris.length === 0) return undefined if (uris.length === 0) return undefined
@ -200,7 +205,13 @@ function broadcastTo (options: {
contextType contextType
} }
JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) JobQueue.Instance.createJob({
type: parallelizable
? 'activitypub-http-broadcast-parallel'
: 'activitypub-http-broadcast',
payload
})
} }
for (const unicastUri of unicastUris) { for (const unicastUri of unicastUris) {

View file

@ -43,6 +43,7 @@ import { processVideosViewsStats } from './handlers/video-views-stats'
type CreateJobArgument = type CreateJobArgument =
{ type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } |
{ type: 'activitypub-http-broadcast-parallel', payload: ActivitypubHttpBroadcastPayload } |
{ type: 'activitypub-http-unicast', payload: ActivitypubHttpUnicastPayload } | { type: 'activitypub-http-unicast', payload: ActivitypubHttpUnicastPayload } |
{ type: 'activitypub-http-fetcher', payload: ActivitypubHttpFetcherPayload } | { type: 'activitypub-http-fetcher', payload: ActivitypubHttpFetcherPayload } |
{ type: 'activitypub-http-cleaner', payload: {} } | { type: 'activitypub-http-cleaner', payload: {} } |
@ -68,6 +69,7 @@ export type CreateJobOptions = {
const handlers: { [id in JobType]: (job: Job) => Promise<any> } = { const handlers: { [id in JobType]: (job: Job) => Promise<any> } = {
'activitypub-http-broadcast': processActivityPubHttpBroadcast, 'activitypub-http-broadcast': processActivityPubHttpBroadcast,
'activitypub-http-broadcast-parallel': processActivityPubHttpBroadcast,
'activitypub-http-unicast': processActivityPubHttpUnicast, 'activitypub-http-unicast': processActivityPubHttpUnicast,
'activitypub-http-fetcher': processActivityPubHttpFetcher, 'activitypub-http-fetcher': processActivityPubHttpFetcher,
'activitypub-cleaner': processActivityPubCleaner, 'activitypub-cleaner': processActivityPubCleaner,
@ -93,6 +95,7 @@ const errorHandlers: { [id in JobType]?: (job: Job, err: any) => Promise<any> }
const jobTypes: JobType[] = [ const jobTypes: JobType[] = [
'activitypub-follow', 'activitypub-follow',
'activitypub-http-broadcast', 'activitypub-http-broadcast',
'activitypub-http-broadcast-parallel',
'activitypub-http-fetcher', 'activitypub-http-fetcher',
'activitypub-http-unicast', 'activitypub-http-unicast',
'activitypub-cleaner', 'activitypub-cleaner',

View file

@ -1,4 +1,3 @@
import { isTestInstance } from '@server/helpers/core-utils' import { isTestInstance } from '@server/helpers/core-utils'
import { logger, loggerTagsFactory } from '@server/helpers/logger' import { logger, loggerTagsFactory } from '@server/helpers/logger'
import { VIEW_LIFETIME } from '@server/initializers/constants' import { VIEW_LIFETIME } from '@server/initializers/constants'

View file

@ -1,5 +1,5 @@
import { values } from 'lodash' import { values } from 'lodash'
import { literal, Op, Transaction } from 'sequelize' import { literal, Op, QueryTypes, Transaction } from 'sequelize'
import { import {
AllowNull, AllowNull,
BelongsTo, BelongsTo,
@ -43,15 +43,18 @@ import {
MActorAccountChannelId, MActorAccountChannelId,
MActorAPAccount, MActorAPAccount,
MActorAPChannel, MActorAPChannel,
MActorFollowersUrl,
MActorFormattable, MActorFormattable,
MActorFull, MActorFull,
MActorHost, MActorHost,
MActorId,
MActorServer, MActorServer,
MActorSummaryFormattable, MActorSummaryFormattable,
MActorUrl, MActorUrl,
MActorWithInboxes MActorWithInboxes
} from '../../types/models' } from '../../types/models'
import { AccountModel } from '../account/account' import { AccountModel } from '../account/account'
import { getServerActor } from '../application/application'
import { ServerModel } from '../server/server' import { ServerModel } from '../server/server'
import { isOutdated, throwIfNotValid } from '../utils' import { isOutdated, throwIfNotValid } from '../utils'
import { VideoModel } from '../video/video' import { VideoModel } from '../video/video'
@ -304,7 +307,10 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
}) })
VideoChannel: VideoChannelModel VideoChannel: VideoChannelModel
static load (id: number): Promise<MActor> { static async load (id: number): Promise<MActor> {
const actorServer = await getServerActor()
if (id === actorServer.id) return actorServer
return ActorModel.unscoped().findByPk(id) return ActorModel.unscoped().findByPk(id)
} }
@ -312,48 +318,21 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
return ActorModel.scope(ScopeNames.FULL).findByPk(id) return ActorModel.scope(ScopeNames.FULL).findByPk(id)
} }
static loadFromAccountByVideoId (videoId: number, transaction: Transaction): Promise<MActor> { static loadAccountActorFollowerUrlByVideoId (videoId: number, transaction: Transaction) {
const query = { const query = `SELECT "actor"."id" AS "id", "actor"."followersUrl" AS "followersUrl" ` +
include: [ `FROM "actor" ` +
{ `INNER JOIN "account" ON "actor"."id" = "account"."actorId" ` +
attributes: [ 'id' ], `INNER JOIN "videoChannel" ON "videoChannel"."accountId" = "account"."id" ` +
model: AccountModel.unscoped(), `INNER JOIN "video" ON "video"."channelId" = "videoChannel"."id" AND "video"."id" = :videoId`
required: true,
include: [ const options = {
{ type: QueryTypes.SELECT as QueryTypes.SELECT,
attributes: [ 'id' ], replacements: { videoId },
model: VideoChannelModel.unscoped(), plain: true as true,
required: true,
include: [
{
attributes: [ 'id' ],
model: VideoModel.unscoped(),
required: true,
where: {
id: videoId
}
}
]
}
]
}
],
transaction transaction
} }
return ActorModel.unscoped().findOne(query) return ActorModel.sequelize.query<MActorId & MActorFollowersUrl>(query, options)
}
static isActorUrlExist (url: string) {
const query = {
raw: true,
where: {
url
}
}
return ActorModel.unscoped().findOne(query)
.then(a => !!a)
} }
static listByFollowersUrls (followersUrls: string[], transaction?: Transaction): Promise<MActorFull[]> { static listByFollowersUrls (followersUrls: string[], transaction?: Transaction): Promise<MActorFull[]> {

View file

@ -3,7 +3,7 @@ import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Mode
import { AttributesOnly } from '@shared/typescript-utils' import { AttributesOnly } from '@shared/typescript-utils'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { CONSTRAINTS_FIELDS } from '../../initializers/constants' import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { MActorDefault } from '../../types/models' import { MActorDefault, MActorFollowersUrl, MActorId } from '../../types/models'
import { MVideoShareActor, MVideoShareFull } from '../../types/models/video' import { MVideoShareActor, MVideoShareFull } from '../../types/models/video'
import { ActorModel } from '../actor/actor' import { ActorModel } from '../actor/actor'
import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' import { buildLocalActorIdsIn, throwIfNotValid } from '../utils'
@ -107,22 +107,19 @@ export class VideoShareModel extends Model<Partial<AttributesOnly<VideoShareMode
}) })
} }
static loadActorsByShare (videoId: number, t: Transaction): Promise<MActorDefault[]> { static listActorIdsAndFollowerUrlsByShare (videoId: number, t: Transaction) {
const query = { const query = `SELECT "actor"."id" AS "id", "actor"."followersUrl" AS "followersUrl" ` +
where: { `FROM "videoShare" ` +
videoId `INNER JOIN "actor" ON "actor"."id" = "videoShare"."actorId" ` +
}, `WHERE "videoShare"."videoId" = :videoId`
include: [
{ const options = {
model: ActorModel, type: QueryTypes.SELECT as QueryTypes.SELECT,
required: true replacements: { videoId },
}
],
transaction: t transaction: t
} }
return VideoShareModel.scope(ScopeNames.FULL).findAll(query) return VideoShareModel.sequelize.query<MActorId & MActorFollowersUrl>(query, options)
.then((res: MVideoShareFull[]) => res.map(r => r.Actor))
} }
static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Promise<MActorDefault[]> { static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Promise<MActorDefault[]> {

View file

@ -9,6 +9,7 @@ export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed'
export type JobType = export type JobType =
| 'activitypub-http-unicast' | 'activitypub-http-unicast'
| 'activitypub-http-broadcast' | 'activitypub-http-broadcast'
| 'activitypub-http-broadcast-parallel'
| 'activitypub-http-fetcher' | 'activitypub-http-fetcher'
| 'activitypub-cleaner' | 'activitypub-cleaner'
| 'activitypub-follow' | 'activitypub-follow'