diff --git a/docker-compose.published.yml b/docker-compose.published.yml index e7821a069..c6eac606f 100644 --- a/docker-compose.published.yml +++ b/docker-compose.published.yml @@ -16,7 +16,7 @@ name: durable-workflow-server # release whose default names another image). Set DW_SERVER_IMAGE to run # any other native image, by tag or digest; pin a digest for production # change control. -x-server-image: &server-image ${DW_SERVER_IMAGE:-durableworkflow/server:3.2.0} +x-server-image: &server-image ${DW_SERVER_IMAGE:-durableworkflow/server:3.3.0} services: server: diff --git a/docs/releases/3.3.0.md b/docs/releases/3.3.0.md new file mode 100644 index 000000000..41b55c703 --- /dev/null +++ b/docs/releases/3.3.0.md @@ -0,0 +1,137 @@ +# Durable Workflow Server 3.3.0 (native) + +Server 3.3.0 is a minor release of the native (Rust) Server. It is published +as `durableworkflow/server:3.3.0` and `ghcr.io/durable-workflow/server:3.3.0`. +The published Compose file defaults to it. Rolling tags move only in the +separate promotion step that follows the release's qualification. + +## Upgrade + +**Schema 10 → 11 on MySQL, MariaDB and PostgreSQL.** This release adds one +index to `workflow_tasks`, so the native schema on MySQL, MariaDB and +PostgreSQL moves from version 10 to version 11. SQLite stays at version 10 +and needs no upgrade step from 3.2.x. + +A server never upgrades its schema at startup. Started on a version 10 +database, 3.3.0 refuses with `native_schema_upgrade_required`. To upgrade: + +1. Stop every native node. +2. Back up the database. +3. Run the 3.3.0 image's `schema-upgrade` once, with the same `DB_*` + settings, as described in + [Native schema upgrades](../../rust/README.md#native-schema-upgrades). +4. Start the 3.3.0 nodes. + +Databases on an earlier native schema this release knows (version 8, from +3.0.x and 3.1.x) are upgraded by the same command. + +- **PostgreSQL:** the upgrade builds the new index with a plain + `CREATE INDEX`, which blocks writes to `workflow_tasks` until the build + finishes. On a large `workflow_tasks` table, plan the upgrade window for it. +- **MySQL and MariaDB:** DDL commits on its own there, so the upgrade marks the + database as upgrading until it finishes. If an upgrade is interrupted, + running `schema-upgrade` again resumes it. + +**Rolling back to 3.2.x requires restoring a backup.** Once a MySQL, MariaDB +or PostgreSQL database is at version 11, a 3.2.x server refuses it with +`native_schema_newer_than_binary`. To go back to 3.2.x, restore the backup +taken before the upgrade. + +SQLite databases stay at version 10, so 3.2.x can still open them, but 3.2.x +must not continue a condition wait opened with the occurrence deadline policy +(below). A 3.2.x server silently drops the policy fields, so a reopen of such a +wait records an ordinary per-open wait in the middle of an anchored chain. +After that, sdk-php refuses to replay the run, and 3.3.0 refuses later reopens +of the chain, even after you upgrade again; upgrading again does not repair a +chain that is already mixed. If any workflow has used the occurrence deadline +policy, roll an SQLite database back to 3.2.x only by restoring the backup +taken before the upgrade. + +**Unchanged:** the worker protocol stays at `1.20` and the control plane at +version `2`. Cluster info and worker `server_capabilities` add one capability, +`condition_wait_occurrence_deadline`. + +## Changes + +### Opt-in occurrence wall deadline for condition waits +An `open_condition_wait` command can opt in to one wall deadline for an +authored condition-wait occurrence, so false wake-ups no longer restart its +timeout +([sdk-php#269](https://github.com/durable-workflow/sdk-php/issues/269), +[#1098](https://github.com/durable-workflow/server/pull/1098)). +Native advertises this as `condition_wait_occurrence_deadline: true`. + +- The command sets `deadline_policy: "occurrence"` and + `condition_wait_timeout_budget_seconds`, the original timeout. Its + `timeout_seconds` stays the remaining time and must be between 0 and the + original budget. It also needs a `condition_wait_occurrence_id`. +- The first open of an occurrence must author its whole budget, and fixes the + deadline at that commit plus the budget. +- A reopen of the same run, occurrence, condition key and definition + fingerprint reuses the first timer's stored deadline exactly. If that + deadline has already passed, the new timer is due at once; it never fires + inside the completion. +- The history records the policy and original budget on + `ConditionWaitOpened`, and `occurrence_deadline_at` on both + `ConditionWaitOpened` and the timeout's `TimerScheduled`. +- Conflicts refuse the whole completion and write nothing: + `condition_wait_occurrence_mismatch`, `condition_wait_occurrence_timed_out` + and `condition_wait_anchor_unavailable`. Malformed policy fields refuse with + `invalid_condition_deadline_policy`. +- An occurrence first opened without the policy keeps its per-open timeout, + and a later opt-in to it is refused. An occurrence opened with the policy + must keep it on every reopen. +- Legacy chains first opened without the policy retain their existing behavior. + +SDK support is merged in sdk-php +([sdk-php#364](https://github.com/durable-workflow/sdk-php/pull/364)) and +ships in a later sdk-php release; sdk-php 3.10.0 does not send the policy. +The sdk-php release that includes it must also fail closed when the +Server stops advertising `condition_wait_occurrence_deadline`: if an existing +anchored chain needs a reopen, the worker aborts the task without completing +it, so the history stays unchanged until a capable Server is back. +The PHP Server does not support this feature. + +### Empty query polls without the transition lock +A new non-waiting query poll (`timeout_seconds: 0` with a `poll_request_id`) +that would find nothing is answered `empty` from one read snapshot, without +taking the transition lock and without writing anything. It does this only +while every fact of that answer holds at the snapshot's instant; otherwise it +takes the locked path as before. On MySQL and MariaDB the snapshot is an +explicit repeatable-read transaction +([server#1057](https://github.com/durable-workflow/server/issues/1057), +[#1097](https://github.com/durable-workflow/server/pull/1097)). + +### Bounded claim candidate reads +- **MySQL and MariaDB** read ready and leased claim candidates in separate, + bounded ranges in dispatch order, through the new scoped dispatch index + ([server#1065](https://github.com/durable-workflow/server/issues/1065), + [#1085](https://github.com/durable-workflow/server/pull/1085)). +- **PostgreSQL** reads ready and leased candidates through separate, fully + scoped ordered ranges on the new index, with namespace, queue and task-kind + predicates inside each range + ([server#1069](https://github.com/durable-workflow/server/issues/1069), + [#1088](https://github.com/durable-workflow/server/pull/1088)). +- **All backends:** a claim first reads one fairness batch of 10 candidates + and tries those, reading the full 100-candidate page only when the batch + cannot be decided or claimed. Batch boundaries and fairness order are + unchanged + ([server#1064](https://github.com/durable-workflow/server/issues/1064), + [#1092](https://github.com/durable-workflow/server/pull/1092)). + +The candidates each claim returns, and their order, are the same as before. + +### First history page reuses the claimed history total +A workflow claim already holds the run's last history sequence. The first +history page and the history budget now use that value instead of reading it +again. Later pages keep the original lookup +([server#1066](https://github.com/durable-workflow/server/issues/1066), +[#1093](https://github.com/durable-workflow/server/pull/1093)). + +## Known limitations +- **Ready-age replay after a restart:** the MariaDB restart lane once failed + the cold-poll replay claim assertion after `SIGKILL` recovery. The retained + response did not establish whether the answer was empty or a capability or + age field was missing. Diagnostics were added; the intermittent cause is + still under investigation + ([server#1096](https://github.com/durable-workflow/server/issues/1096)).