Skip to content

Add large payload support#272

Merged
bachuv merged 8 commits intomainfrom
vabachu/large-payload
Mar 31, 2026
Merged

Add large payload support#272
bachuv merged 8 commits intomainfrom
vabachu/large-payload

Conversation

@bachuv
Copy link
Copy Markdown
Contributor

@bachuv bachuv commented Mar 23, 2026

Issue describing the changes in this PR

Adding large payload support in the Java SDK.

Add large payload support for Durable Task Java SDK

Adds the ability to externalize large payloads (900 KB – 10 MB) to Azure Blob Storage, preventing gRPC message size failures (~4 MB limit). Payloads below 900 KB continue inline with no behavior change. This feature is fully supported for standalone DTS (Durable Task Scheduler) applications. Azure Functions support requires a corresponding .NET extension update and will be added in another PR.

Usage (standalone DTS)

// 1. Configure blob storage for payload externalization
BlobPayloadStoreOptions storeOptions = new BlobPayloadStoreOptions.Builder()
    .setConnectionString("DefaultEndpointsProtocol=https;AccountName=...")
    .build();

// 2. Create worker with large payload support
DurableTaskGrpcWorkerBuilder workerBuilder =
    DurableTaskSchedulerWorkerExtensions.createWorkerBuilder(dtsConnectionString);
AzureBlobPayloadsExtensions.useBlobStoragePayloads(workerBuilder, storeOptions);
workerBuilder.addOrchestration(...);
workerBuilder.addActivity(...);
DurableTaskGrpcWorker worker = workerBuilder.build();

// 3. Create client with large payload support
DurableTaskGrpcClientBuilder clientBuilder =
    DurableTaskSchedulerClientExtensions.createClientBuilder(dtsConnectionString);
AzureBlobPayloadsExtensions.useBlobStoragePayloads(clientBuilder, storeOptions);
DurableTaskClient client = clientBuilder.build();

// payloads >= 900 KB are automatically externalized to blob storage
// and resolved transparently on the receiving side.

How it works

  • Outbound: When a payload (activity input, orchestration output, etc.) exceeds 900 KB, it is gzip-compressed, uploaded to Azure Blob Storage, and replaced with a small token (blob:v1:<container>:<blobName>)
  • Inbound: When the SDK encounters a token in history events or activity inputs, it downloads and decompresses the original payload transparently
  • Payloads > 10 MB are rejected with PayloadTooLargeException
  • Thresholds match the .NET SDK (900 KB default) for cross-SDK compatibility

Code breakdown

New module: azure-blob-payloads

  • BlobPayloadStorePayloadStore implementation that uploads/downloads payloads to Azure Blob Storage with gzip compression
  • BlobPayloadStoreOptions — Builder-pattern configuration (connection string, endpoint+credential, or pre-configured BlobServiceClient; container name, blob prefix, compression toggle)
  • BlobPayloadStoreProviderPayloadStoreProvider SPI implementation for auto-discovery via ServiceLoader when DURABLETASK_LARGE_PAYLOADS_CONNECTION_STRING env var is set
  • AzureBlobPayloadsExtensions — Convenience methods to configure blob storage payloads on worker/client builders

Changes to client module

  • PayloadStore — Interface for upload/download/token detection (extensible to non-blob implementations)
  • PayloadStoreProvider — SPI interface for ServiceLoader-based auto-discovery
  • PayloadHelper — Internal threshold-checking and store-delegation logic (null/empty guard, fast char-length check, UTF-8 byte-length check, max-cap rejection, upload)
  • PayloadInterceptionHelper — Resolves tokens in all protobuf history event types (ExecutionStarted, TaskCompleted, TaskScheduled, SubOrchestrationInstanceCreated, EventRaised, etc.) and externalizes payloads in orchestrator response actions (ScheduleTaskAction, CreateSubOrchestrationAction, CompleteOrchestrationAction)
  • LargePayloadOptions — Configurable thresholds (default 900 KB externalization, 10 MB max)
  • PayloadTooLargeException — Thrown when payload exceeds max
  • DurableTaskGrpcWorkerBuilder.useExternalizedPayloads() — Configures worker with PayloadStore
  • DurableTaskGrpcClientBuilder.useExternalizedPayloads() — Configures client with PayloadStore
  • DurableTaskGrpcWorker — Resolves activity request payloads, externalizes activity response payloads
  • DurableTaskGrpcClient — Externalizes orchestration inputs/events, resolves instance metadata outputs
  • OrchestrationRunner — Resolves incoming history event payloads, externalizes outgoing action payloads

