-
Notifications
You must be signed in to change notification settings - Fork 1
perf: add authenticated HTTP concurrency harness #626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # Authenticated HTTP concurrency evidence | ||
|
|
||
| LineageWeave provides `scripts/k6_http_e2e.js` to measure the real Compose | ||
| HTTP boundary while a synthetic Global Ask job is queued or running. It logs | ||
| in through the seeded Keycloak realm, submits one non-identifying question to | ||
| `POST /api/ask`, then drives concurrent authenticated requests to posts, | ||
| Event Lineage, and the Ask-status projection. | ||
|
|
||
| This implements the measurement side of ADR 0204's resource-release decision: | ||
| provider work is asynchronous, so ordinary readers should remain observable | ||
| while the worker runs. The harness does not prove why a slow observation is | ||
| slow. Correlate a run with backend/PostgreSQL/Valkey/orchestrator telemetry and | ||
| `pg_stat_activity` before naming a bottleneck. | ||
|
|
||
| ## Run | ||
|
|
||
| Start and seed the synthetic stack, then supply the concurrency and observation | ||
| window that match the environment under review: | ||
|
|
||
| ```bash | ||
| make up | ||
| KEYCLOAK_ADMIN_PASSWORD=admin_dev_only make seed | ||
| k6 run --vus <measured-concurrency> --duration <observation-window> \ | ||
| scripts/k6_http_e2e.js | ||
| ``` | ||
|
|
||
| Pass `BACKEND_URL`, `KEYCLOAK_URL`, `KEYCLOAK_REALM`, `KEYCLOAK_CLIENT_ID`, | ||
| `K6_USERNAME`, and `K6_PASSWORD` with k6's `-e NAME=value` option to point the | ||
| harness at another authorized synthetic environment. Never run repository | ||
| performance evidence against identifying production records. | ||
|
|
||
| ## Interpret the output | ||
|
|
||
| k6 reports observed request counts, failure rate, and duration distributions. | ||
| The custom metrics separate: | ||
|
|
||
| - `lineageweave_ask_enqueue_duration`: time to persist and acknowledge the job; | ||
| - `lineageweave_read_duration{endpoint:posts|lineage}`: ordinary reader paths; | ||
| - `lineageweave_ask_poll_duration`: owner-scoped status polling. | ||
|
|
||
| There are deliberately no pass/fail thresholds. A latency or concurrency SLO | ||
| requires a named deployment, representative workload, capacity evidence, and | ||
| product approval; CI runner capacity is not that evidence. Store the raw k6 | ||
| output with the environment's CPU, memory, database pool, worker concurrency, | ||
| dataset counts, exact Git SHA, and observation time. Do not promote one laptop | ||
| or shared-runner result to a product guarantee. | ||
|
|
||
| Figma and screenshot review do not apply: this is a non-UI HTTP load harness. | ||
|
|
||
| ## Current-main verification record | ||
|
|
||
| On 2026-08-25, a worktree based on protected-main commit `48f013a2` passed | ||
| `k6 inspect` for this script. A fresh Compose project did not reach an | ||
| application-ready state: the build was stopped | ||
| after backend dependency synchronization alone had reached 225.5 seconds and | ||
| was still incomplete; other observed BuildKit metadata, copy, and image-export | ||
| steps ranged up to 292.3 seconds. No containers were running afterward, so no | ||
| HTTP latency distribution was produced and no application bottleneck is | ||
| claimed. This is local build-environment evidence only. Re-run the command | ||
| above on an application-ready stack to obtain the product measurement. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| /** | ||
| * Measure authenticated HTTP responsiveness while one synthetic Ask job runs. | ||
| * | ||
| * This is an observation harness, not a release gate: it defines no latency, | ||
| * error-rate, or throughput threshold. The operator supplies concurrency and | ||
| * duration for the environment being measured. | ||
| */ | ||
|
|
||
| import http from "k6/http"; | ||
| import { check, fail } from "k6"; | ||
| import { Trend } from "k6/metrics"; | ||
|
|
||
| const backendUrl = (__ENV.BACKEND_URL || "http://localhost:18420").replace(/\/$/, ""); | ||
| const keycloakUrl = (__ENV.KEYCLOAK_URL || "http://localhost:18080").replace(/\/$/, ""); | ||
| const realm = __ENV.KEYCLOAK_REALM || "lineageweave-demo"; | ||
| const clientId = __ENV.KEYCLOAK_CLIENT_ID || "lineageweave-frontend"; | ||
| const username = __ENV.K6_USERNAME || "demo.analyst"; | ||
| const password = __ENV.K6_PASSWORD || "lineageweave-demo-only"; | ||
|
|
||
| const askEnqueueDuration = new Trend("lineageweave_ask_enqueue_duration", true); | ||
| const readDuration = new Trend("lineageweave_read_duration", true); | ||
| const askPollDuration = new Trend("lineageweave_ask_poll_duration", true); | ||
|
|
||
| export function setup() { | ||
| const tokenResponse = http.post( | ||
| `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`, | ||
| { | ||
| grant_type: "password", | ||
| client_id: clientId, | ||
| username, | ||
| password, | ||
| }, | ||
| { tags: { endpoint: "oidc_token" } }, | ||
| ); | ||
| if (tokenResponse.status !== 200) { | ||
| fail(`synthetic OIDC login failed with HTTP ${tokenResponse.status}`); | ||
| } | ||
|
|
||
| const token = tokenResponse.json("access_token"); | ||
| const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }; | ||
| const submitted = http.post( | ||
| `${backendUrl}/api/ask`, | ||
| JSON.stringify({ question: "Summarize the synthetic demo lineage evidence." }), | ||
| { headers, tags: { endpoint: "ask_enqueue" } }, | ||
| ); | ||
| askEnqueueDuration.add(submitted.timings.duration); | ||
| if (submitted.status !== 202) { | ||
| fail(`synthetic Ask enqueue failed with HTTP ${submitted.status}: ${submitted.body}`); | ||
| } | ||
| return { token, askJobId: submitted.json("ask_job_id") }; | ||
| } | ||
|
|
||
| export default function (data) { | ||
| const params = { headers: { Authorization: `Bearer ${data.token}` } }; | ||
|
seonghobae marked this conversation as resolved.
|
||
| const responses = http.batch([ | ||
| ["GET", `${backendUrl}/api/posts`, null, { ...params, tags: { endpoint: "posts" } }], | ||
| ["GET", `${backendUrl}/api/lineage`, null, { ...params, tags: { endpoint: "lineage" } }], | ||
| [ | ||
| "GET", | ||
| `${backendUrl}/api/ask/jobs/${data.askJobId}`, | ||
| null, | ||
| { ...params, tags: { endpoint: "ask_poll" } }, | ||
| ], | ||
| ]); | ||
|
|
||
| readDuration.add(responses[0].timings.duration, { endpoint: "posts" }); | ||
| readDuration.add(responses[1].timings.duration, { endpoint: "lineage" }); | ||
| askPollDuration.add(responses[2].timings.duration); | ||
| check(responses[0], { "posts read succeeds": (response) => response.status === 200 }); | ||
| check(responses[1], { "lineage read succeeds": (response) => response.status === 200 }); | ||
| check(responses[2], { "Ask poll succeeds": (response) => response.status === 200 }); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.