Skip to content

Add Gatling load tests for the articles API with SLO assertions - #294

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1786454321-gatling-load-tests
Open

devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1786454321-gatling-load-tests

Conversation

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Summary

Adds a Gatling load test for the articles REST API plus PERFORMANCE_REPORT.md with the measured
results. No application code changed.

The simulation lives in a standalone Gradle build (load-tests/, own settings.gradle) so
Gatling's Scala/Netty stack, the extra source set and the plugin's Gradle 8 deprecations stay out of
the application build, which keeps ./gradlew build and the JaCoCo gate untouched. Run it with
./gradlew -p load-tests gatlingRun-io.spring.gatling.ArticlesLoadSimulation.

Design points that aren't obvious from the code:

  • Open workload, one request per virtual userconstantUsersPerSec(30) therefore is 30
    req/sec, and a slow server queues rather than self-throttling (a closed model would silently
    reduce the offered rate and hide saturation).
  • Setup traffic is not measured. Login, read fixtures and warm up run through a plain JDK
    HttpClient (ConduitApiClient) in the simulation constructor, so JWT issuance and JIT warm up
    don't pollute the percentiles.
  • Deletes never eat the seed data. They drain a queue feeder of throwaway articles created
    during setup, sized from the run parameters:
    deletes = rate * durationSeconds * deleteWeight / totalWeight;
    fixtures = ceil(deletes * 1.25) + 20;   // head room, so the feeder can't run dry
  • The mix is a nested randomSwitch: 80 % reads (split 50/50 between GET /articles and
    GET /articles/{slug}), 15 % POST /articles, 5 % DELETE /articles/{slug}. Measured split over
    9,000 requests: 40.0 / 39.6 / 15.2 / 5.3 %.
  • SLOs are Gatling assertions, not just report numbers — p95 < 200 ms (globally and per
    endpoint) and error rate < 1 %, so gatlingRun exits non-zero on a breach and the simulation can
    be dropped into a pipeline as a performance gate as is. Every knob (rate, durationSeconds,
    weights, thresholds, baseUrl, credentials) is a -P property.

Results (30 req/sec, 5 minutes)

PASS — 9,000 requests, 0 failures, global p50 5 ms / p95 9 ms / p99 10 ms, ~20x margin on the
p95 SLO. A capacity probe puts the knee at ~240 req/sec (p95 65 ms); at 300 req/sec throughput stops
tracking the injected rate and latency goes to tens of seconds with refused connections.

PERFORMANCE_REPORT.md has the per-endpoint percentiles, the capacity table, and the optimisation
backlog (the redundant five-way-join countArticle on every list page, the three extra round trips
in ArticleQueryService.fillExtraInfo, missing indexes since V1__create_tables.sql declares
primary keys only, SQLite WAL/write serialisation, and no effective statement timeout —
default-statement-timeout=3000 is 3000 seconds). None are required to meet the SLO, so none were
implemented here.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/67a0af5b5df34bb48b0f89952124d574

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

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.

0 participants