Deployment & Settings Changes - v1.73.0

Release: v1.73.0 Date: 2026-09-04


Summary

Impact Level: MEDIUM

Action Required: YES for operators who run the converter as a separate release from editor-service, and for anyone who calls the converter or the PDF export directly. NO for a single-release deployment that consumes the published images.

Backward Compatible: MOSTLY - three migrations are additive, every new setting defaults to current behaviour, but PDF export is removed and the converter’s conversion routes now require a shared secret.

This release adds booking sessions, three additive migrations, an asynchronous conversion queue in the converter, a deployment-environment declaration, and a shared secret between editor-service and the converter. A single Helm release that runs editor-service and the converter together generates and wires that secret itself. A deployment that runs the converter in a separate release must pin the same secret on both sides before upgrading.


Quick Decision Matrix

Component/Area Change Type Risk Level Action Required Impact
Environment Variables New LOW YES (split converter release only) New variables on editor-service, booking-service and the converter, all defaulted; one removed converter variable
Database Schema New LOW YES (restricted DB roles only) V118, V119, V120 applied automatically on editor-service startup; V119 builds an index concurrently outside a transaction
Database Modules No changes NONE NO -
JooQ Configuration Modified LOW NO Generated sources regenerated for the new tables; build-time only
Docker Images Modified LOW NO Converter image no longer downloads a second browser at build time
Kubernetes/Helm Config MEDIUM YES (split converter release, or a GPU/CPU pair) Seven new values, one new validation template, a chart-managed converter secret, Redis for the converter’s queue
Runtime Dependencies No changes NONE NO -
Build Dependencies No changes NONE NO -
Monitoring/Logging Modified LOW NO /health/redis now reports the pod’s role and queue depths; conversion routes are rate-limited separately from job polling
Public API Breaking MEDIUM YES (direct API callers only) PDF export and /convert_pdf removed; section photo upload answers 202 and is polled; converter routes need a bearer token

Deployment Decision

Overall Risk: MEDIUM

Recommended Action: APPROVE, with the converter secret in place first

Downtime Required: NO - Zero-downtime supported

Rollback Risk: SAFE for the Java services - V118-V120 add tables, columns and indexes that a previous image ignores. Roll the converter back together with editor-service, or before it, never after it (see Rollback).

Special Considerations: A deployment where editor-service and the converter live in different Helm releases must give both the same secrets.converter.apiKey before upgrading either. Upgrade editor-service first, then the converter.


Configuration Changes

Environment Variables

New Variables

Variable Service Required Default Description
SEATMAP_SERVICES_PREVIEW_API_KEY editor-service Yes, once the converter runs v1.73.0 (empty, no header) Bearer token sent on every converter call. The chart reads it from the <release>-converter secret when converter.enabled is true or secrets.converter.apiKey is set.
SEATMAP_SERVICES_PREVIEW_JOBS editor-service No jobs/ Path under the converter URL polled for a queued conversion.
SEATMAP_SERVICES_PREVIEW_AWAIT_TIMEOUT editor-service No PT5M How long editor-service waits for a queued conversion before reporting it failed.
SEATMAP_SERVICES_PREVIEW_POLL_INTERVAL editor-service No PT2S Interval between job status polls.
SEATMAP_INSTANCE_ENVIRONMENT editor, booking No (undeclared) Declared deployment environment, set from the Helm value instance.environment. Returned by GET /api/instance-info and used as the default error-report environment.
SEATMAP_HEARTBEAT_ENABLED editor-service No true via Helm Enables the periodic diagnostic check-in. Set from instance.heartbeatEnabled. Outside the prod Spring profile the application default is false.
SEATMAP_SESSION_ENABLED booking-service No true Serves the booking-session endpoints. Set from global.bookingSessions.enabled. When false the session controllers are not registered, so the session routes answer 404 while existing holds still expire normally.
SEATMAP_SESSION_TTLSECONDS booking-service No 900 Lifetime of an active session’s hold before it expires.
SEATMAP_SESSION_PENDINGGRACESECONDS booking-service No 600 Extra time a session frozen for payment keeps its seats.
SEATMAP_SESSION_MAXSEATS booking-service No 10 Upper bound per session, counted as seats plus general-admission capacity.
SEATMAP_SESSION_REAPERBATCHSIZE booking-service No 100 Expired sessions released per reaper batch (REAPERMAXBATCHES 10 batches per run, REAPERINTERVALMS 15000 ms between runs).
API_KEY converter Yes, when the routes must be authenticated (empty, open) Shared secret. When set, POST /, /background, /thumbnail, /photo and GET /jobs/{id} require Authorization: Bearer <value>. The chart sets it from the <release>-converter secret.
RATE_LIMIT_MAX converter No 600 Requests per RATE_LIMIT_WINDOW_MS per IP on the conversion routes. POST /photo now counts against this budget; GET /jobs/{id} has its own, JOBS_RATE_LIMIT_MAX.
REDIS_ENABLED converter No false Queue conversions in Redis instead of rendering inside the request. Set from converter.config.async.enabled (chart default true).
REDIS_HOST / REDIS_PORT / REDIS_PASSWORD converter Yes, when REDIS_ENABLED=true - Redis the queue lives in. The chart wires the release’s Redis (global.redis.* or the bundled redis).
CONVERTER_ROLE converter No cpu gpu claims a queued job immediately; cpu waits CPU_CLAIM_DELAY_MS first. The chart sets it per pod variant.
CPU_CLAIM_DELAY_MS converter No 5000 GPU head start. The chart sets 0 when converter.gpu.enabled is false.
JOBS_RATE_LIMIT_MAX converter No 5000 Per-minute budget for GET /jobs/{id} polling, separate from RATE_LIMIT_MAX. Set from converter.config.rateLimit.jobsMax.
WORKER_ENABLED converter No true false makes a pod accept submissions without rendering.
JOB_TTL_SECONDS / RESULT_TTL_SECONDS converter No 3600 / 900 Lifetime of a job record and of its rendered result in Redis.
JOB_VISIBILITY_TIMEOUT_MS converter No 300000 Render budget before a claimed job is returned to the queue.
JOB_MAX_ATTEMPTS converter No 3 Attempts before a job is recorded as failed.
WORKER_IDLE_POLL_MS / REAPER_INTERVAL_MS converter No 500 / 30000 Queue polling cadence and lost-claim sweep interval.
TILE_SIZE / RENDER_TILE_SIZE converter No 2048 / 2048 Background tile size served to the renderer and screenshot tile size; both clamp to 256-4096.

