openvibers/openvibe.reviews Commits

perf: partial index for the rejected review outbox count (#237)

OP OpenVibers committed 5 days ago · d1a5b80e04 · 2 files, +41 −0

Changes

ddiff --git a/migrations/0009_review_outbox_rejected_index.sql b/migrations/0009_review_outbox_rejected_index.sql
nnew file mode 100644
iindex 0000000..19e3cf3
---- /dev/null
++++ b/migrations/0009_review_outbox_rejected_index.sql
@@@ -0,0 +1,7 @@
++-- phase: expand
++-- no-transaction
++-- openvibe-sdk's outbox.status().rejected reads the whole outbox for its count
++-- (SELECT count(*) FROM review_event_outbox WHERE rejected_at IS NOT NULL): 92 ms mean on 2026-11-08.
++-- 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 review_event_outbox_rejected ON review_event_outbox (rejected_at) WHERE rejected_at IS NOT NULL;
ddiff --git a/test/outbox-rejected-index.test.js b/test/outbox-rejected-index.test.js
nnew file mode 100644
iindex 0000000..de9e9ed
---- /dev/null
++++ b/test/outbox-rejected-index.test.js
@@@ -0,0 +1,34 @@
++'use strict';
++/**
++ * The rejected-events count on /api/ready and the metrics gauges (openvibe-sdk outbox.status()) walks the
++ * whole review outbox without an index: 92 ms mean on the 2026-11-08 production read. Migration 0009 adds
++ * a partial index over the rejected rows only; this proves the count can use it and still answers the same.
++ */
++const assert = require('assert');
++const { testDb } = require('./db');
++
++(async () => {
++ const { db } = await testDb();
++ try {
++ const indexes = await db.prepare(`SELECT indexname FROM pg_indexes
++ WHERE schemaname = current_schema() AND tablename = 'review_event_outbox'`).all();
++ assert.ok(indexes.some((r) => r.indexname === 'review_event_outbox_rejected'), 'migration 0009 created the partial index');
++
++ const now = Date.now();
++ await db.prepare(`INSERT INTO review_event_outbox (event_id, envelope, created_at, next_attempt_at) VALUES
++ ('evt_sent', '{}', ?, ?), ('evt_rejected', '{}', ?, ?)`).run(now, now, now, now);
++ await db.prepare('UPDATE review_event_outbox SET sent_at = ? WHERE event_id = ?').run(now, 'evt_sent');
++ await db.prepare('UPDATE review_event_outbox SET rejected_at = ? WHERE event_id = ?').run(now, 'evt_rejected');
++
++ const count = await db.prepare('SELECT count(*) AS n FROM review_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 review_event_outbox WHERE rejected_at IS NOT NULL').all())
++ .map((r) => r['QUERY PLAN']).join('\n');
++ assert.match(plan, /review_event_outbox_rejected/, plan);
++ } finally {
++ await db.close();
++ }
++ console.log('ok - the rejected outbox count is an index-only scan over review_event_outbox_rejected');
++})().catch((err) => { console.error(err); process.exit(1); });