Skip to content

chore(perf): capture failed-request bodies in tracker perf test#24507

Closed
enricocolasante wants to merge 3 commits into
masterfrom
worktree-tracker-perf-409-diagnosis
Closed

chore(perf): capture failed-request bodies in tracker perf test#24507
enricocolasante wants to merge 3 commits into
masterfrom
worktree-tracker-perf-409-diagnosis

Conversation

@enricocolasante

Copy link
Copy Markdown
Contributor

Summary

The Performance tests compare workflow intermittently fails with a storm of 409 CONFLICT on every POST /api/tracker import (e.g. run 29728194277). When it happens, all 180 import requests are KO for the whole lifetime of the DHIS2 container (warmup and measured run), and the export scenarios then find 0 events.

The failure is environmental / flaky, not a code regression — it has hit both the baseline and the candidate side across different runs. It has stayed undiagnosable because:

  • Gatling's simulation.log records only the status code, not the response body — so the actual 409 payload (the import error) is discarded.
  • The harness does not capture DHIS2 server logs by default (CAPTURE_DHIS2_LOGS is off in the compare workflow).

Changes

  • logback-test.xml — enable Gatling's io.gatling.http.engine.response logger at DEBUG. Gatling dumps the full request + response (URL, headers, body) for failed requests only at this level; green runs have no failures so it is silent and does not perturb the perf measurement. Overridable via -DGATLING_RESPONSE_LOG_LEVEL (TRACE = all requests, WARN = off). This makes the next CI occurrence self-diagnosing.
  • scripts/reproduce-tracker-409.sh — runs the LOAD profile in a loop against a chosen image, preserving each iteration's full console output and stopping on the first failure so the captured 409 body can be inspected.

Next steps

Once merged, re-run the compare workflow (or the repro script) until the storm recurs; the DEBUG dump will reveal the actual import error and let us fix the root cause. This PR only adds instrumentation — it does not attempt a fix.

🤖 Generated with Claude Code

enricocolasante and others added 3 commits July 20, 2026 11:27
The "Performance tests compare" workflow intermittently fails with a storm
of 409 CONFLICT responses on every `POST /api/tracker` import (and, as a
downstream effect, empty exports). The failure is environmental/flaky - it
has hit both the baseline and the candidate side - but it has been
undiagnosable because Gatling's simulation.log records only the status code,
not the response body, and the harness does not capture DHIS2 server logs by
default. So the actual import error (the 409 payload) is lost.

Enable Gatling's `io.gatling.http.engine.response` logger at DEBUG, which
dumps the full request + response (including body) for FAILED requests only.
Green runs have no failures, so this is silent there and does not perturb the
perf measurement. Override via -DGATLING_RESPONSE_LOG_LEVEL. This makes the
next CI occurrence self-diagnosing.

Also add scripts/reproduce-tracker-409.sh: runs the LOAD profile in a loop
against a chosen image, preserving each iteration's full console output and
stopping on the first failure so the captured 409 body can be inspected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Temporary diagnostic instrumentation (NOT for merge) to root-cause the flaky
tracker-import 409 storm where preheat fails to resolve Program/OrgUnit that
demonstrably exist. Prior SQL-log capture showed the failure is in-memory
(Program never issues a uid query), so SQL logs can't see it.

- AbstractSchemaStrategy.queryForIdentifiableObjects: [preheat-diag] logs
  requested-vs-resolved per metadata class (class, idScheme, requested/resolved
  counts, requested + resolved UIDs). Directly shows "class=Program requested=1
  resolved=0" during the storm.
- DefaultQueryService.queryObjects: [query-diag] logs DB-engine result size vs
  in-memory-filter outcome for Program/OrgUnit/TET/ProgramStage, to tell
  "DB returned nothing" apart from "in-memory filter dropped them".
- docker/log4j2.xml: enable DEBUG for the two packages so the lines land in the
  container log (dhis.log) during the perf run.

All gated on isDebugEnabled(); silent unless enabled. Revert once root cause
is found.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The storm hits a DIFFERENT metadata class each time (Program+OrgUnit, then
DataElement, then TEA) and the failing resolution issues NO SQL at all. Widen
[query-diag] to fire on ANY empty result (not just a fixed type set), and log
which branch (PRESET vs DBPLAN), preset size, DB-engine result size, in-memory
filtering, and the planned db/memory queries so we can see where the id-in
filter landed. Diagnostic only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
2 New issues
2 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

@enricocolasante

Copy link
Copy Markdown
Contributor Author

Closing: this branch was diagnostic instrumentation only (Gatling failed-response body logging, [preheat-diag]/[query-diag] logging in the query/preheat path, log4j2 DEBUG config, repro script) used to root-cause the intermittent tracker-import 409 storm. It was never meant to be merged.

Root cause found and fixed in #24508 (thread-safe eager store-map init in JpaCriteriaQueryEngine), which is validated against the performance loop.

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.

1 participant