openvibers/openvibe.live Commits
Channel 'Most watched': its three Media reads in parallel and the answer kept a minute per channel (p95 was 0.77 s, sequential reads on every offline channel view) (#196)
Changes
@@@ -580,17 +580,23 @@ router.get('/channel/:username', optionalAuth, async (req, res) => {});// ── Most-popular recent VOD + clip (offline-screen "explore" cards) ──++// One answer per channel for a minute, its three Media reads in parallel: they ran one after another on every view++// of an offline channel (p95 0.77 s, 2026-10-17). The answer is the same for every visitor (no auth on this route).++const POPULAR_TTL_MS = 60 * 1000;++const _popularCache = new Map(); // user id → { at, data }router.get('/channel/:username/popular', async (req, res) => {try {const user = await db.getUserByUsername(req.params.username);if (!user) return res.status(404).json({ error: 'Not found' });-- let vods = [], clips = [];++ const hit = _popularCache.get(user.id);++ if (hit && Date.now() - hit.at < POPULAR_TTL_MS) return res.json(hit.data);const media = require('../media-client');-- try { const r = await media.listVods({ user_id: user.id, order: 'views', limit: 12 }); vods = r?.vods || (Array.isArray(r) ? r : []); } catch { /* */ }-- try { const r = await media.listClips({ channel_user_id: user.id, order: 'views', limit: 12 }); clips = r?.clips || (Array.isArray(r) ? r : []); } catch { /* */ }-- let ranges = null;-- try { ranges = await require('../media-proxy/lookups').topContentRanges(user); } catch { /* */ }-- res.json({++ const [vods, clips, ranges] = await Promise.all([++ media.listVods({ user_id: user.id, order: 'views', limit: 12 }).then((r) => r?.vods || (Array.isArray(r) ? r : [])).catch(() => []),++ media.listClips({ channel_user_id: user.id, order: 'views', limit: 12 }).then((r) => r?.clips || (Array.isArray(r) ? r : [])).catch(() => []),++ Promise.resolve().then(() => require('../media-proxy/lookups').topContentRanges(user)).catch(() => null),++ ]);++ const data = {// Singular kept for back-compat; arrays let the offline screen fill the space.vod: vods[0] || null,clip: clips[0] || null,@@@ -598,7 +604,10 @@ router.get('/channel/:username/popular', async (req, res) => {clips,// Top VOD + clip per time window (week/month/all) for the compact offline cycler.ranges,-- });++ };++ if (_popularCache.size >= 500) _popularCache.clear();++ _popularCache.set(user.id, { at: Date.now(), data });++ res.json(data);} catch (err) {res.status(500).json({ error: 'Failed to get popular content' });}nnew file mode 100644@@@ -0,0 +1,58 @@++'use strict';++/**++ * GET /api/streams/channel/:username/popular (the offline channel's "Most watched"): its three Media reads run in++ * parallel and the answer is kept for a minute per channel (they ran one after another on every view: p95 0.77 s,++ * 2026-10-17). A Media read that fails leaves its part empty, not the whole answer.++ *++ * node --import ./test/helpers/pg-preload.mjs test/channel-popular-cache.test.js++ */++const assert = require('assert');++const http = require('http');++++process.env.NODE_ENV = 'test';++const quiet = console.log;++console.log = (...a) => { if (!/^\[/.test(String(a[0]))) quiet(...a); };++console.warn = () => {};++++(async () => {++ const db = require('../server/db/database');++ await db.initDb();++ await db.createUser({ username: 'popstar', email: 'pop@x', password_hash: 'x', display_name: 'popstar', stream_key: 'p'.repeat(32) });++++ const media = require('../server/media-client');++ const lookups = require('../server/media-proxy/lookups');++ const calls = { vods: 0, clips: 0, ranges: 0 };++ let inFlight = 0, maxInFlight = 0;++ const slow = (fn) => async (...a) => {++ inFlight++; maxInFlight = Math.max(maxInFlight, inFlight);++ await new Promise((r) => setTimeout(r, 60));++ inFlight--;++ return fn(...a);++ };++ media.listVods = slow(() => { calls.vods++; return { vods: [{ id: 1, title: 'big night' }] }; });++ media.listClips = slow(() => { calls.clips++; throw new Error('Media down'); });++ lookups.topContentRanges = slow(() => { calls.ranges++; return { week: null, month: null, all: null }; });++++ const express = require('express');++ const app = express();++ app.use('/api/streams', require('../server/streaming/routes'));++ const server = http.createServer(app).listen(0, '127.0.0.1');++ await new Promise((r) => server.once('listening', r));++ const get = async (p) => { const r = await fetch(`http://127.0.0.1:${server.address().port}${p}`); return { status: r.status, json: await r.json() }; };++++ const first = await get('/api/streams/channel/popstar/popular');++ assert.strictEqual(first.status, 200);++ assert.strictEqual(first.json.vod.title, 'big night');++ assert.deepStrictEqual(first.json.clips, [], 'a failed Media read leaves its part empty');++ assert.ok(first.json.ranges, 'the other reads still answer');++ assert.strictEqual(maxInFlight, 3, 'the three Media reads run in parallel');++++ await get('/api/streams/channel/popstar/popular');++ assert.deepStrictEqual(calls, { vods: 1, clips: 1, ranges: 1 }, 'the second view within a minute is answered from the cache');++ assert.strictEqual((await get('/api/streams/channel/nobody_here/popular')).status, 404);++++ server.close();++ console.log = quiet;++ console.log('channel popular: all checks passed');++ process.exit(0);++})().catch((e) => { console.error(e); process.exit(1); });
@@ -580,17 +580,23 @@ router.get('/channel/:username', optionalAuth, async (req, res) => {
@@ -580,17 +580,23 @@ router.get('/channel/:username', optionalAuth, async (req, res) => {
});
});
// ── Most-popular recent VOD + clip (offline-screen "explore" cards) ──
// ── Most-popular recent VOD + clip (offline-screen "explore" cards) ──
+// One answer per channel for a minute, its three Media reads in parallel: they ran one after another on every view
+// of an offline channel (p95 0.77 s, 2026-10-17). The answer is the same for every visitor (no auth on this route).
+const POPULAR_TTL_MS = 60 * 1000;
+const _popularCache = new Map(); // user id → { at, data }
router.get('/channel/:username/popular', async (req, res) => {
router.get('/channel/:username/popular', async (req, res) => {
try {
try {
const user = await db.getUserByUsername(req.params.username);
const user = await db.getUserByUsername(req.params.username);
if (!user) return res.status(404).json({ error: 'Not found' });
if (!user) return res.status(404).json({ error: 'Not found' });
- let vods = [], clips = [];
+ const hit = _popularCache.get(user.id);
+ if (hit && Date.now() - hit.at < POPULAR_TTL_MS) return res.json(hit.data);
const media = require('../media-client');
const media = require('../media-client');
- try { const r = await media.listVods({ user_id: user.id, order: 'views', limit: 12 }); vods = r?.vods || (Array.isArray(r) ? r : []); } catch { /* */ }
- try { const r = await media.listClips({ channel_user_id: user.id, order: 'views', limit: 12 }); clips = r?.clips || (Array.isArray(r) ? r : []); } catch { /* */ }
- let ranges = null;
- try { ranges = await require('../media-proxy/lookups').topContentRanges(user); } catch { /* */ }
- res.json({
+ const [vods, clips, ranges] = await Promise.all([
+ media.listVods({ user_id: user.id, order: 'views', limit: 12 }).then((r) => r?.vods || (Array.isArray(r) ? r : [])).catch(() => []),
+ media.listClips({ channel_user_id: user.id, order: 'views', limit: 12 }).then((r) => r?.clips || (Array.isArray(r) ? r : [])).catch(() => []),
+ Promise.resolve().then(() => require('../media-proxy/lookups').topContentRanges(user)).catch(() => null),
+ ]);
+ const data = {
// Singular kept for back-compat; arrays let the offline screen fill the space.
// Singular kept for back-compat; arrays let the offline screen fill the space.
vod: vods[0] || null,
vod: vods[0] || null,
clip: clips[0] || null,
clip: clips[0] || null,
@@ -598,7 +604,10 @@ router.get('/channel/:username/popular', async (req, res) => {
@@ -598,7 +604,10 @@ router.get('/channel/:username/popular', async (req, res) => {
clips,
clips,
// Top VOD + clip per time window (week/month/all) for the compact offline cycler.
// Top VOD + clip per time window (week/month/all) for the compact offline cycler.
ranges,
ranges,
- });
+ };
+ if (_popularCache.size >= 500) _popularCache.clear();
+ _popularCache.set(user.id, { at: Date.now(), data });
+ res.json(data);
} catch (err) {
} catch (err) {
res.status(500).json({ error: 'Failed to get popular content' });
res.status(500).json({ error: 'Failed to get popular content' });
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
+'use strict';
+/**
+ * GET /api/streams/channel/:username/popular (the offline channel's "Most watched"): its three Media reads run in
+ * parallel and the answer is kept for a minute per channel (they ran one after another on every view: p95 0.77 s,
+ * 2026-10-17). A Media read that fails leaves its part empty, not the whole answer.
+ *
+ * node --import ./test/helpers/pg-preload.mjs test/channel-popular-cache.test.js
+ */
+const assert = require('assert');
+const http = require('http');
+
+process.env.NODE_ENV = 'test';
+const quiet = console.log;
+console.log = (...a) => { if (!/^\[/.test(String(a[0]))) quiet(...a); };
+console.warn = () => {};
+
+(async () => {
+ const db = require('../server/db/database');
+ await db.initDb();
+ await db.createUser({ username: 'popstar', email: 'pop@x', password_hash: 'x', display_name: 'popstar', stream_key: 'p'.repeat(32) });
+
+ const media = require('../server/media-client');
+ const lookups = require('../server/media-proxy/lookups');
+ const calls = { vods: 0, clips: 0, ranges: 0 };
+ let inFlight = 0, maxInFlight = 0;
+ const slow = (fn) => async (...a) => {
+ inFlight++; maxInFlight = Math.max(maxInFlight, inFlight);
+ await new Promise((r) => setTimeout(r, 60));
+ inFlight--;
+ return fn(...a);
+ };
+ media.listVods = slow(() => { calls.vods++; return { vods: [{ id: 1, title: 'big night' }] }; });
+ media.listClips = slow(() => { calls.clips++; throw new Error('Media down'); });
+ lookups.topContentRanges = slow(() => { calls.ranges++; return { week: null, month: null, all: null }; });
+
+ const express = require('express');
+ const app = express();
+ app.use('/api/streams', require('../server/streaming/routes'));
+ const server = http.createServer(app).listen(0, '127.0.0.1');
+ await new Promise((r) => server.once('listening', r));
+ const get = async (p) => { const r = await fetch(`http://127.0.0.1:${server.address().port}${p}`); return { status: r.status, json: await r.json() }; };
+
+ const first = await get('/api/streams/channel/popstar/popular');
+ assert.strictEqual(first.status, 200);
+ assert.strictEqual(first.json.vod.title, 'big night');
+ assert.deepStrictEqual(first.json.clips, [], 'a failed Media read leaves its part empty');
+ assert.ok(first.json.ranges, 'the other reads still answer');
+ assert.strictEqual(maxInFlight, 3, 'the three Media reads run in parallel');
+
+ await get('/api/streams/channel/popstar/popular');
+ assert.deepStrictEqual(calls, { vods: 1, clips: 1, ranges: 1 }, 'the second view within a minute is answered from the cache');
+ assert.strictEqual((await get('/api/streams/channel/nobody_here/popular')).status, 404);
+
+ server.close();
+ console.log = quiet;
+ console.log('channel popular: all checks passed');
+ process.exit(0);
+})().catch((e) => { console.error(e); process.exit(1); });