vergabe-teilnahme/docs/synthetic-pilot.md
tegwick 11f7c8ae83 feat: measure configurable synthetic pilot workload in isolated PostgreSQL runtime
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
2026-10-19 14:54:19 +02:00

4.7 KiB

Provisional synthetic pilot workload

The owner implementation is scripts/synthetic_pilot.py, configured by config/synthetic-pilot.json. It reuses the application routes/forms exercised by existing workflow tests. There was no existing performance driver in this repository or railiance-apps. The driver uses real loopback HTTP, cookie sessions, CSRF, Gunicorn and a fresh PostgreSQL 16 container; it never points to an existing service/database. No live credentials, customer records or publication are needed.

Run from the application repository with its installed locked environment and cached postgres:16-alpine Docker image:

.venv/bin/python scripts/test_synthetic_pilot.py
.venv/bin/python scripts/synthetic_pilot.py \
  --config config/synthetic-pilot.json --output /tmp/vergabe-sizing.json

The container uses a unique name, random loopback database port and disposable volume; cleanup removes only that container and its volume. Gunicorn binds a random loopback port. Database, media and issue storage are isolated; temporary passwords exist only for generated local users. The local settings disable SSO; this measures local password login, not the invited deployment's identity flow. No seed_dev command or existing PostgreSQL volume is reused.

Adjustable assumptions

Defaults are two users, two synchronous Gunicorn workers, 100 initial tenders, 180 seconds of ordinary work after 30 seconds idle, and 0.2 seconds think time per cycle. Each cycle creates a tender, lot and task, and reads list, search and detail pages; it uploads then downloads one 1 MiB synthetic PDF. Thus the corpus grows during the run. This is an explicit provisional operation mix, not observed customer demand. iterations is a per-user safety ceiling; duration normally ends the run first. User/worker count, duration, idle, iteration ceiling, corpus, think time, document size, HTTP timeout and ordinary p95 threshold are tunable. The PDF bytes are a transfer fixture, not a claim of document parser fidelity.

Pass requires all expected operations, ordinary p95 <= 2 seconds and zero failed ordinary/transfer requests. Redirects are not automatically followed: rejected login redirects cannot count as successful data-page responses. Document transfer samples are reported separately. Ordinary samples include the login page/login; fixture creation/migration and readiness are outside the measured workload.

Coverage and interpretation

The result retains individual status/latency/response-size records, phase-tagged 0.5-second Gunicorn process CPU-time/RSS samples, separate interval Docker database stats, driver/config/application source digests, runtime versions, image identity, hardware and UTC boundaries. App RSS sums process resident sets and can double-count shared pages; CPU reflects this host and has no app quota. Database snapshots are separate demand, not allocated to the app by inference. A shared deployment database requires a baseline and attribution before using these observations for capacity.

Startup is readiness-checked, not timed. Idle and fixed-think-time ordinary phases are measured; restart and a distinct burst phase are not. Ingress/TLS/browser/SSO, production quotas, throttling and shared services are unmeasured. Local success does not establish human acceptance or prove the existing 60m production request. Revisit when user count, corpus, document sizes, workers, image, database, hardware or quota changes, and after an owner-approved target-runtime observation window. The existing VERGABE-WP-0019 / RAPPS-WP-0014 human acceptance remains separate.

Provisional allocation calculation

config/synthetic-sizing-margins.json keeps safety factors tunable. After a complete passing run, derive a candidate envelope without applying it:

.venv/bin/python scripts/summarize_synthetic_sizing.py /tmp/vergabe-sizing.json \
  --margins config/synthetic-sizing-margins.json --output /tmp/vergabe-sizing-envelope.json

CPU request is measured aggregate ordinary p95 times 1.5, rounded up to 25m; CPU limit is observed sample peak times 1.5, rounded up to 100m. Memory request is peak summed RSS times 1.25 and limit times 1.5, each rounded up to 64Mi. These are conservative test candidates for this configuration, not measured production requirements. The normalized per-worker CPU value divides aggregate by configured workers; it is not a claim that worker load is equal. No database capacity is folded into the app recommendation. No resource setting is applied by this tool.

The configured cycle creates rather than edits tender/lot/task records. Editing, startup timing, restart, a distinct burst and target-platform quota/throttling remain coverage gaps before full pilot sizing acceptance.