Skip to content

feat(ab-testing): add indexes to experiment-variant entity (#1223) - #1382

Open
devgbmuyiwa wants to merge 1 commit into
rinafcode:mainfrom
devgbmuyiwa:feat/1223-experiment-variant-indexes
Open

feat(ab-testing): add indexes to experiment-variant entity (#1223)#1382
devgbmuyiwa wants to merge 1 commit into
rinafcode:mainfrom
devgbmuyiwa:feat/1223-experiment-variant-indexes

Conversation

@devgbmuyiwa

@devgbmuyiwa devgbmuyiwa commented Aug 29, 2026

Copy link
Copy Markdown

Closes #1223


experiment-variant.entity.ts declared no @Index, so every lookup on experiment_variants forced a sequential scan regardless of table size.

Query-path review (ab-testing.service.ts, experiments/experiment.service.ts, analysis/statistical-analysis.service.ts,
automation/automated-decision.service.ts,
reporting/ab-testing-reports.service.ts) shows every real access to variant data goes through the experiment relation
(experimentRepository.findOne({ relations: [...] })), then filters the loaded array in-memory for the control variant (isControl) or the winning variant (isWinner). There is no direct variantRepository.find({ where }) call on any other column.

Changes:

  • src/ab-testing/entities/experiment-variant.entity.ts: add two class-level composite indexes, @Index(['experiment', 'isControl']) and @Index(['experiment', 'isWinner']), matching that access shape. A plain experiment-only index is intentionally omitted: leftmost-prefix lookup means either composite already serves a bare "all variants for this experiment" query, so a third index would be a redundant duplicate per the issue's acceptance criteria. A standalone index on isControl or isWinner alone would also be poor: both are low-cardinality booleans with no selectivity without the experiment prefix.
  • src/migrations/1802000000000-add-experiment-variant-indexes.ts: new migration creating the same two indexes via CREATE INDEX IF NOT EXISTS ... ("experimentId", "isControl"/"isWinner") (idempotent, matching this repo's established index-migration style, e.g. 1801000000000-add-achievement-indexes.ts), with a down() that drops them. Timestamp/class name follow migration-timestamps.spec.ts's rules (unique timestamp, class name suffixed with it). experimentId is the column name confirmed by TypeORM's default FK-naming convention, verified against an already-applied migration in this codebase (1750000000000-add-gamification-indexes.ts) since this entity has no custom NamingStrategy or explicit @joincolumn.
  • Migrations are auto-discovered via the src/migrations/[0-9]*.{ts,js} glob in src/config/datasource.ts, so no manual registration was needed. synchronize is false there, so the migration — not the @Index decorators — is what actually creates the indexes on a real database; the decorators keep the entity's schema intent documented and in sync.

Validation: node_modules is not installed in this sandbox (only a single stray entry under node_modules/, jest/tsc not resolvable), so npm test, npm run typecheck, and npm run lint could not be run here. Both files were reviewed by hand against this repo's real conventions: the migration's timestamp/class-name were checked against migration-timestamps.spec.ts's actual assertions, the FK column name was cross-checked against a real applied migration rather than assumed, and the entity's decorator/import syntax mirrors user-achievement.entity.ts's established @Index([...]) composite-index pattern in this same codebase.

…#1223)

`experiment-variant.entity.ts` declared no `@Index`, so every lookup on
`experiment_variants` forced a sequential scan regardless of table size.

Query-path review (ab-testing.service.ts, experiments/experiment.service.ts,
analysis/statistical-analysis.service.ts,
automation/automated-decision.service.ts,
reporting/ab-testing-reports.service.ts) shows every real access to variant
data goes through the `experiment` relation
(`experimentRepository.findOne({ relations: [...] })`), then filters the
loaded array in-memory for the control variant (`isControl`) or the winning
variant (`isWinner`). There is no direct `variantRepository.find({ where })`
call on any other column.

Changes:
- src/ab-testing/entities/experiment-variant.entity.ts: add two class-level
  composite indexes, `@Index(['experiment', 'isControl'])` and
  `@Index(['experiment', 'isWinner'])`, matching that access shape. A plain
  `experiment`-only index is intentionally omitted: leftmost-prefix lookup
  means either composite already serves a bare "all variants for this
  experiment" query, so a third index would be a redundant duplicate per
  the issue's acceptance criteria. A standalone index on `isControl` or
  `isWinner` alone would also be poor: both are low-cardinality booleans
  with no selectivity without the `experiment` prefix.
- src/migrations/1802000000000-add-experiment-variant-indexes.ts: new
  migration creating the same two indexes via
  `CREATE INDEX IF NOT EXISTS ... ("experimentId", "isControl"/"isWinner")`
  (idempotent, matching this repo's established index-migration style, e.g.
  1801000000000-add-achievement-indexes.ts), with a `down()` that drops
  them. Timestamp/class name follow migration-timestamps.spec.ts's rules
  (unique timestamp, class name suffixed with it). `experimentId` is the
  column name confirmed by TypeORM's default FK-naming convention, verified
  against an already-applied migration in this codebase
  (1750000000000-add-gamification-indexes.ts) since this entity has no
  custom NamingStrategy or explicit @joincolumn.
- Migrations are auto-discovered via the `src/migrations/[0-9]*.{ts,js}`
  glob in src/config/datasource.ts, so no manual registration was needed.
  `synchronize` is `false` there, so the migration — not the `@Index`
  decorators — is what actually creates the indexes on a real database;
  the decorators keep the entity's schema intent documented and in sync.

Validation: node_modules is not installed in this sandbox (only a single
stray entry under node_modules/, `jest`/`tsc` not resolvable), so
`npm test`, `npm run typecheck`, and `npm run lint` could not be run here.
Both files were reviewed by hand against this repo's real conventions:
the migration's timestamp/class-name were checked against
migration-timestamps.spec.ts's actual assertions, the FK column name was
cross-checked against a real applied migration rather than assumed, and
the entity's decorator/import syntax mirrors user-achievement.entity.ts's
established `@Index([...])` composite-index pattern in this same codebase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented Aug 29, 2026

Copy link
Copy Markdown

@devgbmuyiwa Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Well done on the job done so far!
Kindly fix workflow to pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add database indexes to the experiment-variant entity

2 participants