Leaving every variable unset preserves current behaviour, with two exceptions that follow from the images themselves: a converter started with API_KEY set rejects unauthenticated conversion calls, and a chart-managed converter always has it set.

Modified Variables

Variable Service Change
GLITCHTIP_*_ENVIRONMENT (chart-wired) editor, booking Now default to instance.environment when the per-service glitchtip.*.environment value is not set. Existing explicit values keep winning.

Deprecated Variables

Variable Service Replacement Notes
SEATMAP_SERVICES_PREVIEW_PATH editor-service none The PDF conversion path is gone with PDF export. An override of this variable is now ignored.
REDIS_QUEUE_NAME converter none The queue is keyed by the converter itself. An override is ignored.

Database Changes

Migrations

New Migrations: YES

Version Script Type Tables Action Required
V118 V118__booking_sessions.sql Schema booking_session, booking_session_config, seat_on_event NO
V119 V119__seat_on_event_booking_session_idx.sql (+ .sql.conf) Index seat_on_event NO
V120 V120__event_series.sql Schema event_series, event_series_exception, event NO

All three run automatically when editor-service starts, in order, after V117. None rewrites or backfills existing rows.

  • V118 creates the booking_session and booking_session_config tables with their indexes, adds the nullable booking_session_id column to seat_on_event, and adds a foreign key from it to booking_session declared NOT VALID, so no existing row is scanned. ADD COLUMN takes a brief exclusive lock on seat_on_event; on a busy deployment run the upgrade outside the peak booking window.
  • V119 builds the partial index on seat_on_event (booking_session_id) with CREATE INDEX CONCURRENTLY. Its .sql.conf sidecar sets executeInTransaction=false, which Flyway needs for a concurrent build. Writes continue during the build. If the build is interrupted, PostgreSQL leaves an INVALID index behind: drop seat_on_event_booking_session_idx before repairing and restarting Flyway.
  • V120 creates the event_series and event_series_exception tables, adds the nullable series_id column to event with a foreign key and a partial unique index. The foreign key is validated on creation; every existing event row has a null series_id, so the scan is short.

Note: The highest migration version moves from V117 to V120. A duplicate-version check across the migration directory reports no collisions; the three migrations are sequential.

Restricted database roles: the migration role needs CREATE on the schema, ownership or ALTER on seat_on_event and event, and REFERENCES on booking_session, organization, schema, event and event_series. Deployments that run migrations as the table-owner role and connect the services as a lower-privileged role must also grant that runtime role SELECT, INSERT, UPDATE, DELETE on the four new tables and usage on the event_series_exception_id_seq sequence, or booking-service fails at the first session request.

Schema Changes

New Tables

