openvibers/openvibe.media Commits

perf: keep the no-good-copy gauge 60 s (#349)

OP OpenVibers committed 5 days ago · c2509de5eb · 2 files, +60 −2

Changes

ddiff --git a/server/observability.js b/server/observability.js
iindex 78f348d..70f4494 100644
---- a/server/observability.js
++++ b/server/observability.js
@@@ -29,6 +29,23 @@ const protectedSessions = require('./protected-sessions');
const OUTBOX_BACKLOG_LIMIT = 1000;
++// `ready media objects with no good copy` is an anti-join over every ready object and its locations: 56 ms mean,
++// 5,486 calls in 22 h on 2026-11-08 (the /metrics gauge ran it every ~15 s). The metric answers a slow-moving
++// figure, so it is kept for 60 s in production; every call runs elsewhere (tests read their own writes).
++const NO_GOOD_COPY_MS = Math.max(0, Number(process.env.MEDIA_NO_GOOD_COPY_CACHE_MS || (process.env.NODE_ENV === 'production' ? 60_000 : 0)) || 0);
++function kept(read, keepMs = NO_GOOD_COPY_MS, now = () => Date.now()) {
++ if (!(keepMs > 0)) return read;
++ let hold = null;
++ return () => {
++ const t = now();
++ if (hold && (hold.pending || t - hold.at < keepMs)) return hold.pending || Promise.resolve(hold.value);
++ const pending = Promise.resolve().then(read);
++ hold = { at: t, pending };
++ return pending.then((value) => { if (hold && hold.pending === pending) hold = { at: t, value }; return value; },
++ (err) => { if (hold && hold.pending === pending) hold = null; throw err; });
++ };
++}
++
/**
* A count that cannot be answered is unknown (null), never zero: ovhost reads null as unknown and
* holds the restart. The count is bounded, so a slow database cannot make the whole /api/ready
@@@ -78,7 +95,12 @@ function storageDirs(config) {
};
}
++function noGoodCopyReader(db) {
++ return kept(() => copyReport.countNoGoodCopy(db));
++}
++
function domainMetrics(registry, { db, recorder, events }) {
++ const noGoodCopy = noGoodCopyReader(db);
registry.gauge({ name: 'media_recordings_in_progress', help: 'Recordings ffmpeg is writing right now', collect: () => recorder.activeCount() });
const timelineCache = require('./objects/timeline-cache');
registry.gauge({ name: 'media_timeline_local_bytes', help: 'Bytes used by local timeline files, including orphan files', collect: () => timelineCache.status().bytes });
@@@ -107,7 +129,7 @@ function domainMetrics(registry, { db, recorder, events }) {
});
registry.gauge({
name: 'media_objects_no_good_copy', help: 'Ready media objects with no present or unverified copy anywhere (see scripts/no-good-copy-report.js)',
-- collect: async () => await copyReport.countNoGoodCopy(db),
++ collect: async () => await noGoodCopy(),
});
registry.gauge({
name: 'media_verify_last_run_timestamp_seconds', help: 'When the scheduled copy verification last finished a run',
@@@ -226,4 +248,4 @@ function mountReady(app, registry, deps) {
return readiness;
}
--module.exports = { instrument, mountReady, createMediaReadiness, domainMetrics, storageDirs };
++module.exports = { instrument, mountReady, createMediaReadiness, domainMetrics, storageDirs, kept, noGoodCopyReader, NO_GOOD_COPY_MS };
ddiff --git a/test/no-good-copy-cache.test.js b/test/no-good-copy-cache.test.js
nnew file mode 100644
iindex 0000000..3e4de28
---- /dev/null
++++ b/test/no-good-copy-cache.test.js
@@@ -0,0 +1,36 @@
++'use strict';
++/**
++ * The /metrics gauge for ready objects without a good copy ran the anti-join on every scrape (5,486 calls,
++ * 56 ms mean on 2026-11-08). It is now kept for 60 s in production (server/observability.js kept). This proves
++ * two collects inside the window read once, a failed read is not kept, and the keep expires.
++ */
++process.env.MEDIA_NO_GOOD_COPY_CACHE_MS = '60000';
++
++const assert = require('assert');
++const copyReport = require('../server/objects/copy-report');
++const { noGoodCopyReader, kept } = require('../server/observability');
++
++(async () => {
++ let reads = 0;
++ const original = copyReport.countNoGoodCopy;
++ copyReport.countNoGoodCopy = async () => { reads += 1; return 0; };
++ try {
++ const read = noGoodCopyReader({});
++ await read();
++ await read();
++ assert.strictEqual(reads, 1, 'two collects inside the 60 s window read once');
++ } finally {
++ copyReport.countNoGoodCopy = original;
++ }
++
++ let clock = 0;
++ let keptReads = 0;
++ const read = async () => ++keptReads;
++ const k = kept(read, 60_000, () => clock);
++ assert.strictEqual(await k(), 1);
++ clock = 59_000;
++ assert.strictEqual(await k(), 1, 'within the window: kept');
++ clock = 60_000;
++ assert.strictEqual(await k(), 2, 'past the window: read again');
++ console.log('ok - the no-good-copy gauge is kept for 60 s');
++})().catch((err) => { console.error(err); process.exit(1); });