Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/homepage scoring #251

Merged
merged 14 commits into from
Dec 6, 2023
6 changes: 3 additions & 3 deletions src/mappings/content/commentsAndReactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export async function processReactVideoMessage(

await processVideoReaction(overlay, block, memberId, video, reactionType, existingReaction)

videoRelevanceManager.scheduleRecalcForVideo(video.id)
videoRelevanceManager.scheduleRecalcForChannel(channelId)

return new MetaprotocolTransactionResultOK()
}
Expand Down Expand Up @@ -403,7 +403,7 @@ export async function processCreateCommentMessage(
// schedule comment counters update
commentCountersManager.scheduleRecalcForComment(comment.parentCommentId)
commentCountersManager.scheduleRecalcForVideo(comment.videoId)
videoRelevanceManager.scheduleRecalcForVideo(comment.videoId)
videoRelevanceManager.scheduleRecalcForChannel(video.channelId)

// add CommentCreated event
const event = overlay.getRepository(Event).new({
Expand Down Expand Up @@ -537,7 +537,7 @@ export async function processDeleteCommentMessage(
// schedule comment counters update
commentCountersManager.scheduleRecalcForComment(comment.parentCommentId)
commentCountersManager.scheduleRecalcForVideo(comment.videoId)
videoRelevanceManager.scheduleRecalcForVideo(comment.videoId)
videoRelevanceManager.scheduleRecalcForChannel(video.channelId)

// update the comment
comment.text = ''
Expand Down
2 changes: 0 additions & 2 deletions src/mappings/content/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ import {
genericEventFields,
invalidMetadata,
metaprotocolTransactionFailure,
videoRelevanceManager,
} from '../utils'
import { AsDecoded, ASSETS_MAP, EntityAssetProps, EntityAssetsMap, MetaNumberProps } from './utils'

Expand Down Expand Up @@ -572,7 +571,6 @@ export async function processModerateCommentMessage(
// schedule comment counters updates
commentCountersManager.scheduleRecalcForComment(comment.parentCommentId)
commentCountersManager.scheduleRecalcForVideo(comment.videoId)
videoRelevanceManager.scheduleRecalcForVideo(comment.videoId)

comment.text = ''
comment.status = CommentStatus.MODERATED
Expand Down
2 changes: 1 addition & 1 deletion src/mappings/content/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export async function processVideoCreatedEvent({
videoRelevance: 0,
})

videoRelevanceManager.scheduleRecalcForVideo(videoId)
videoRelevanceManager.scheduleRecalcForChannel(channelId.toString())

// fetch related channel and owner
const channel = await overlay.getRepository(Channel).getByIdOrFail(channelId.toString())
Expand Down
6 changes: 5 additions & 1 deletion src/mappings/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import { VideoRelevanceManager } from '../utils/VideoRelevanceManager'

export const commentCountersManager = new CommentCountersManager()
export const videoRelevanceManager = new VideoRelevanceManager()
videoRelevanceManager.init(1000 * 60 * 60)
// eslint-disable-next-line no-void
void videoRelevanceManager.init({
fullUpdateLoopTime: 1000 * 60 * 60 * 12, // 12 hrs
scheduledUpdateLookTime: 1000 * 60 * 10, // 10 mins
})

export const JOYSTREAM_SS58_PREFIX = 126

Expand Down
10 changes: 9 additions & 1 deletion src/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ async function processEvent<EventName extends EventNames>(
async function afterDbUpdate(em: EntityManager) {
await commentCountersManager.updateVideoCommentsCounters(em)
await commentCountersManager.updateParentRepliesCounters(em)
await videoRelevanceManager.updateVideoRelevanceValue(em)
}

processor.run(new TypeormDatabase({ isolationLevel: 'READ COMMITTED' }), async (ctx) => {
Expand Down Expand Up @@ -314,9 +313,18 @@ processor.run(new TypeormDatabase({ isolationLevel: 'READ COMMITTED' }), async (
}
}
}

if (
!videoRelevanceManager.isVideoRelevanceEnabled &&
block.header.height >= exportBlockNumber
) {
videoRelevanceManager.turnOnVideoRelevanceManager()
}

// Importing exported offchain state
if (block.header.height >= exportBlockNumber && !offchainState.isImported) {
ctx.log.info(`Export block ${exportBlockNumber} reached, importing offchain state...`)
// there is no need to recalc video relevance before orion is synced
await overlay.updateDatabase()
const em = overlay.getEm()
await offchainState.import(em)
Expand Down
4 changes: 0 additions & 4 deletions src/server-extension/resolvers/AdminResolver/utils.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { EntityManager, In } from 'typeorm'
import { CommentCountersManager } from '../../../utils/CommentsCountersManager'
import { Comment } from '../../../model'
import { VideoRelevanceManager } from '../../../utils/VideoRelevanceManager'

export async function processCommentsCensorshipStatusUpdate(em: EntityManager, ids: string[]) {
const manager = new CommentCountersManager()
const videoRelevanceManager = new VideoRelevanceManager()
const comments = await em.getRepository(Comment).find({ where: { id: In(ids) } })
comments.forEach((c) => {
manager.scheduleRecalcForComment(c.parentCommentId)
manager.scheduleRecalcForVideo(c.videoId)
videoRelevanceManager.scheduleRecalcForVideo(c.videoId)
})
await manager.updateVideoCommentsCounters(em)
await manager.updateParentRepliesCounters(em)
await videoRelevanceManager.updateVideoRelevanceValue(em)
}
3 changes: 1 addition & 2 deletions src/server-extension/resolvers/VideosResolver/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,9 @@ export class VideosResolver {

const tick = await config.get(ConfigVariable.VideoRelevanceViewsTick, em)
if (video.viewsNum % tick === 0) {
videoRelevanceManager.scheduleRecalcForVideo(videoId)
videoRelevanceManager.scheduleRecalcForChannel(video.channelId)
}
await em.save([video, video.channel, newView])
await videoRelevanceManager.updateVideoRelevanceValue(em)
return {
videoId,
viewsNum: video.viewsNum,
Expand Down
135 changes: 88 additions & 47 deletions src/utils/VideoRelevanceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,35 @@ import { globalEm } from './globalEm'
// constant used to parse seconds from creation
export const NEWNESS_SECONDS_DIVIDER = 60 * 60 * 24

type VideoRelevanceManagerLoops = {
fullUpdateLoopTime: number
scheduledUpdateLookTime: number
WRadoslaw marked this conversation as resolved.
Show resolved Hide resolved
}

export class VideoRelevanceManager {
private videosToUpdate: Set<string> = new Set()
private channelsToUpdate: Set<string> = new Set()
private _isVideoRelevanceEnabled = false

public get isVideoRelevanceEnabled(): boolean {
return this._isVideoRelevanceEnabled
}

async init({
fullUpdateLoopTime,
scheduledUpdateLookTime,
}: VideoRelevanceManagerLoops): Promise<void> {
const em = await globalEm

this.updateScheduledLoop(em, scheduledUpdateLookTime)
.then(() => {
/* Do nothing */
})
.catch((err) => {
console.error(err)
process.exit(-1)
})

init(intervalMs: number): void {
this.updateLoop(intervalMs)
this.updateFullUpdateLoop(em, fullUpdateLoopTime)
.then(() => {
/* Do nothing */
})
Expand All @@ -19,66 +43,83 @@ export class VideoRelevanceManager {
})
}

scheduleRecalcForVideo(id: string | null | undefined) {
id && this.videosToUpdate.add(id)
turnOnVideoRelevanceManager() {
this._isVideoRelevanceEnabled = true
}

scheduleRecalcForChannel(id: string | null | undefined) {
id && this.channelsToUpdate.add(id)
}

async updateVideoRelevanceValue(em: EntityManager, forceUpdateAll?: boolean) {
if (this.videosToUpdate.size || forceUpdateAll) {
const [
newnessWeight,
viewsWeight,
commentsWeight,
reactionsWeight,
[joystreamTimestampWeight, ytTimestampWeight] = [7, 3],
defaultChannelWeight,
] = await config.get(ConfigVariable.RelevanceWeights, em)
const channelWeight = defaultChannelWeight ?? 1
await em.query(`
WITH weighted_timestamp AS (
SELECT
"video"."id",
(
if (!this._isVideoRelevanceEnabled || !(this.channelsToUpdate.size || forceUpdateAll)) {
return
}

const [
newnessWeight,
viewsWeight,
commentsWeight,
reactionsWeight,
[joystreamTimestampWeight, ytTimestampWeight] = [7, 3],
defaultChannelWeight,
] = await config.get(ConfigVariable.RelevanceWeights, em)
const channelWeight = defaultChannelWeight ?? 1
const wtEpoch = `((
extract(epoch from video.created_at)*${joystreamTimestampWeight} +
COALESCE(extract(epoch from video.published_before_joystream), extract(epoch from video.created_at))*${ytTimestampWeight}
) / ${joystreamTimestampWeight} + ${ytTimestampWeight} as wtEpoch,
"channel"."channel_weight" as CW
FROM
"video"
INNER JOIN
"channel" ON "video"."channel_id" = "channel"."id"
) / ${joystreamTimestampWeight} + ${ytTimestampWeight})`

await em.query(`
WITH videos_with_weight AS (
SELECT
video.id as videoId,
channel.id as channelId,
(ROUND((
(extract(epoch from now()) - ${wtEpoch})
/ ${NEWNESS_SECONDS_DIVIDER} * ${newnessWeight * -1}
+ (views_num * ${viewsWeight})
+ (comments_count * ${commentsWeight})
+ (reactions_count * ${reactionsWeight}))
* COALESCE(channel.channel_weight, ${channelWeight}),2)) as videoRelevance
FROM video
INNER JOIN channel ON video.channel_id = channel.id
${
forceUpdateAll
? ''
: `WHERE "video"."id" IN (${[...this.videosToUpdate.values()]
: `WHERE video.channel_id in (${[...this.channelsToUpdate.values()]
.map((id) => `'${id}'`)
.join(', ')})`
}
)
UPDATE
"video"
SET
"video_relevance" = ROUND(
(
(extract(epoch from now()) - wtEpoch) / ${NEWNESS_SECONDS_DIVIDER} * ${newnessWeight * -1} +
(views_num * ${viewsWeight}) +
(comments_count * ${commentsWeight}) +
(reactions_count * ${reactionsWeight})
) * COALESCE(CW, ${channelWeight}),
2)
FROM
weighted_timestamp
WHERE
"video".id = weighted_timestamp.id;
ORDER BY video.id),

top_channel_score as (
SELECT
channel.id as channelId,
MAX(videoCte.videoRelevance) as maxChannelRelevance
FROM channel
INNER JOIN videos_with_weight as videoCte on videoCte.channelId = channel.id
GROUP BY channel.id)

UPDATE video
SET video_relevance = COALESCE(topChannelVideo.maxChannelRelevance, 1)
FROM videos_with_weight as videoCte
LEFT JOIN top_channel_score as topChannelVideo on topChannelVideo.channelId = videoCte.channelId and topChannelVideo.maxChannelRelevance = videoCte.videoRelevance
WHERE video.id = videoCte.videoId;
`)
this.videosToUpdate.clear()
this.channelsToUpdate.clear()
}

private async updateScheduledLoop(em: EntityManager, intervalMs: number): Promise<void> {
while (true) {
await this.updateVideoRelevanceValue(em)
await new Promise((resolve) => setTimeout(resolve, intervalMs))
}
}

private async updateLoop(intervalMs: number): Promise<void> {
const em = await globalEm
private async updateFullUpdateLoop(em: EntityManager, intervalMs: number): Promise<void> {
while (true) {
await this.updateVideoRelevanceValue(em, true)
await this.updateVideoRelevanceValue(em)
await new Promise((resolve) => setTimeout(resolve, intervalMs))
}
}
Expand Down