Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-compose.published.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
137 changes: 137 additions & 0 deletions docs/releases/3.3.0.md
Original file line number Diff line number Diff line change
@@ -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)).
Loading