enhancement: One-shot bootstrap container for startup seeders (move out of API cold path) #131

Closed
opened 2026-05-12 23:56:05 +00:00 by spikerj · 3 comments
Owner

Summary

SpikerSoft.Api currently runs multiple blocking startup seed operations synchronously inside Program.cs (terms, areas of interest, postal codes, positions, lesson catalog hydration). Each API replica repeats this work unless idempotency shields it everywhere — costing cold-start latency and tying up CPUs on constrained hosts (edge / Jetson) before the pipeline is ready to serve traffic.

Move seeding behind a dedicated one-shot bootstrap job (container or Job/initContainer pattern in orchestration) that runs once per deploy/environment, then exits cleanly. API instances assume data is ready (or degrade gracefully).

Current behavior

See SpikerSoft.Api/Program.cs seeding block: TermsOfServiceSeederService, AreasOfInterestSeederService, PostalCodeSeederService, PositionSeederService, ILessonCatalogHydrationService.HydrateAsync. Failures are caught and logged; app continues — multiple replicas still race redundant work.

Desired behavior

  1. SpikerSoft.Bootstrap (or similar console/Docker CMD) hosts the same DI + MediatR/repositories needed only for seeders — no ASP.NET listener.
  2. Invocation: compose profiles, Swarm/K8s deploy hook, CI job, or docker compose run bootstrap documented in README / ops runbook.
  3. Idempotency: existing seed methods stay safe to re-run; optional exit code 0 when nothing to do.
  4. API: remove or gate inline seeding via config, e.g. Seeding:RunOnStartup default false in production; optional true for local dev only.
  5. Locking (optional enhancement): distributed lock / "primary seeder" role so accidental parallel bootstrap doesn’t thundering-herd heavy stores.

Acceptance criteria

  • New bootstrap project/container published from repo with clear entrypoint.
  • docker-compose (and/or swarm stack) exposes a documented one-shot service or documented run invocation.
  • Production API defaults to no startup seeding; dev profile can keep ergonomic auto-seed if desired.
  • Health/readiness unaffected: APIs start without waiting for postal/lesson hydrate on every replica.
  • Short ADR or comment in bootstrap README referencing why (cold start / edge).

Labels

enhancement · backend · devops

## Summary `SpikerSoft.Api` currently runs multiple **blocking startup seed operations** synchronously inside `Program.cs` (terms, areas of interest, postal codes, positions, lesson catalog hydration). Each API replica repeats this work unless idempotency shields it everywhere — costing cold-start latency and tying up CPUs on constrained hosts (edge / Jetson) before the pipeline is ready to serve traffic. Move seeding behind a **dedicated one-shot bootstrap job** (container or `Job`/`initContainer` pattern in orchestration) that runs **once per deploy/environment**, then exits cleanly. API instances assume data is ready (or degrade gracefully). ## Current behavior See `SpikerSoft.Api/Program.cs` seeding block: `TermsOfServiceSeederService`, `AreasOfInterestSeederService`, `PostalCodeSeederService`, `PositionSeederService`, `ILessonCatalogHydrationService.HydrateAsync`. Failures are caught and logged; app continues — multiple replicas still race redundant work. ## Desired behavior 1. **`SpikerSoft.Bootstrap`** (or similar console/Docker CMD) hosts the same DI + MediatR/repositories needed **only for seeders** — no ASP.NET listener. 2. **Invocation:** compose `profiles`, Swarm/K8s deploy hook, CI job, or `docker compose run bootstrap` documented in README / ops runbook. 3. **Idempotency:** existing seed methods stay safe to re-run; optional exit code `0` when nothing to do. 4. **API:** remove or gate inline seeding via config, e.g. `Seeding:RunOnStartup` default `false` in production; optional `true` for local dev only. 5. **Locking (optional enhancement):** distributed lock / "primary seeder" role so accidental parallel bootstrap doesn’t thundering-herd heavy stores. ## Acceptance criteria - [ ] New bootstrap project/container published from repo with clear entrypoint. - [ ] `docker-compose` (and/or swarm stack) exposes a documented one-shot service or documented `run` invocation. - [ ] Production API defaults to **no** startup seeding; dev profile can keep ergonomic auto-seed if desired. - [ ] Health/readiness unaffected: APIs start without waiting for postal/lesson hydrate on every replica. - [ ] Short ADR or comment in bootstrap `README` referencing why (cold start / edge). ## Labels `enhancement` · `backend` · `devops`
Author
Owner

