Skip to content

External Storage Integration: WorkflowWorker, replay, history - #3017

Open
cconstable wants to merge 16 commits into
mainfrom
extstore/workflow-worker
Open

External Storage Integration: WorkflowWorker, replay, history#3017
cconstable wants to merge 16 commits into
mainfrom
extstore/workflow-worker

Conversation

@cconstable

@cconstable cconstable commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

What was changed

  • Integrated external storage into the replay handler and history iterator.
  • WorkflowWorker handle + send response now use external storage.

Why

  • Workflow workers should utilize external storage and fetch payloads on replay.

Checklist

  • Added tests.

@cconstable
cconstable force-pushed the extstore/workflow-worker branch from dff9b7f to 3a20dcf Compare August 18, 2026 21:04
@cconstable
cconstable changed the base branch from main to extstore/foundation August 19, 2026 15:58
@cconstable
cconstable force-pushed the extstore/foundation branch from 831c284 to 460bfbf Compare August 19, 2026 17:40
@cconstable
cconstable changed the base branch from extstore/foundation to main August 19, 2026 17:53
@cconstable
cconstable changed the base branch from main to extstore/foundation August 19, 2026 17:53
@cconstable
cconstable force-pushed the extstore/workflow-worker branch from 3a20dcf to 0e37f9d Compare August 19, 2026 18:12
@cconstable cconstable changed the title extstore/workflow worker External Storage Integration: WorkflowWorker, replay, history Aug 19, 2026
@cconstable
cconstable force-pushed the extstore/workflow-worker branch from 0e37f9d to c67e0f0 Compare August 19, 2026 18:30
@cconstable
cconstable marked this pull request as ready for review August 19, 2026 18:57
@cconstable
cconstable requested a review from a team as a code owner August 19, 2026 18:57
@cconstable
cconstable force-pushed the extstore/foundation branch from 460bfbf to b3804da Compare August 21, 2026 20:35
@cconstable
cconstable force-pushed the extstore/workflow-worker branch from c67e0f0 to e292757 Compare August 21, 2026 21:57
@cconstable
cconstable force-pushed the extstore/foundation branch from b3804da to ca09b50 Compare August 24, 2026 01:23
@cconstable
cconstable force-pushed the extstore/workflow-worker branch from e292757 to cb05443 Compare August 24, 2026 01:25
Comment thread temporal-sdk/src/main/java/io/temporal/internal/worker/WorkflowWorker.java Outdated
@cconstable
cconstable force-pushed the extstore/workflow-worker branch 5 times, most recently from 6d90f7e to 1538817 Compare August 27, 2026 21:28
@cconstable
cconstable force-pushed the extstore/foundation branch from 2d2d90e to 504467c Compare August 28, 2026 16:10
Base automatically changed from extstore/foundation to main August 28, 2026 17:27
@cconstable
cconstable force-pushed the extstore/workflow-worker branch from 1538817 to d7b9a62 Compare August 28, 2026 21:36
@cconstable
cconstable force-pushed the extstore/workflow-worker branch from d7b9a62 to 8062db7 Compare August 31, 2026 15:49
Duration stickyTaskQueueScheduleToStartTimeout,
WorkflowServiceStubs service,
LocalActivityDispatcher localActivityDispatcher,
CancellationToken<CancellationException> storageCancellation) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing in a cancellation token to each handler type, can we update ExternalStorageRunner to have a shutdown method (or something similar) and have the owner of the runner shut it down from one place?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yea I think this would simplify this. Will update unless I can think of a reason why we would need the token

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought about this a bit and I don't think we could add a shutdown method. A shutdown method would cancel everything and external storage is shared by across clients and workers (that is how we will eventually get our concurrency controls). One worker shutting down shouldn't cause external storage to shutdown in the others.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could potentially put a token on the worker options that can be used to then cancel all inflight extstore operations and get rid of the threading.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also still need to retain the ability to cancel individual extstore operations for things like heartbeats (they can be canceled)

return current;
case ATTRIBUTES_NOT_SET:
case START_TIMER_COMMAND_ATTRIBUTES:
case COMPLETE_WORKFLOW_EXECUTION_COMMAND_ATTRIBUTES:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should be retargeting to the parent if (1) the parent is known, and (2) is not a CaN'd workflow. Just like the other SDKs already do.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to require some plumbing. I don't think we have access to that information here

CommandOrBuilder command = (CommandOrBuilder) message;
// Keep this exhaustive so new command attributes require an explicit target decision.
switch (command.getAttributesCase()) {
case SCHEDULE_ACTIVITY_TASK_COMMAND_ATTRIBUTES:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be retargeted to the activity and should remain as the workflow. The external storage is tied to the lifetime of the workflow, not the activity.

String workflowType = workflowTask.getWorkflowType().getName();
Scope metricsScope =
options.getMetricsScope().tagged(ImmutableMap.of(MetricsTag.WORKFLOW_TYPE, workflowType));
ExternalStorageRunner externalStorageRunner = options.getExternalStorageRunner();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are executed outside of the try-catch that would proactively fail the workflow task; otherwise, an external storage failure is going to just end task processing and wait for the server to timeout.

}
try {
externalStorageRunner.store(builder, target, targetVisitor, storageCancellation);
} catch (Throwable e) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore the catch if it's cancelled?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, should be checking if it's a cancellation here.

@cconstable cconstable Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I think we do need to check for cancellation here but we also need we also need to throw the cancellation and then check it somewhere up the chain (sendTaskCompleted) and explicitly handle cancellation there (e.g. if the worker is shutting down we want to ignore the error). This was not very easy to understand.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't just ignore it here because that would cause the completion event to be sent in sendTaskCompleted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue: when the task fails from this it just says Failed to send workflow task completion: External storage store failed even if it was cancellation. That seems misleading.

grpcRetryOptions);
}

private void sendDirectQueryCompletedResponse(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have test coverage for this.

workflowTypeScope,
workflowStorageTarget(workflowExecution, workflowType));
} catch (ExternalStorageTaskFailure e) {
if (currentTask.getAttempt() > 1) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is behaviorally different than the other SDKs. I think this came from duplicating the gRPC response path. However, they are different causes, where too large gRPC is not fixable but external storage failures likely are (missing replicated payloads, service temporarily faulted, etc).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Will remove this check.

WorkflowServiceStubs service,
LocalActivityDispatcher localActivityDispatcher,
CancellationToken<CancellationException> storageCancellation) {
this.storageCancellation = storageCancellation;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is test coverage for the shutdown / cancellation behavior.

}

@Test
public void resolvesExternalStorageReferencesInFetchedFullHistory() throws Throwable {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test probably passed even if external storage just passed the payload through without modification. So the test isn't demonstrating any capability of external storage.

return mockFactory;
}

private static final class InMemoryStorageDriver implements StorageDriver {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several test driver implementations that nearly do the same thing. Can we make one and alter the behavior via parameters / options?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants