Upgrades and rollback
Use this procedure when changing a core/replay release pair. It covers stopping traffic, draining work, migrating the database, and restoring the previous version if needed. Keep the matching configuration and database backup with each image set.
Core shutdown contract
Core responds to SIGTERM and SIGINT in three stages:
- Withdraw:
/readyand/readyzreturn 503 with statuswithdrawing./healthand/healthzremain healthy. Existing and new flow requests still run duringserver.shutdown_delay, giving the load balancer time to remove the replica. Background destination probes cannot restore readiness. - Drain: new and queued flow requests receive 503 with
Retry-After: 1. All listeners stop accepting connections concurrently; admitted HTTP requests and replay redrives finish before shared resources close. HTTP requests haveserver.shutdown_timeout; detached redrives retain their own deadlines. - Close: stop probes and flow resources, then publishers, then replay capture. Async publishers may each consume their configured drain timeout; capture has a final six-second drain allowance.
server.shutdown_total_timeout bounds the entire sequence from the signal,
including the withdrawal delay, all listeners, and resource cleanup. When omitted it defaults
to max(60s, shutdown_delay + shutdown_timeout + 30s), preserving existing longer
HTTP drain settings. An explicit value must exceed shutdown_delay + shutdown_timeout. A total timeout, unfinished asynchronous publisher drain, or
failed HTTP drain exits unsuccessfully; dependent resources are not closed under
unfinished handlers. Process exit can lose unfinished work. This is a safety
ceiling, not a delivery guarantee or durable spool.
server:
shutdown_delay: 5s # default 0s; tune to measured load-balancer removal
shutdown_timeout: 10s # HTTP drain after withdrawal
shutdown_total_timeout: 60s # complete process shutdown budget
Allow enough total time for the delay plus the larger of HTTP draining and the
maximum replay execution deadline, followed by sequential publisher drains and
capture cleanup, with margin. A publisher drain defaults to shutdown_timeout
when no destination override is set. Multiple async destinations can therefore
require a larger total. Set the orchestrator/container grace period above
the total (the Compose examples use 70s for a 60s total). Do not add an external
preStop sleep without also budgeting for it. Abrupt SIGKILL and host failure cannot
drain memory-backed work.
On listener failure core skips the withdrawal delay, stops admission, and exits with an error after bounded cleanup.
Replay stops claiming new work at shutdown or listener failure. Already claimed
runs retain the remaining lease for snapshot retrieval, execution and finalizing
the result (10s reserved for finalization). HTTP draining allows 30s concurrently.
Set replay's grace period above max(30s, workers.lease) with margin. A killed
worker's unfinished run may expire to unknown; inspect downstream state before
retrying an uncertain delivery.
Compatibility policy
Core and replay must use the same release. Their wire contract is strict; mixed versions are unsupported. Replica replacement within one release is tested. Compatibility between releases must be checked for the specific upgrade. Use pinned image digests, preserve the previous configuration and image digests, and treat a release pair plus its replay schema as one deployment unit.
| Boundary | Supported current combination |
|---|---|
| Application releases | Matching core, replay API and console/WASM from one release; mixed-release rolling upgrades are not assumed safe. |
| HTTP/WASM record/action contracts | Version 1 consumers and generic /v1/records routes; no legacy failure routes. |
| Replay database | Exact current embedded migration version (1 for this baseline), plus the required generic-record tables/types and fencing constraints/indexes. Legacy/future/incomplete shapes fail without repair before serving. |
| Core configuration | Strict current configuration contract; offline validation is not live dependency readiness. Explicit store declarations and matching policy fingerprints remain required. |
| Protection migration | Explicit store-owner migration before core startup; configured runtime roles do not need DDL. |
Schema version and release version are different identifiers. Two databases reporting schema 1 can have incompatible failure-shaped versus generic-record tables. Do not edit migration bookkeeping to bypass compatibility checks. See legacy preservation and isolated restore.
The migration CLI applies forward migrations. There is no supported automatic schema downgrade. A previous replay binary is not assumed to understand a newer schema, even if it starts. Review migration changes before an upgrade.
Coordinated upgrade procedure
Use a maintenance window unless release-specific compatibility has been proven. Test the procedure in staging with copies of configuration and a disposable restored replay database before using it in production.
- Record core/replay image digests, configuration, schema version and counts of
pending/running replay jobs. Validate the candidate core configuration with
hookway validate --config <file>using the new binary and actual environment. - Pause operator retries and incoming webhook traffic at the edge. Do not answer success for requests you have not accepted. Arrange sender-side redelivery or upstream durable buffering for sources that cannot retry the maintenance gap.
- Keep both services running until admitted/pending core requests, asynchronous publisher queues, capture uploads, and queued/running replay jobs have settled. Confirm destination receipts and capture outcomes. If work cannot settle, resolve or explicitly reconcile it before proceeding; stopping replay while core is still producing captures can lose those best-effort uploads.
- After queued/running retry work has settled and new actions are paused, stop
all core replicas using their full shutdown budget while replay capture is
still reachable. Then stop replay replicas. Verify successful exits and no
running jobs; inspect final capture drops and destination receipts. Do not stop
core underneath a known active retry; if work cannot settle, keep maintenance
in place and explicitly reconcile the uncertainty. Do not migrate while any
old replay process runs. Ordinary parallel Compose
downis not a substitute for this controlled release-maintenance sequence. - Back up the quiescent replay database and verify the restore procedure. Keep old images/configuration and this snapshot together. Record a reconciliation checkpoint for downstream deliveries before proceeding.
- Run
hookway-replay migrate --config <file>using the new release and migration-only owner credentials. Require successful exit, then apply reviewed runtime grants for the candidate objects; stop on any failure. Start new core replicas, then the matching replay release. Keep public ingress and operator retries paused until both pass readiness and configuration checks. Investigate any expiredunknownreplay runs before reissuing them. - Exercise a signed canary through the real destination, then a controlled captured failure and operator retry. Check destination receipts and replay outcomes. Resume traffic and retries; monitor errors, latency, capacity, capture drops, and queue depth.
Back up coupled protection state too, not replay alone. Use Backup and restore for isolated targets, logical/artifact/grant checks and reconciliation of work delivered after the snapshot. A failed canary keeps public ingress and operator retries paused; it is not permission to resume the candidate or replay the old queue automatically.
Rollback procedure
Before migration, restore the previous image pair and configuration normally.
After migration, reestablish quiescence and use the coordinated stop steps above.
Restore the previous database snapshot to a separate database and verify it.
Preserve the upgraded database and reconcile all restored queued/running work
against post-snapshot receipts before starting either rollback service;
serve immediately starts workers. Only after that decision may the matching
previous image/configuration pair point at the reconciled target and run a
controlled canary before public traffic/actions resume.
Restoring a snapshot discards replay records and state changes made after it. Do not automatically re-run restored jobs: a job marked queued in the snapshot may already have delivered. Reconcile downstream receipts/event IDs and the preserved upgraded database first. Configuration changes can also make existing snapshots incompatible with a flow; a matching binary pair alone cannot repair those records.
Legacy failure-shaped databases are not compatible with the current record model. Preserve their data and matching binaries; see legacy databases.
ECS deployment ordering and stop budgets
Use the same maintenance ordering on ECS. Account for service replacement, load-balancer removal and each container's stop allowance.
- Pin the matching candidate digests and prepare migration-only task definitions with owner credentials, configuration and database network access. Keep those credentials out of the runtime tasks and console. Pause ingress/operator actions, settle work and preserve the coupled state/checkpoints first.
- Stop old runtime tasks only after their required drains. Disable their service replacement during maintenance; stopping one task in a live service is not quiescence. Keep final core capture reachable before replay is stopped. Verify all old workers exited before migration.
- Run the core protection migration if PostgreSQL protection is selected:
hookway state-store migrate --config core.yaml --store cluster. Then runhookway-replay migrate --config migrate.yamlusing the replay owner. Apply reviewed runtime grants for the candidate objects. - Require an empty
RunTask.failureslist and exactly the intended task ARN. Waiting forSTOPPEDis insufficient: inspectDescribeTasksfailures, the expected migration container's presence and numericexitCode: 0, plusstoppedReasonand logs. Missing exit codes, launch failures or nonzero exit stop the rollout. Retain task definition/digest/config identities and logs. - Start matching core/replay/console tasks with runtime credentials. Keep the edge/actions paused through readiness, signed destination canary and controlled capture/retry receipt reconciliation. Only then resume service traffic.
AWS documents placement failures separately from tasks in RunTask and reports task/container outcomes in DescribeTasks. Do not interpret a successful API call or a stopped task as successful migration.
Set explicit container stopTimeout above core's total shutdown budget and above
replay's max(30s, workers.lease), with measured margin. For the local recipe's
60s core total and 60s replay lease, 70s is the documented container allowance;
load-balancer withdrawal/deregistration must be measured separately. Fargate's
default 30s is insufficient for that configuration and its maximum is 120s.
Longer local runtime budgets remain valid, but must be reduced or deployed on
infrastructure supporting the required grace period before choosing Fargate.
Do not silently clamp application deadlines to hide this conflict. See the
AWS stopTimeout contract.
Check the upgrade before resuming traffic
Confirm the migration exited successfully, both services are ready, a signed canary reaches its destination, and a controlled captured failure can be retried without repeating successful branches. Keep the previous images, configuration and database backup until the new version is working.
Test backup restoration and the exact rollback transition in staging. An installation smoke test does not check database compatibility, and restoring a snapshot can repeat work delivered after the backup. Follow Backup and restore before starting restored workers.