Changes to azurefunctions module

  • OrchestrationMiddleware — Extended to intercept DurableActivityTrigger (resolves input tokens, externalizes large outputs) and uses a resolve-only PayloadStore wrapper for orchestrations (resolves history tokens but does not externalize action inputs, since the .NET extension dispatches those)

Tests

  • PayloadHelperTest — Threshold logic, null/empty handling, UTF-8 byte counting
  • PayloadInterceptionHelperTest — Protobuf history event resolution, action externalization
  • LargePayloadOptionsTest — Builder validation
  • OrchestrationRunnerPayloadTest — End-to-end OrchestrationRunner with PayloadStore
  • DurableTaskGrpcWorkerBuilderTest — Builder configuration validation
  • LargePayloadIntegrationTests — Integration tests against DTS emulator + Azurite
  • BlobPayloadStoreOptionsTest — Options builder validation

Pull request checklist

  • My changes do not require documentation changes
    • Otherwise: Documentation issue linked to PR
  • My changes are added to the CHANGELOG.md
  • I have added all required tests (Unit tests, E2E tests)

Additional information

Additional PR information

@bachuv bachuv requested a review from a team as a code owner March 23, 2026 05:31
Copilot AI review requested due to automatic review settings March 23, 2026 05:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds large payload support to the Durable Task Java SDK by introducing an external payload store abstraction, wiring it into client/worker/runner execution paths, and providing an Azure Blob Storage-backed implementation for Azure Functions scenarios.

Changes:

  • Introduces PayloadStore, LargePayloadOptions, and internal helpers to externalize/resolve payloads in protobuf messages.
  • Updates worker/client/orchestration runner to resolve incoming tokens and externalize outgoing large payloads; adds orchestrator response chunking.
  • Adds new azure-blob-payloads module with BlobPayloadStore + options, and wires it into the Azure Functions middleware.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
settings.gradle Registers the new :azure-blob-payloads Gradle module.
client/src/main/java/com/microsoft/durabletask/PayloadStore.java Public interface for upload/download + token recognition.
client/src/main/java/com/microsoft/durabletask/LargePayloadOptions.java Configures externalization thresholds and max externalized payload size.
client/src/main/java/com/microsoft/durabletask/PayloadHelper.java Centralizes size checks + store delegation for externalize/resolve.
client/src/main/java/com/microsoft/durabletask/PayloadInterceptionHelper.java Resolves/externalizes payload fields inside orchestration/activity protobufs.
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorkerBuilder.java Adds externalized payload configuration + response chunk-size configuration.
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcWorker.java Resolves incoming tokens, externalizes outputs, announces capability, and chunks orchestrator responses.
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClientBuilder.java Adds externalized payload configuration for the client.
client/src/main/java/com/microsoft/durabletask/DurableTaskGrpcClient.java Externalizes outgoing inputs/reasons and resolves tokens in returned metadata.
client/src/main/java/com/microsoft/durabletask/OrchestrationRunner.java Adds overloads to run orchestrations with externalized payload support.
client/src/test/java/com/microsoft/durabletask/PayloadHelperTest.java Unit tests for thresholding, max cap behavior, and resolve/externalize round trips.
client/src/test/java/com/microsoft/durabletask/PayloadInterceptionHelperTest.java Unit tests verifying request/response interception resolves/externalizes expected fields.
client/src/test/java/com/microsoft/durabletask/LargePayloadOptionsTest.java Unit tests for defaults and validation behavior.
client/src/test/java/com/microsoft/durabletask/OrchestrationRunnerPayloadTest.java Tests runner load+run behavior with/without a store.
client/src/test/java/com/microsoft/durabletask/DurableTaskGrpcWorkerBuilderTest.java Tests chunk-size validation and externalized payload builder configuration.
client/src/test/java/com/microsoft/durabletask/LargePayloadIntegrationTests.java End-to-end integration tests for externalization + autochunk behavior.
client/build.gradle Makes test Java executable selection Windows-aware.
azurefunctions/src/main/java/.../OrchestrationMiddleware.java Initializes a blob-backed payload store from env vars and passes it to the runner.
azurefunctions/build.gradle Adds dependency on the new :azure-blob-payloads module.
azure-blob-payloads/build.gradle New module build + publish/sign/spotbugs configuration.
azure-blob-payloads/src/main/java/.../BlobPayloadStoreOptions.java Config object for blob-based payload storage.
azure-blob-payloads/src/main/java/.../BlobPayloadStore.java Azure Blob-backed PayloadStore implementation with optional gzip.
azure-blob-payloads/src/main/java/.../AzureBlobPayloadsExtensions.java Convenience methods to configure builders with blob payload storage.
azure-blob-payloads/src/test/java/.../BlobPayloadStoreOptionsTest.java Unit tests for options validation and defaults.
azure-blob-payloads/spotbugs-exclude.xml SpotBugs exclusions for exposed Azure SDK client objects in options.

