Skip to main content

Workflow Versioning

Workers replay existing workflow histories against the currently deployed workflow code. A code change that produces a different sequence of decisions can therefore cause a non-deterministic workflow error.

Use workflow.get_version to introduce incompatible workflow changes while preserving the path recorded by executions that started on older code.

Samples​

SampleDescriptionCode
Versioning integration testRecords and replays a version marker against a Cadence servertest_versioning.py

Adding a version gate​

Suppose a workflow originally called one activity:

result = await workflow.execute_activity(
"charge_card",
str,
order_id,
start_to_close_timeout=timedelta(minutes=1),
)

To replace it with a new activity, deploy a version gate:

from cadence import workflow

version = workflow.get_version(
"charge-card-api",
workflow.DEFAULT_VERSION,
1,
)

if version == workflow.DEFAULT_VERSION:
result = await workflow.execute_activity(
"charge_card",
str,
order_id,
start_to_close_timeout=timedelta(minutes=1),
)
else:
result = await workflow.execute_activity(
"charge_card_v2",
str,
order_id,
start_to_close_timeout=timedelta(minutes=1),
)

For a new execution, get_version selects and records max_supported, which is 1 here. When replaying history created before the gate existed, it returns workflow.DEFAULT_VERSION (-1).

Use a unique, stable change_id for each incompatible change. Repeated calls with the same ID return the version already selected for that workflow execution.

Raising the maximum version​

To introduce another implementation later, increase max_supported and retain every path that may still appear in open workflow histories:

version = workflow.get_version(
"charge-card-api",
workflow.DEFAULT_VERSION,
2,
)

if version == workflow.DEFAULT_VERSION:
result = await charge_with_v1(order_id)
elif version == 1:
result = await charge_with_v2(order_id)
else:
result = await charge_with_v3(order_id)

New executions select version 2; older executions continue to use the version recorded in their history.

Removing old branches​

Remove an old branch only after no execution can replay that version. First raise min_supported while keeping the get_version call:

version = workflow.get_version("charge-card-api", 1, 2)

Do not exclude workflow.DEFAULT_VERSION while executions whose histories predate the version gate are still open. Replaying an unsupported recorded version is treated as a fatal non-determinism error.

Eventually, after all histories containing the change have aged out or can no longer replay, you can remove the gate and obsolete branches.

Finding versioned executions​

When a new version is selected, the SDK updates the reserved CadenceChangeVersion search attribute with a value in the form:

<change-id>-<version>

For example, charge-card-api-2 identifies executions that selected version 2. Do not write CadenceChangeVersion yourself; it is reserved for get_version.