Optimize video viewer stats
Many Redis (and so network) calls can be expensive Avoid them if we can by using in memory cache
This commit is contained in:
parent
8ecf95471d
commit
de862fd0e7
2 changed files with 45 additions and 8 deletions
|
@ -352,9 +352,9 @@ class Redis {
|
|||
return { setKey: `local-video-views-buffer`, videoKey: `local-video-views-buffer-${videoId}` }
|
||||
}
|
||||
|
||||
private generateLocalVideoViewerKeys (ip: string, videoId: number): { setKey: string, viewerKey: string }
|
||||
private generateLocalVideoViewerKeys (): { setKey: string }
|
||||
private generateLocalVideoViewerKeys (ip?: string, videoId?: number) {
|
||||
generateLocalVideoViewerKeys (ip: string, videoId: number): { setKey: string, viewerKey: string }
|
||||
generateLocalVideoViewerKeys (): { setKey: string }
|
||||
generateLocalVideoViewerKeys (ip?: string, videoId?: number) {
|
||||
return { setKey: `local-video-viewer-stats-keys`, viewerKey: `local-video-viewer-stats-${ip}-${videoId}` }
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ type LocalViewerStats = {
|
|||
export class VideoViewerStats {
|
||||
private processingViewersStats = false
|
||||
|
||||
private readonly viewerCache = new Map<string, LocalViewerStats>()
|
||||
|
||||
constructor () {
|
||||
setInterval(() => this.processViewerStats(), VIEW_LIFETIME.VIEWER_STATS)
|
||||
}
|
||||
|
@ -56,7 +58,7 @@ export class VideoViewerStats {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getWatchTime (videoId: number, ip: string) {
|
||||
const stats: LocalViewerStats = await Redis.Instance.getLocalVideoViewer({ ip, videoId })
|
||||
const stats: LocalViewerStats = await this.getLocalVideoViewerByIP({ ip, videoId })
|
||||
|
||||
return stats?.watchTime || 0
|
||||
}
|
||||
|
@ -72,7 +74,7 @@ export class VideoViewerStats {
|
|||
const { video, ip, viewEvent, currentTime } = options
|
||||
const nowMs = new Date().getTime()
|
||||
|
||||
let stats: LocalViewerStats = await Redis.Instance.getLocalVideoViewer({ ip, videoId: video.id })
|
||||
let stats: LocalViewerStats = await this.getLocalVideoViewerByIP({ ip, videoId: video.id })
|
||||
|
||||
if (stats && stats.watchSections.length >= MAX_LOCAL_VIEWER_WATCH_SECTIONS) {
|
||||
logger.warn('Too much watch section to store for a viewer, skipping this one', { currentTime, viewEvent, ...lTags(video.uuid) })
|
||||
|
@ -121,7 +123,7 @@ export class VideoViewerStats {
|
|||
|
||||
logger.debug('Set local video viewer stats for video %s.', video.uuid, { stats, ...lTags(video.uuid) })
|
||||
|
||||
await Redis.Instance.setLocalVideoViewer(ip, video.id, stats)
|
||||
await this.setLocalVideoViewer(ip, video.id, stats)
|
||||
}
|
||||
|
||||
async processViewerStats () {
|
||||
|
@ -136,7 +138,7 @@ export class VideoViewerStats {
|
|||
const allKeys = await Redis.Instance.listLocalVideoViewerKeys()
|
||||
|
||||
for (const key of allKeys) {
|
||||
const stats: LocalViewerStats = await Redis.Instance.getLocalVideoViewer({ key })
|
||||
const stats: LocalViewerStats = await this.getLocalVideoViewerByKey(key)
|
||||
|
||||
// Process expired stats
|
||||
if (stats.lastUpdated > now - VIEW_LIFETIME.VIEWER_STATS) {
|
||||
|
@ -155,7 +157,7 @@ export class VideoViewerStats {
|
|||
}
|
||||
})
|
||||
|
||||
await Redis.Instance.deleteLocalVideoViewersKeys(key)
|
||||
await this.deleteLocalVideoViewersKeys(key)
|
||||
} catch (err) {
|
||||
logger.error('Cannot process viewer stats for Redis key %s.', key, { err, ...lTags() })
|
||||
}
|
||||
|
@ -193,4 +195,39 @@ export class VideoViewerStats {
|
|||
private buildWatchTimeFromSections (sections: { start: number, end: number }[]) {
|
||||
return sections.reduce((p, current) => p + (current.end - current.start), 0)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Redis calls can be expensive so try to cache things in front of it
|
||||
*
|
||||
*/
|
||||
|
||||
private getLocalVideoViewerByIP (options: {
|
||||
ip: string
|
||||
videoId: number
|
||||
}): Promise<LocalViewerStats> {
|
||||
const { viewerKey } = Redis.Instance.generateLocalVideoViewerKeys(options.ip, options.videoId)
|
||||
|
||||
return this.getLocalVideoViewerByKey(viewerKey)
|
||||
}
|
||||
|
||||
private getLocalVideoViewerByKey (key: string): Promise<LocalViewerStats> {
|
||||
const viewer = this.viewerCache.get(key)
|
||||
if (viewer) return Promise.resolve(viewer)
|
||||
|
||||
return Redis.Instance.getLocalVideoViewer({ key })
|
||||
}
|
||||
|
||||
private setLocalVideoViewer (ip: string, videoId: number, stats: LocalViewerStats) {
|
||||
const { viewerKey } = Redis.Instance.generateLocalVideoViewerKeys(ip, videoId)
|
||||
this.viewerCache.set(viewerKey, stats)
|
||||
|
||||
return Redis.Instance.setLocalVideoViewer(ip, videoId, stats)
|
||||
}
|
||||
|
||||
private deleteLocalVideoViewersKeys (key: string) {
|
||||
this.viewerCache.delete(key)
|
||||
|
||||
return Redis.Instance.deleteLocalVideoViewersKeys(key)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue