openvibers/openvibe.reviews Commits

The home's 'with data' list reads from the signals to the entities: 178 ms to 2.5 ms (#195)

OP OpenVibers committed 11 days ago · b13bad287b · 2 files, +48 −3

entitiesWithData ran an EXISTS subquery for each of the 6,921 active
entities to find the 19 with data. It now unions the active signals'
entities, the entities they were merged into and the published summaries
first, then joins the active entities. The same 19 rows on production.

Changes

ddiff --git a/server/reviews/service.js b/server/reviews/service.js
iindex 8b18195..ec6fb47 100644
---- a/server/reviews/service.js
++++ b/server/reviews/service.js
@@@ -2050,9 +2050,14 @@ function createReviewsService({ db, stores, outbox, config, now = () => Date.now
/** Entities with at least one live signal or a published summary (the home page). */
async entitiesWithData(limit = 60) {
-- return (await Promise.all((await db.prepare(`SELECT e.* FROM review_entities e WHERE e.state = 'active' AND (
-- EXISTS (SELECT 1 FROM review_signals s JOIN review_entities m ON m.id = s.entity_id WHERE s.status = 'active' AND (m.id = e.id OR m.merged_into = e.id))
-- OR EXISTS (SELECT 1 FROM review_summaries u WHERE u.entity_id = e.id AND u.state = 'published'))
++ // From the few entities that have data to the entities, not the other way round: the EXISTS form ran its
++ // subquery once per active entity (6,921 on 2026-11-02, 178 ms of the home); this one reads the active
++ // signals (an entity's own, or one merged into it) and published summaries first (2.5 ms, the same rows).
++ return (await Promise.all((await db.prepare(`WITH has AS (
++ SELECT s.entity_id AS id FROM review_signals s WHERE s.status = 'active'
++ UNION SELECT m.merged_into FROM review_signals s JOIN review_entities m ON m.id = s.entity_id WHERE s.status = 'active' AND m.merged_into IS NOT NULL
++ UNION SELECT u.entity_id FROM review_summaries u WHERE u.state = 'published')
++ SELECT e.* FROM review_entities e JOIN has h ON h.id = e.id WHERE e.state = 'active'
ORDER BY e.updated_at DESC LIMIT ?`).all(limit)).map(async (e) => ({ entity: e, aggregate: aggregateView(await q.lastAggregate.get(e.id)) }))));
},
ddiff --git a/test/entities-with-data.test.js b/test/entities-with-data.test.js
nnew file mode 100644
iindex 0000000..5bd5d49
---- /dev/null
++++ b/test/entities-with-data.test.js
@@@ -0,0 +1,40 @@
++'use strict';
++/**
++ * The home's "with data" list (svc.entitiesWithData): every active entity with an active signal of its own, or one on
++ * an entity merged into it, or a published summary; none without; newest change first. It reads from the signals and
++ * summaries to the entities (2.5 ms on production), not a subquery per active entity (178 ms, 2026-11-02).
++ */
++const assert = require('assert');
++const { boot, suite, steamItem, importItem, createEntity, req, editorToken } = require('./helpers');
++
++const t = suite('entities-with-data');
++
++t('the entities with signals, merged signals or a published summary; none without', async () => {
++ const h = await boot();
++ try {
++ const rated = await createEntity(h, { name: 'Portal 2', kind: 'game', aliases: [{ type: 'source', value: 'steam-reviews-portal-2' }] });
++ const dup = await createEntity(h, { name: 'Portal Two', kind: 'game', aliases: [{ type: 'source', value: 'steam-reviews-portal-two-dup' }] });
++ const keeper = await createEntity(h, { name: 'Portal (the keeper)', kind: 'game' });
++ const bare = await createEntity(h, { name: 'No data yet', kind: 'game' });
++ const summarised = await createEntity(h, { name: 'Summarised', kind: 'game' });
++ await importItem(h, steamItem({ votedUp: true }));
++ await importItem(h, steamItem({ votedUp: false, source: 'steam-reviews-portal-two-dup' }));
++ // The duplicate merges into the keeper: its signal now counts for the keeper.
++ const m = await req(h, 'POST', `/api/v1/entities/${dup.slug}/merge`, { token: editorToken(), body: { into: keeper.slug, note: 'duplicate' } });
++ assert.ok(m.status < 300, m.text);
++ await h.db.prepare("INSERT INTO review_summaries (id, entity_id, state, created_at, updated_at) VALUES (?, ?, 'published', ?, ?)").run('sum_entities_with_data', summarised.id, Date.now(), Date.now());
++ const got = (await h.svc.entitiesWithData(60)).map((x) => x.entity.id);
++ assert.ok(got.includes(rated.id), 'its own active signal');
++ assert.ok(got.includes(keeper.id), 'a signal on an entity merged into it');
++ assert.ok(!got.includes(dup.id), 'the merged entity itself is not active');
++ assert.ok(!got.includes(bare.id), 'nothing without data');
++ assert.ok(got.includes(summarised.id), 'a published summary');
++ const rows = await h.db.prepare('SELECT id, updated_at FROM review_entities WHERE id = ANY(?)').all(got);
++ const at = new Map(rows.map((r) => [r.id, String(r.updated_at)]));
++ for (let i = 1; i < got.length; i++) assert.ok(at.get(got[i - 1]) >= at.get(got[i]), 'newest change first');
++ assert.ok((await h.svc.entitiesWithData(1)).length === 1, 'the limit holds');
++ } finally {
++ await h.stop();
++ }
++});
++