openvibers/openvibe.deals Commits
perf: partial index for the rejected outbox count (#253)
Changes
nnew file mode 100644@@@ -0,0 +1,7 @@++-- phase: expand++-- no-transaction++-- openvibe-sdk's outbox.status().rejected reads the whole outbox for its count++-- (SELECT count(*) FROM event_outbox WHERE rejected_at IS NOT NULL): 625 ms and 79,944++-- pages on 2026-11-08, with 0 rejected rows. A partial index holds only the rejected++-- rows, so the same count is an index-only scan over an index that is usually empty.++CREATE INDEX CONCURRENTLY IF NOT EXISTS event_outbox_rejected ON event_outbox (rejected_at) WHERE rejected_at IS NOT NULL;nnew file mode 100644@@@ -0,0 +1,35 @@++'use strict';++/**++ * The rejected-events count on /api/ready and the metrics gauges (openvibe-sdk outbox.status()) walks the++ * whole outbox without an index: 625 ms and 79,944 shared pages on the 2026-11-08 production read, for a++ * count that was zero. Migration 0014 adds a partial index over the rejected rows only; this proves the++ * count can use it (an index-only scan) and still answers the same.++ */++const assert = require('assert');++const { testDb } = require('./helpers/db');++++(async () => {++ const { db } = await testDb();++ try {++ const indexes = await db.prepare(`SELECT indexname FROM pg_indexes++ WHERE schemaname = current_schema() AND tablename = 'event_outbox'`).all();++ assert.ok(indexes.some((r) => r.indexname === 'event_outbox_rejected'), 'migration 0014 created the partial index');++++ const now = Date.now();++ await db.prepare(`INSERT INTO event_outbox (event_id, envelope, created_at, next_attempt_at) VALUES++ ('evt_sent', '{}', ?, ?), ('evt_rejected', '{}', ?, ?)`).run(now, now, now, now);++ await db.prepare('UPDATE event_outbox SET sent_at = ? WHERE event_id = ?').run(now, 'evt_sent');++ await db.prepare('UPDATE event_outbox SET rejected_at = ? WHERE event_id = ?').run(now, 'evt_rejected');++++ const count = await db.prepare('SELECT count(*) AS n FROM event_outbox WHERE rejected_at IS NOT NULL').get();++ assert.strictEqual(Number(count.n), 1, 'the count still sees the one rejected event');++++ await db.query('SET enable_seqscan = off');++ const plan = (await db.prepare('EXPLAIN SELECT count(*) FROM event_outbox WHERE rejected_at IS NOT NULL').all())++ .map((r) => r['QUERY PLAN']).join('\n');++ assert.match(plan, /event_outbox_rejected/, plan);++ } finally {++ await db.close();++ }++ console.log('ok - the rejected outbox count is an index-only scan over event_outbox_rejected');++})().catch((err) => { console.error(err); process.exit(1); });
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+-- phase: expand
+-- no-transaction
+-- openvibe-sdk's outbox.status().rejected reads the whole outbox for its count
+-- (SELECT count(*) FROM event_outbox WHERE rejected_at IS NOT NULL): 625 ms and 79,944
+-- pages on 2026-11-08, with 0 rejected rows. A partial index holds only the rejected
+-- rows, so the same count is an index-only scan over an index that is usually empty.
+CREATE INDEX CONCURRENTLY IF NOT EXISTS event_outbox_rejected ON event_outbox (rejected_at) WHERE rejected_at IS NOT NULL;
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+'use strict';
+/**
+ * The rejected-events count on /api/ready and the metrics gauges (openvibe-sdk outbox.status()) walks the
+ * whole outbox without an index: 625 ms and 79,944 shared pages on the 2026-11-08 production read, for a
+ * count that was zero. Migration 0014 adds a partial index over the rejected rows only; this proves the
+ * count can use it (an index-only scan) and still answers the same.
+ */
+const assert = require('assert');
+const { testDb } = require('./helpers/db');
+
+(async () => {
+ const { db } = await testDb();
+ try {
+ const indexes = await db.prepare(`SELECT indexname FROM pg_indexes
+ WHERE schemaname = current_schema() AND tablename = 'event_outbox'`).all();
+ assert.ok(indexes.some((r) => r.indexname === 'event_outbox_rejected'), 'migration 0014 created the partial index');
+
+ const now = Date.now();
+ await db.prepare(`INSERT INTO event_outbox (event_id, envelope, created_at, next_attempt_at) VALUES
+ ('evt_sent', '{}', ?, ?), ('evt_rejected', '{}', ?, ?)`).run(now, now, now, now);
+ await db.prepare('UPDATE event_outbox SET sent_at = ? WHERE event_id = ?').run(now, 'evt_sent');
+ await db.prepare('UPDATE event_outbox SET rejected_at = ? WHERE event_id = ?').run(now, 'evt_rejected');
+
+ const count = await db.prepare('SELECT count(*) AS n FROM event_outbox WHERE rejected_at IS NOT NULL').get();
+ assert.strictEqual(Number(count.n), 1, 'the count still sees the one rejected event');
+
+ await db.query('SET enable_seqscan = off');
+ const plan = (await db.prepare('EXPLAIN SELECT count(*) FROM event_outbox WHERE rejected_at IS NOT NULL').all())
+ .map((r) => r['QUERY PLAN']).join('\n');
+ assert.match(plan, /event_outbox_rejected/, plan);
+ } finally {
+ await db.close();
+ }
+ console.log('ok - the rejected outbox count is an index-only scan over event_outbox_rejected');
+})().catch((err) => { console.error(err); process.exit(1); });