Skip to content

APP-926: optimize sp_morbidity_report_datamart_postprocessing - #980

Open
nullflux wants to merge 17 commits into
rel-7.13from
aw/app-926/optimize-morbidity-datamart-sp
Open

APP-926: optimize sp_morbidity_report_datamart_postprocessing#980
nullflux wants to merge 17 commits into
rel-7.13from
aw/app-926/optimize-morbidity-datamart-sp

Conversation

@nullflux

@nullflux nullflux commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Description

Optimizes sp_morbidity_report_datamart_postprocessing (>48h in KY). Three RDB_MODERN changes to the #MORB_EVENT_INIT build (the plan's worst query):

  1. Covering index on EVENT_METRIC(EVENT_UID) — the plan's own missing-index hint; turns a full-table scan into a seek.
  2. Parse the five UID params once into typed, PK-indexed temp tables probed with EXISTS, replacing seven per-row STRING_SPLIT re-parses.
  3. A #cand pre-filter that scopes the projection to the batch's candidate reports instead of every active report (superset of keys + the original WHERE left unchanged).

On a 5M-row / 50k-report repro: ~9,273 ms → ~114 ms (~82x), output byte-identical.

Related Issue

APP-926

Additional Notes

  • Behavior-preserving by design: CAST (not TRY_CONVERT) keeps the original throw-on-malformed-input behavior; the WHERE is unchanged, so multi-event reports stay correct.
  • Index ships as its own changeset (tables/263-…); the SP migration is runOnChange: true.
  • Reaching <30 ms would require surgery on the #MORB_EVENT_FINAL projection (result-producing logic), left out to keep output guaranteed identical.

Checklist

  • Manageable size
  • Reviewed — clear and documented
  • Documentation — n/a
  • Tests added — 8 DataDrivenUnitTests cases (one per qualifying branch + edges), passing 8/8.

The #MORB_EVENT_INIT build (the SP's worst query per the attached exec plan) is optimized two ways,
both in RDB_MODERN:

1. Covering index IX_EVENT_METRIC_EVENT_UID on EVENT_METRIC(EVENT_UID) INCLUDE(...). The build
   LEFT JOINs EVENT_METRIC on EVENT_UID, but the clustered PK is (EVENT_TYPE, EVENT_UID) so it
   scanned the entire table. This is the plan's own missing-index hint (32.7% impact).
   New migration tables/263-... + changelog entry app-926-add_ix_event_metric_event_uid.

2. Split materialization in the SP. The WHERE had seven OR'd col IN (STRING_SPLIT(@PARAM,',')).
   Each @*_uids list is now parsed once into a typed bigint PK-indexed temp and probed with EXISTS.
   CAST (not TRY_CONVERT) preserves the original's throw-on-malformed-input behavior exactly.

Result on a 5M-EVENT_METRIC / 50k-report repro: full SP ~9,273ms -> ~450ms (~20x); the target
statement ~7,700ms -> ~370ms; EVENT_METRIC logical reads 100,847 -> 828. Output is byte-identical
to the original across 8 scenarios (obs/pat/prov/org/inv/mixed/empty/junk) - see the equivalence
harness under testing-tools/app-926-morbidity-equivalence (AC #2). SP file is runOnChange:true (AC #3).

Further gain (not applied, needs MORBIDITY_REPORT_EVENT 1:1 validation): a UNION-of-seeks pre-filter
would stop processing every ACTIVE report per call and close the last gap to <300ms.
Adds a #cand pre-filter before the #MORB_EVENT_INIT projection: a superset of candidate MORB_RPT_KEYs
collected via seek-driven UNION branches (each anchored on a small #uid_* temp), then INNER JOIN #cand
into the projection. The original WHERE (7 OR'd EXISTS + MORB_RPT_KEY<>1 + RECORD_STATUS='ACTIVE') is
left UNCHANGED, so the per-(MR,MRE)-row filter still decides output -> equivalent even for reports with
multiple events. This stops the SP from seek-joining every active report per call; runtime now scales
with batch size, not total report count or dimension size. Also forces MAXDOP 1 on the #MORB_EVENT_FINAL
build (it was taking a parallel plan burning ~140ms CPU on 251 rows).

#MORB_EVENT_INIT ~370ms -> 16.6ms; full SP ~450ms -> ~114ms (~82x from the 9,273ms baseline).

Equivalence harness extended to a 9th scenario: a report with two MORBIDITY_REPORT_EVENT rows where only
one matches the filter (the case a key-grain rewrite would break). All 9 scenarios byte-identical
(orig vs optimized, full-row EXCEPT both directions), determinism check clean.

Remaining ~114ms is dominated by #MORB_EVENT_FINAL (~45ms, intrinsic 150-col projection CPU, already
batch-scoped); reaching 30ms would require projection-logic surgery with its own equivalence risk.
Converts the equivalence scenarios into the repo's golden-file unit-test framework
(src/test/resources/testData/unit/, run by DataDrivenUnitTests). 8 cases, one per
qualifying branch plus edges: obs, pat, prov, org, inv, mixed, multievent, empty.

Each case: setup.sql seeds a tiny RDB_MODERN fixture (distinct 92xxxxx key range per case)
and EXECs sp_morbidity_report_datamart_postprocessing; query.sql selects the resulting
MORBIDITY_REPORT_DATAMART rows; expected.json is the golden. Goldens were captured and
verified to match the ORIGINAL (pre-optimization) SP, so they assert behavior, not just the
current output. The multievent case (one report, two MORBIDITY_REPORT_EVENT rows, only one
matching) guards the batch-scoping equivalence. The empty case asserts COUNT=0 (the runner
requires a row-returning query). Full suite passes 8/8.

These are the forward-looking CI regression guards; the original-vs-optimized diff harness
remains under testing-tools/app-926-morbidity-equivalence for one-off certification + perf.
@nullflux
nullflux requested a review from a team as a code owner August 5, 2026 14:53
nullflux and others added 2 commits August 5, 2026 14:56
The forward-looking regression coverage lives in the DataDrivenUnitTests cases
(testData/unit/app926_morbidity_datamart_*). The original-vs-optimized comparison harness
was a one-time certification aid and is no longer needed in the repo.
@nullflux
nullflux force-pushed the aw/app-926/optimize-morbidity-datamart-sp branch from 93af69c to f755dd0 Compare August 5, 2026 18:39
…litting @obs_uids in the inactive lookup step.
…YS. This targets the later join on LAB_RPT_LOCAL_ID in the lab results step.
…ning access pattern (group by MORBIDITY_REPORT_KEY + joins filtered by row_num).
…n that groups by MORBIDITY_REPORT_KEY and joins by row_num.
…ned with the update intent for existing target rows only (src.DML_IND = 'U').
ericnagel
ericnagel previously approved these changes Aug 6, 2026
…ze-morbidity-datamart-sp

# Conflicts:
#	reporting-pipeline-service/src/main/resources/db/changelog/migrations/v7.13/rdb/rdb.changelog-7.13.yaml
…ze-morbidity-datamart-sp

# Conflicts:
#	reporting-pipeline-service/src/main/resources/db/changelog/migrations/v7.13/rdb/rdb.changelog-7.13.yaml
nullflux and others added 3 commits August 10, 2026 20:34
…ze-morbidity-datamart-sp

# Conflicts:
#	reporting-pipeline-service/src/main/resources/db/changelog/migrations/v7.13/rdb/rdb.changelog-7.13.yaml
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.

3 participants