Copy link
Copy Markdown
Member

@YunchuWang YunchuWang left a comment

Choose a reason for hiding this comment

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

Code Review: Large Payload Support

Good overall design — the PayloadStore abstraction, PayloadHelper threshold logic, and PayloadInterceptionHelper protobuf interception are well-structured. The response chunking implementation is solid. However, I found several issues that need attention.

Summary of Findings

Critical:

  1. OrchestrationMiddleware silently enables blob storage for ALL Azure Functions apps (falls back to AzureWebJobsStorage)
  2. OrchestrationMiddleware hardcodes BlobPayloadStore — violates the PayloadStore abstraction

Medium (Performance):
3. BlobPayloadStore.upload() makes 2 HTTP calls for compressed uploads (upload + setHttpHeaders)
4. BlobPayloadStore.download() makes 2 HTTP calls (downloadStream + getProperties)
5. PayloadHelper.maybeExternalize() allocates full UTF-8 byte array just to check size

Low / Design:
6. BlobPayloadStore.extractBlobName() ignores container name from token
7. PayloadStore interface has no delete() — externalized payloads accumulate forever
8. PayloadInterceptionHelperfield.isInitialized() is always true in proto3
9. BlobPayloadStore constructor makes network call (ensureContainerExists)

@bachuv bachuv requested a review from YunchuWang March 26, 2026 19:55
Copy link
Copy Markdown
Member

@YunchuWang YunchuWang left a comment

Choose a reason for hiding this comment

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

LGTM. Thorough review of all 27 changed files. Well-architected large payload support with clean interface abstraction (PayloadStore/PayloadStoreProvider), comprehensive protobuf interception at all 4 boundary points (client, worker, orchestration runner, AzFunctions middleware), correct chunking algorithm, and strong test coverage (1859 lines including 853-line integration suite).

Key strengths:

  • Clean separation: client module has zero blob storage deps; azure-blob-payloads provides one implementation via SPI
  • Security: decompression bomb protection (20 MiB cap), container name validation preventing cross-container access, no SAS tokens in payload tokens
  • Backward compatible: zero overhead when externalization is not configured
  • Correct JDK 8 compat in production code

Minor non-blocking suggestions for follow-up:

  1. Consider adding TaskFailedEvent.failureDetails interception in resolveHistoryEvent for cross-SDK completeness
  2. Consider adding Content-Type: application/json header on blob uploads for better Azure Portal observability
  3. BlobPayloadStoreOptionsTest.multipleAuthMethods_throws test name is slightly misleading (actually tests endpoint-without-credential)

None of these are blockers.

@bachuv bachuv merged commit 34780a9 into main Mar 31, 2026
8 checks passed
@bachuv bachuv deleted the vabachu/large-payload branch March 31, 2026 22:26
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.

3 participants