Slice 1 up: backend PR #246 — the five-step seeding sequence (terms → areas → postal[gated] → positions → curriculum store → lesson hydration) is extracted from inline Program.cs into a reusable StartupSeedRunner, behind a Seeding:RunOnStartup gate (default true — zero behavior change now). The runner keeps the API's swallow-and-log semantics and exposes throwOnFailure for the job path (a failed bootstrap run must exit non-zero).

Slice 2 (next): SpikerSoft.Bootstrap console host reusing the same runner, Dockerfile + CI workflow, and the infra one-shot service (gameserver-init restart-policy pattern) — with Seeding__RunOnStartup=false flipped on the API in the same infra change, so deploy ordering can never leave an environment without a seeder.

Slice 1 up: **backend PR #246** — the five-step seeding sequence (terms → areas → postal[gated] → positions → curriculum store → lesson hydration) is extracted from inline `Program.cs` into a reusable `StartupSeedRunner`, behind a `Seeding:RunOnStartup` gate (default **true** — zero behavior change now). The runner keeps the API's swallow-and-log semantics and exposes `throwOnFailure` for the job path (a failed bootstrap run must exit non-zero). Slice 2 (next): `SpikerSoft.Bootstrap` console host reusing the same runner, Dockerfile + CI workflow, and the infra one-shot service (gameserver-init restart-policy pattern) — with `Seeding__RunOnStartup=false` flipped on the API **in the same infra change**, so deploy ordering can never leave an environment without a seeder.
Author
Owner

Slice 2 up, both halves:

  • backend #248: dotnet SpikerSoft.Api.dll --bootstrap — the API image itself runs the seed sequence with throwOnFailure and exits without starting Kestrel. Deliberately NOT a second project/Dockerfile/CI lane: same image CI already publishes, composition root identical by construction (no missing-DI drift à la #513).
  • infra #50: spikersoft-bootstrap one-shot stack (gameserver-init semantics — Complete stays done, failed runs retry ×3 and the #510-B reconciler re-runs failed one-shots) + Seeding__RunOnStartup=false on the API service in the same change.

Merge/deploy order: #248 → CI publishes image → pull /mnt/infrastructure → deploy spikersoft-bootstrap (verify Complete, exit 0 in docker service ps) → next API deploy picks up the env flip and replicas stop paying the seed pass on cold start. Close this ticket on that verification.

Slice 2 up, both halves: - **backend #248**: `dotnet SpikerSoft.Api.dll --bootstrap` — the API image itself runs the seed sequence with `throwOnFailure` and exits without starting Kestrel. Deliberately NOT a second project/Dockerfile/CI lane: same image CI already publishes, composition root identical by construction (no missing-DI drift à la #513). - **infra #50**: `spikersoft-bootstrap` one-shot stack (gameserver-init semantics — Complete stays done, failed runs retry ×3 and the #510-B reconciler re-runs failed one-shots) + `Seeding__RunOnStartup=false` on the API service in the same change. **Merge/deploy order**: #248 → CI publishes image → pull /mnt/infrastructure → deploy `spikersoft-bootstrap` (verify `Complete`, exit 0 in `docker service ps`) → next API deploy picks up the env flip and replicas stop paying the seed pass on cold start. Close this ticket on that verification.
Author
Owner

Board-sweep verified complete: backend PRs #246 (StartupSeedRunner + Seeding:RunOnStartup gate) + #248 (--bootstrap mode) + infra PR #50 (spikersoft-bootstrap one-shot stack) all merged; StartupSeedRunner wiring and the infra spikersoft-bootstrap/ stack verified in the trees. Closing.

Board-sweep verified complete: backend PRs #246 (StartupSeedRunner + Seeding:RunOnStartup gate) + #248 (--bootstrap mode) + infra PR #50 (spikersoft-bootstrap one-shot stack) all merged; StartupSeedRunner wiring and the infra spikersoft-bootstrap/ stack verified in the trees. Closing.
Sign in to join this conversation.