Skip to content

O3-5693: Resolve previousQueueEntry via a column to avoid N+1 - #117

Open
UjjawalPrabhat wants to merge 5 commits into
openmrs:mainfrom
UjjawalPrabhat:O3-5693-queue-entry-overfetch
Open

O3-5693: Resolve previousQueueEntry via a column to avoid N+1#117
UjjawalPrabhat wants to merge 5 commits into
openmrs:mainfrom
UjjawalPrabhat:O3-5693-queue-entry-overfetch

Conversation

@UjjawalPrabhat

@UjjawalPrabhat UjjawalPrabhat commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Summary

The queue REST page resolved previousQueueEntry with one query per row (getPreviousQueueEntry ran a criteria lookup per entry), an N+1 on every load.

This persists the relationship instead: a new previous_queue_entry column on queue_entry, set when the entry is created — directly for a transition, otherwise derived from queueComingFrom — and read straight off the column thereafter. QueueEntry carries @BatchSize(size = 100), so the predecessors for a page of entries load in one round trip rather than one query each. Existing rows are backfilled where the predecessor is unambiguous (MySQL/MariaDB and Postgres changesets).

Behaviour changes

  • An ambiguous predecessor now resolves to null. Previously getPreviousQueueEntry threw IllegalStateException("Multiple previous queue entries found") on read — during list serialization, so it could fail a whole page render.
  • Pre-upgrade rows rely on the backfill, so a database outside MySQL/MariaDB/Postgres keeps null links for existing entries. New writes are unaffected.
  • Undoing a transition clears the voided entry's link rather than leaving it pointing at the re-opened predecessor.
  • Where an entry has no visit, the runtime lookup doesn't constrain on visit (unchanged from the old criteria) while the backfill requires both sides to be null.
  • The FK uses ON DELETE SET NULL, unlike the others in liquibase.xml: purgeQueueEntry hard-deletes, and NO ACTION would make purging any entry that has a successor fail.

Related Issue

O3-5693

Other

Related PRs' here and here.

@sonarqubecloud

Copy link
Copy Markdown

Comment thread api/src/main/java/org/openmrs/module/queue/api/impl/QueueEntryServiceImpl.java Outdated
@dkayiwa

dkayiwa commented Jul 22, 2026

Copy link
Copy Markdown
Member

@claude review

Comment thread api/src/main/java/org/openmrs/module/queue/api/impl/QueueEntryServiceImpl.java Outdated
Comment thread api/src/main/resources/liquibase.xml
@UjjawalPrabhat
UjjawalPrabhat force-pushed the O3-5693-queue-entry-overfetch branch from cb00b2d to 524d556 Compare July 25, 2026 08:42
@jwnasambu

Copy link
Copy Markdown

@dkayiwa, @ibacher though I have approved the PR I would appreciate another pair of eyes before merge.

Comment thread api/src/main/resources/liquibase.xml Outdated
Comment thread api/src/main/java/org/openmrs/module/queue/api/impl/QueueEntryServiceImpl.java Outdated
@sonarqubecloud

Copy link
Copy Markdown

@UjjawalPrabhat
UjjawalPrabhat requested a review from dkayiwa August 11, 2026 10:29
@dkayiwa

dkayiwa commented Aug 11, 2026

Copy link
Copy Markdown
Member

@claude review

@Getter
@ToString
@Entity
@BatchSize(size = 100)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The batch size is load-bearing for the whole point of this change, and nothing in the suite would notice if it went away. Without it, reading previousQueueEntry across a page initializes each proxy on its own select, so the N+1 comes straight back in a different shape, with every test still green and the payload byte-identical. The column itself is well covered by the new tests; this one property isn't covered by anything.

A test alongside the new ones in the integration suite could pin it: turn statistics on (sessionFactory.getStatistics().setStatisticsEnabled(true)), transition two or three entries, flush and clear, then reload the successors and call getPreviousQueueEntry on each, asserting the prepared-statement count goes up once rather than once per entry. Your call whether that's worth the setup, and it doesn't block merging.

Comment thread api/src/main/java/org/openmrs/module/queue/api/QueueEntryService.java Outdated
Comment thread api/src/main/resources/liquibase.xml
Comment thread api/src/main/java/org/openmrs/module/queue/api/QueueEntryService.java Outdated
Conflict in liquibase.xml: main added four queue_entry index changesets at
the same point this branch adds the previous_queue_entry column, FK and
backfills. Kept both, indexes first.

main also dropped @EqualsAndHashCode from QueueEntry when it made the
associations lazy, so the field-level @EqualsAndHashCode.Exclude this
branch put on previousQueueEntry no longer resolves; removed it. The
@ToString.Exclude beside it matches the other lazy associations.

Also splits the previous_queue_entry FK into its own changeset, per
review.
- getPreviousQueueEntry javadoc: note that the visit is only matched when
  Q has one, and simplify the @return wording.
- Unit test: assert the criteria the write-time lookup builds (patient,
  visit, endedOn, queues), not just the resolved result.
- Integration test: flush and clear before asserting undoTransition
  cleared the link, so the assertion reads the database rather than the
  instance it just mutated.
@sonarqubecloud

Copy link
Copy Markdown

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.

4 participants