Run B: the pre-built image halves the heap floor and turns S7's silent failure loud
- Upgrade
- 26.0.0 → 26.7.1
- Scale
- 1,000 users, typical, baseline-26.0.0-typical-1k
- Database
- PostgreSQL 16, single node, container
- Topology
- single container
- Host
- Hetzner CCX33 (fsn1) — 8 vCPU dedicated, 30 GB RAM, local NVMe
- Condition
- routine — no adverse condition applied
Why this run existed
docker-compose.yml says, in a comment, why the lab does not use --optimized:
start --optimized is valid, but when Keycloak detects a config change it re-runs
the build step with the same argv and build rejects --optimized. The supported
route is to run kc.sh build at image build time. Nobody had done it, which
left the lab's single biggest divergence from a production deployment untested —
and left S5's published heap floor carrying a caveat.
Environment
| Field | Value |
|---|---|
| Keycloak from → to | 26.0.0 (stock image) → 26.7.1 (pre-built image) |
| Image | kclab/keycloak-optimized:26.7.1, built by fixtures/optimized/Dockerfile — kc.sh build with KC_DB=postgres, health and metrics on; 772 MB against the stock 766 MB |
| Start mode | start --optimized --http-enabled=true |
| Database | PostgreSQL 16, single node, container |
| Postgres tuning | stock |
| Dataset scale | 1,000 users, typical, baseline-26.0.0-typical-1k |
| Topology | single container |
| Host | Hetzner CCX33 (fsn1) — 8 vCPU dedicated, 30 GB RAM, local NVMe |
| JVM heap | -Xms1g -Xmx4g except in B2, where it is the variable |
| Lab change | fixtures/optimized/Dockerfile, bin/build-optimized.sh, docker-compose.optimized.yml, docker-compose.optimized-noprov.yml |
Outcome
-
Migration completes under
--optimized: 211 rows, model 26.7.1 - Time to ready 17s → 11s at 1k users
-
Heap floor re-bisected:
(64m, 96m] - Provider handling characterised in four configurations
Verification
| Claim | Primary source | Checked |
|---|---|---|
| The optimized image runs the migration normally | 211 changelog rows, migration_model 26.7.1 | ☑ |
| Upgrade elapsed 17s → 11s | upgrade.sh output, same baseline both runs | ☑ |
| The saving is the build step, not the migration | Quarkus started in and Bootstrap identical to within 0.03s | ☑ |
Heap floor is (64m, 96m] on the optimized image | 96m READY, 64m FAILED with an OOM line | ☑ |
S5's floor of (128m, 160m] was the build step | 128m FAILED stock, READY optimized | ☑ |
| A runtime-only provider jar prevents startup | B3a, message quoted | ☑ |
| A providers bind mount shadows the image's own | B3b-i vs B3b-ii; ls inside the container empty vs populated | ☑ |
| The provider registers when built in and unmounted | /admin/serverinfo lists lab-event-listener | ☑ |
Heap floor at 2M users under --optimized | not tested — B2 ran at 1k. S5 showed the stock floor is scale-independent; that is an assumption here, not a measurement | ☐ |
| Whether a broken provider built into an optimized image still fails lazily | not tested — only the working jar was built in | ☐ |
--optimized across a version boundary (build at 26.0.0, start at 26.7.1) | not tested — the optimized image existed only for 26.7.1, so the 26.0.0 side of B1 ran stock | ☐ |