Skip to main content

Bypass the 2 MB Limit Without Shrinking Your Workflow

· 3 min read
Kevin Burns
Developer Advocate @ Uber

The 2 MB per-payload limit in Cadence does not come with a helpful error. Your workflow does not receive a graceful degradation notice. It just fails. And if you have never hit the limit before, the stack trace is not obvious about what happened.

The claim-check pattern solves this completely. Instead of compressing a large payload and hoping it fits, you offload it to an external blob store and write only a small reference into Cadence history. The limit no longer applies to your payload; only to the reference, which is always tiny.

How it works

The DataConverter intercepts every payload before it reaches Cadence history. With claim-check, it makes a size decision on each one:

  • At or below the threshold (4 KB in the demo): payload is stored inline in history, prefixed with 0x00.
  • Above the threshold: payload is written to an external blob store. Only a small JSON reference (the blob key) is stored in history, prefixed with 0x01.

On the decode side, the worker reads the prefix byte and either uses the inline payload directly or fetches it from the blob store.

large payloadPUT blobtiny refWorkerDataConverterBlobStoreHistory

Encode: large payload goes to the blob store; only a tiny reference enters Cadence history.

Threshold tuning

The default threshold in the samples is 4 KB. In production, profile your actual payload sizes first:

  • Too low: nearly everything gets offloaded, meaning more blob store API calls and more latency per payload.
  • Too high: large payloads still slip into Cadence history and approach the 2 MB cap.

A reasonable starting point for most teams is 64 KB–256 KB, then adjust based on observed payload distribution.

Inline in history (no converter)
Claim-check (reference in history)
Exceeds 2 MB limit
2 MB limit
Small signal (1 KB)
1 KB
102 B
Activity result (50 KB)
50 KB
102 B
JSON report (500 KB)
500 KB
102 B
Binary blob (1.8 MB)
1.8 MB
102 B
Over the limit (3 MB)
2.9 MB (exceeds limit)
102 B

Claim-check stores only a ~100 B JSON reference in Cadence history regardless of payload size. The actual payload lives in the blob store.

Blob store options

Dev only

Writes blobs to a local directory. No credentials needed. Works out of the box with the sample repos. Not suitable for production: blobs are not shared across machines and are lost on pod restart.

store := blobstore.NewFilesystemStore("/tmp/cadence-blobs")
dc := claimcheck.NewDataConverter(store, 4*1024)

All backends implement the same BlobStore interface, so swapping is a one-line config change.

The one rule you cannot skip

Blob keys must be deterministic.

Cadence can replay a workflow from the beginning at any time (to recover from a crash, to apply a code change, or during normal task processing). When it does, ToData is called again with the same payload.

If your key is a UUID, every replay writes a new blob and orphans the old one. After enough replays, you have a storage bill full of blobs no one can find.

The fix is a single line: use SHA-256 of the serialized payload as the key. The same payload always produces the same key. The Put on replay is a no-op.

Common production pitfalls

What's next in this series

Week 3: Encrypt Cadence History Payloads (And Know What You Didn't Encrypt)

Key management, the CADENCE_ENCRYPTION_KEY environment variable, and a practical map of what AES-256-GCM encryption protects in Cadence versus what it leaves exposed. If you handle regulated data, this is the post to bookmark.

Get started