Skip to content

Comments

perf: Fix indexes on singleevent table [DHIS2-20891]#22977

Open
enricocolasante wants to merge 1 commit intomasterfrom
DHIS2-20891-indexes
Open

perf: Fix indexes on singleevent table [DHIS2-20891]#22977
enricocolasante wants to merge 1 commit intomasterfrom
DHIS2-20891-indexes

Conversation

@enricocolasante
Copy link
Contributor

@enricocolasante enricocolasante commented Feb 17, 2026

Before (6 single-column indexes):

  • (status, occurreddate)
  • (occurreddate)
  • (attributeoptioncomboid)
  • (programstageid)
  • (organisationunitid)
  • (deleted, assigneduserid)

After (3 composite indexes, all leading with programstageid):

  • (programstageid, occurreddate)
  • (programstageid, organisationunitid)
  • (programstageid, assigneduserid)

Following up on #22971, the key insight is that single event queries always filter by program stage — this is now explicitly enforced in JdbcSingleEventStore where ev.programstageid = :programstageid is added as a mandatory filter. Since programstageid is always in the WHERE clause, making it the leading column in every composite index means:

  • Every query can use an index scan from the first column. A B-tree index is only efficiently usable when you filter on a left prefix. The old single-column indexes on occurreddate, organisationunitid, or assigneduserid couldn't be combined with the programstageid filter in a single index scan — Postgres would have to do a bitmap AND of two separate index scans or fall back to a sequential scan.
  • Dramatically reduces the working set. The programstageid filter is highly selective (narrows to one program's events). The second column then further narrows within that subset, making range scans (e.g. occurreddate for date range queries) and lookups (e.g.organisationunitid for org unit filtering) very efficient.
  • Fewer indexes = less write overhead. Going from 6 indexes to 3 reduces the cost of every INSERT/UPDATE/DELETE on the singleevent table.
  • The dropped (status, occurreddate) and (attributeoptioncomboid) indexes were either unused or covered by other access paths.

Performance

Compared against 0de04dd (master at time of PR).

baseline:  dhis2/core-pr:22971
candidate: dhis2/core-pr:22977
Request Base p95 Cand p95 Diff
Get first enrollment 11ms 10ms -9%
Get first event 21ms 20ms -5%
Get first event from enrollment 24ms 25ms +4%
Get first page of TEs of program ur1Edk5Oe2n 28ms 28ms +0%
Get first tracked entity 36ms 38ms +6%
Get relationships for first event 4ms 4ms +0%
Get relationships for first tracked entity 6ms 5ms -17%
Get TEs with enrollment status 33ms 34ms +3%
Get tracked entities from events 7ms 6ms -14%
Go to first page of program VBqh0ynB2wv 14ms 13ms -7%
Go to second page of program VBqh0ynB2wv 15ms 13ms -13%
Not found TE by name with like operator 11ms 11ms +0%
Not found TE by national id with eq operator 9ms 8ms -11%
Search events by program stage 10ms 9ms -10%
Search single events assigned to any user in program VBqh0ynB2wv 8ms 7ms -12%
Search single events in date interval in program VBqh0ynB2wv 31ms 29ms -6%
Search single events not assigned to a user in program VBqh0ynB2wv 14ms 12ms -14%
Search TE by name with like operator 27ms 28ms +4%
Search TE by national id with eq operator 10ms 9ms -10%
Request Base p95 Cand p95 Diff
Get first enrollment 27ms 28ms +4%
Get first event 41ms 40ms -2%
Get first event from enrollment 76ms 76ms +0%
Get first page of TEs of program ur1Edk5Oe2n 47ms 47ms +0%
Get first tracked entity 55ms 56ms +2%
Get relationships for first event 4ms 4ms +0%
Get relationships for first tracked entity 5ms 5ms +0%
Get TEs with enrollment status 54ms 54ms +0%
Get tracked entities from events 13ms 13ms +0%
Go to first page of program VBqh0ynB2wv 11ms 11ms +0%
Go to second page of program VBqh0ynB2wv 11ms 11ms +0%
Not found TE by name with like operator 11ms 12ms +9%
Not found TE by national id with eq operator 8ms 8ms +0%
Search events by program stage 20ms 19ms -5%
Search single events assigned to any user in program VBqh0ynB2wv 7ms 7ms +0%
Search single events in date interval in program VBqh0ynB2wv 11ms 11ms +0%
Search single events not assigned to a user in program VBqh0ynB2wv 11ms 11ms +0%
Search TE by name with like operator 43ms 42ms -2%
Search TE by national id with eq operator 9ms 9ms +0%

@sonarqubecloud
Copy link

@enricocolasante enricocolasante marked this pull request as ready for review February 18, 2026 11:02
@enricocolasante enricocolasante requested a review from a team February 18, 2026 11:02
@muilpp muilpp requested a review from a team February 18, 2026 12:47
@teleivo
Copy link
Contributor

teleivo commented Feb 18, 2026

Dramatically reduces the working set. The programstageid filter is highly selective (narrows to one program's events)
The selectivity depends on the actual data.

Why don't we see more of an effect in any of the response times? 1-2ms is not that dramatic 😅 Although definitely good from the write end to remove the overhead!

drop index if exists in_singleevent_organisationunitid;
drop index if exists in_singleevent_deleted_assigneduserid;

-- Composite: serves date range filters, ORDER BY occurreddate, and program-only queries
Copy link
Contributor

Choose a reason for hiding this comment

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

Why this date and not (also) others? I mean it already had an index but why this date vs another?

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