openvibers/openvibe.media Commits
perf: keep the no-good-copy gauge 60 s (#349)
Changes
@@@ -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 };nnew file mode 100644@@@ -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); });
@@ -29,6 +29,23 @@ const protectedSessions = require('./protected-sessions');
@@ -29,6 +29,23 @@ const protectedSessions = require('./protected-sessions');
const OUTBOX_BACKLOG_LIMIT = 1000;
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
* 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
* holds the restart. The count is bounded, so a slow database cannot make the whole /api/ready
@@ -78,7 +95,12 @@ function storageDirs(config) {
@@ -78,7 +95,12 @@ function storageDirs(config) {
};
};
}
}
+function noGoodCopyReader(db) {
+ return kept(() => copyReport.countNoGoodCopy(db));
+}
+
function domainMetrics(registry, { db, recorder, events }) {
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() });
registry.gauge({ name: 'media_recordings_in_progress', help: 'Recordings ffmpeg is writing right now', collect: () => recorder.activeCount() });
const timelineCache = require('./objects/timeline-cache');
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 });
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 }) {
@@ -107,7 +129,7 @@ function domainMetrics(registry, { db, recorder, events }) {
});
});
registry.gauge({
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)',
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({
registry.gauge({
name: 'media_verify_last_run_timestamp_seconds', help: 'When the scheduled copy verification last finished a run',
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) {
@@ -226,4 +248,4 @@ function mountReady(app, registry, deps) {
return readiness;
return readiness;
}
}
-module.exports = { instrument, mountReady, createMediaReadiness, domainMetrics, storageDirs };
+module.exports = { instrument, mountReady, createMediaReadiness, domainMetrics, storageDirs, kept, noGoodCopyReader, NO_GOOD_COPY_MS };
new file mode 100644
new file mode 100644
@@ -0,0 +1,36 @@
@@ -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); });