Overview
Workflow Update combines the capabilities of Signals and Queries in a single API call:- Bidirectional - Send data to workflow and receive results back
- Validated - Workflow can reject updates before processing
- Efficient - Rejected updates leave no trace in workflow history
- Synchronous - Caller blocks until update is accepted or rejected
Workflow Update is a complex feature that acts as a proxy between the API caller and workflow code, enabling workflows to expose custom APIs.
Update vs Signal vs Query
Signal
Fire-and-forget
- Cannot be rejected
- Always writes events
- No return value
- Cannot know if processed
Query
Read-only
- Cannot modify state
- Returns data
- No history events
- Synchronous response
Update
Read-write with validation
- Can be rejected
- Only accepted updates write events
- Returns data
- Synchronous accept/reject
Key Requirement: No Persistence on Reject
Updates must be “cheap” when rejected:- No “Update Received” event - Updates arrive as Messages, not Events
- No “Update Rejected” event - Rejected updates simply disappear
- First event is “Update Accepted” - Contains the original request payload
- Update outcome in “Update Completed” event - Success or failure result
- Speculative Workflow Task - Task to ship update is not persisted in mutable state
Update Registry
Updates are managed through theupdate.Registry interface:
- Location:
workflow.ContextImplstruct - Stores: Admitted and accepted updates (in-flight only)
- Completed updates: Accessed through
UpdateStoreinterface - Mutable state: Contains
UpdateInfomap linking ID to acceptance/completion info
Update States
Update Lifecycle
Update admitted to registry
History service admits update to the registry. Update is now in-memory only.
- No persistence write at this stage
- Stored in
update.Registry - Linked to workflow execution
Speculative workflow task created
A speculative (non-persisted) workflow task is created to ship the update to a worker.See Workflow Lifecycle for details.
Worker receives update via message
Update is shipped to worker as a Message (not an Event).Worker can:
- Accept the update (validation passed)
- Reject the update (validation failed)
Worker responds with acceptance or rejection
If accepted:
WorkflowExecutionUpdateAcceptedEvent written to historyIf rejected: Update disappears with no history traceUpdate handler executes (if accepted)
Workflow code executes the update handler.Handler can:
- Complete successfully with result
- Fail with error
Message Protocol
Updates use the Message Protocol instead of Events:- Messages - Arbitrary data shipped to worker outside history events
- Commands - Must produce events, so cannot be used for updates
- Update request - Shipped as
Messageto avoid events on rejection - Update outcome - Cannot use Commands (would force event creation)
The Message Protocol was introduced specifically to support Update’s “no event on reject” requirement.
Speculative Workflow Tasks
Updates rely on speculative workflow tasks:- Not persisted in mutable state
- Transient - Exist only to ship the update
- Converted to normal task if update is accepted
- Discarded if update is rejected
Why speculative tasks?
Why speculative tasks?
Normal workflow tasks must be persisted before being sent to workers. If an update is rejected, we cannot have any persisted state. Speculative tasks are created in-memory only and are either promoted to normal tasks (on accept) or discarded (on reject).
Update Validation
Workflows can validate updates before accepting:Update Events
WorkflowExecutionUpdateAcceptedEvent
Written when update is accepted:Unique identifier for the update
Original update request including input payload
Message ID from the workflow task that accepted it
WorkflowExecutionUpdateCompletedEvent
Written when update completes:Identifier matching the accepted event
Success or failure result from update handler
Metadata about the update execution
In-Memory Queue
Updates use the in-memory queue for speculative tasks:- Purpose: Schedule speculative workflow tasks without persistence
- Location:
service/history/queues/inmemory_queue.go - Behavior: Tasks exist only in memory until promoted or discarded
Effect Package
Update implementation uses theeffect package for managing side effects:
- Purpose: Defer and batch side effects until commit
- Location:
service/history/workflow/effect/ - Usage: Ensures update acceptance/rejection is atomic
Best Practices
Keep validation fast
Keep validation fast
Update validation should be quick since the client is blocked waiting.
- Avoid expensive computations in validators
- Don’t call activities from validators
- Return rejection quickly if validation fails
Use idempotent update IDs
Use idempotent update IDs
Provide idempotent update IDs to safely retry:
- Use deterministic IDs (e.g., based on request data)
- Server deduplicates updates with same ID
- Enables safe retry on client timeout
Handle update failures gracefully
Handle update failures gracefully
Update handlers can fail - design for this:
- Return meaningful error messages
- Distinguish validation failures from execution failures
- Client receives failure in response
Don't overuse updates
Don't overuse updates
Updates are powerful but complex:
- Use Signals if you don’t need return values
- Use Queries if you don’t need to modify state
- Updates add overhead - use judiciously
Monitoring
Key metrics for update operations:- update_admitted_total - Total updates admitted
- update_accepted_total - Updates accepted by workflows
- update_rejected_total - Updates rejected by workflows
- update_completed_total - Updates completed successfully
- update_failed_total - Updates that failed during execution
- update_latency - Time from admission to completion
Limitations
See Also
Workflow Lifecycle
Understand speculative tasks and message protocol
Event Sourcing
Learn how update events are stored