Table Purpose
booking_session Server-held cart: state, cart JSON, expiry, idempotency key, per organisation and event
booking_session_config Per-organisation opt-in for public (keyless) session endpoints, default off
event_series Reserved for a later release; no runtime path writes to it yet
event_series_exception Reserved for a later release; no runtime path writes to it yet

Modified Tables

Table Change
seat_on_event booking_session_id uuid (nullable, FK ON DELETE SET NULL), partial index
event series_id uuid (nullable, FK ON DELETE SET NULL), partial unique index

Performance Impact

No existing index is dropped or rebuilt. The two new partial indexes cover only rows that carry a session or a series, so they stay empty until the features are used.


JooQ Configuration Changes

What Changed: The committed jOOQ sources were regenerated for the new tables and now also carry two pieces of booking-session metadata that the database already had (the event column on the organisation idempotency index, and the seat-to-session foreign key). No hand-written code referenced either.

Location: products/booking-service/src/generated/java/pro/seatmap/booking/jooq/

Action Required: NO - build-time only.


Docker & Container Changes

Docker Images

Modified Images

Image Changes Breaking
converter-service Build no longer downloads a second browser through Puppeteer; the image’s own Chromium renders No
editor-service Rebuilt No
booking-service Rebuilt No
editor-client Rebuilt No
booking-client Rebuilt No

All images are published to registry.gitlab.com/seatmap.pro/seatmap/ at tag 1.73.0.


Kubernetes / Helm Changes

Helm Chart

App Version: v1.73.0

Values Changes

New Values:

global:
  bookingSessions:
    enabled: true

converter:
  config:
    async:
      enabled: true
      cpuClaimDelayMs: 5000
    rateLimit:
      jobsMax: 5000

instance:
  environment: ''
  heartbeatEnabled: true

secrets:
  converter:
    apiKey: ''

global.bookingSessions.enabled propagates to booking-service as SEATMAP_SESSION_ENABLED. Set it to false to keep the session endpoints closed; public sessions additionally stay off per organisation until enabled through the session-config endpoint.

converter.config.async.enabled queues conversions in Redis: set global.redis.host (and its secret) or enable the bundled redis. While Redis is unreachable the converter keeps rendering inside the request and queueing stays off (see Known Issues). Set the value to false to render inside the request by design, which needs no Redis. cpuClaimDelayMs only matters when a GPU pod is deployed alongside a CPU pod; the chart passes 0 otherwise.

instance.environment accepts production, staging, development or unspecified; leaving it empty means undeclared. Any other value now fails at render time (templates/validate.yaml), so an abbreviation such as prod must be spelled out before the next upgrade.

secrets.converter.apiKey pins the converter bearer token. Left empty on a release with converter.enabled: true, the key-generation job creates a random one on first install and keeps it across upgrades. See Secrets for the split-release case.

Modified Values: none.

Removed Values: none.

New Templates

Template Rendered when Purpose
validate.yaml always Fails the render when instance.environment is outside the accepted list
secrets.yaml (new block) secrets.converter.apiKey is set and converter.enabled is false Creates <release>-converter so editor-service can read the token for an external converter

Modified Templates

  • generate-keys-job.yaml and editor/rbac.yaml now also run when only the converter is enabled, so a converter-only release generates its own <release>-converter secret (previously both were tied to editor.enabled).
  • editor/deployment.yaml mounts SEATMAP_SERVICES_PREVIEW_API_KEY from <release>-converter, sets SEATMAP_INSTANCE_ENVIRONMENT when instance.environment is declared, always sets SEATMAP_HEARTBEAT_ENABLED from instance.heartbeatEnabled, and uses instance.environment as the error-report environment when no per-service value is set.
  • converter/service.yaml adds a <release>-converter-gpu Service when converter.gpu.enabled is true.
  • booking/deployment.yaml sets SEATMAP_SESSION_ENABLED and SEATMAP_INSTANCE_ENVIRONMENT.
  • converter/deployment.yaml wires API_KEY, REDIS_*, CONVERTER_ROLE, CPU_CLAIM_DELAY_MS and JOBS_RATE_LIMIT_MAX, and labels pods with their conversion role.

Secrets

seatmap-helm-secrets needs no new key for a single-release deployment. The key set that release depends on is unchanged from the currently deployed release.

New chart-managed secret: <release>-converter with one key, api-key (<release> is the chart’s full name: the release name, or <release>-seatmap when the release name does not contain seatmap). It is created by the key-generation job when converter.enabled is true, or rendered from secrets.converter.apiKey when the converter is external. To read the generated value:

kubectl -n <namespace> get secret <release>-converter -o jsonpath='{.data.api-key}' | base64 -d

Split releases (editor-service and the converter in different Helm releases): both releases must carry the same value in secrets.converter.apiKey. The supported way is one key, for example converter-api-key, in each release’s seatmap-helm-secrets, mapped through valuesFrom to secrets.converter.apiKey. On the converter side a pinned value overwrites the generated secret on the next reconcile; on the editor side it renders the <release>-converter secret the deployment mounts. Add the key to both secrets before upgrading either release.


Dependencies

Runtime Dependencies

No Updated Dependencies

Java, Spring Boot, Node.js and the PostgreSQL and Redis requirements are unchanged. The converter now uses Redis at runtime when queueing is enabled; it is the same Redis the Java services already use.

No New Dependencies

No new libraries.

Build Dependencies

No changes. JDK 25 and Node 22 remain the toolchain.


CI/CD Deployment

Automated Deployment via GitLab CI/CD

Deployment Flow:

  1. Merge to dev: auto-deploys to the stage environment.
  2. Merge to main: manual trigger to deploy to production.
  3. A converter that runs as its own release is upgraded after editor-service in the same window.

Zero-Downtime: Supported. V118 takes a brief lock on seat_on_event for the column add; V119 builds concurrently; V120 touches event briefly. No new configuration is required for a single-release deployment.

Action Required: split releases only: pin the same secrets.converter.apiKey on both releases before the deploy (see Secrets).


Rollback

Rollback Support: Automated via GitLab CI/CD

Database Rollback: Not required. V118-V120 only add tables, columns and indexes, all nullable or empty, so a previous image runs against the migrated database unchanged. The migrations are not reversed by a rollback; rows created while v1.73.0 ran stay in their tables and are ignored by an earlier image. Seats still held by a session at rollback time keep their booking_session_id and are released only by the state they carry, so confirm no session is mid-checkout before rolling back.

Procedure: Redeploy the previous image tags for editor-service, booking-service and the clients. If the converter was upgraded, roll it back first or in the same step as editor-service; do not leave a rolled-back editor-service running against a converter that has not been rolled back.


Monitoring & Health Checks

New Metrics

GET /health/redis now reports the pod’s conversion role and the pending and processing queue depths, replacing queue_name and queue_length; the converter ingress does not expose it, so it is read from inside the cluster. /health and /metrics are unchanged, carry no token and are not rate-limited.

Health Checks

No changes to the Java services’ health endpoints. GET /api/instance-info on editor-service now also returns installationId and the declared environment.


Known Issues

  • A converter with a pinned or generated API_KEY answers 401 to any caller that does not send it. Internal probes that call a conversion route must send the token; /health does not need it.
  • A converter with converter.config.async.enabled: true and no reachable Redis stays Ready and renders inside the request, answers 503 on GET /jobs/{id}, and logs Redis connection errors until Redis is reachable; queueing starts on its own once it is. Provide Redis, or set the value to false.

Release Notes Summary

What Changed

  1. Booking sessions: two additive migrations, one Helm value (global.bookingSessions.enabled, default on), session endpoints closed per organisation until enabled.
  2. Converter queue and shared secret: conversions are queued in Redis and claimed by GPU-first pods; the conversion routes require a bearer token the chart generates and hands to editor-service.
  3. Deployment identity: instance.environment is declared once, validated by the chart, returned by /api/instance-info, and used as the default error-report environment.
  4. PDF export removed: the editor action, the GET /api/export/{id}/pdf/{eventId}/ endpoint and the converter’s POST /convert_pdf route are gone; SVG export remains.
  5. Section photo upload: POST /api/section-photo/ answers 202 with a request id when the queue is on; poll GET /api/section-photo/{requestId}/.

What Didn’t Change

  • No existing table, column or index is altered or dropped.
  • No key is added to or renamed in seatmap-helm-secrets for a single-release deployment.
  • No health-check endpoint is renamed or removed.
  • POST /photo on the converter now counts against the conversion-route request budget; no other existing environment variable changes meaning; two obsolete ones are ignored.
  • Runtime and build toolchains are unchanged.

Support

Questions

Escalation

Standard escalation procedures apply.


Sign-Off

Deployment Risk: MEDIUM

Rollback Safety: SAFE for the Java services; roll the converter back before or with editor-service

Downtime Required: NO

Special Steps: Pin secrets.converter.apiKey on both sides of a split converter deployment before upgrading; upgrade editor-service before the converter; give the converter a Redis before turning converter.config.async.enabled on.


This release is a medium-risk deployment: the database changes are additive and automatic, every new setting is defaulted, and the one hard prerequisite - a shared converter secret on both sides of a split deployment - is a one-time secret addition before the deploy.