Skip to content

Add PurgeExternalPayloads RPC for large-payload blob auto-purge#76

Draft
YunchuWang wants to merge 4 commits into
mainfrom
yunchuwang-add-purge-external-payloads-rpc
Draft

Add PurgeExternalPayloads RPC for large-payload blob auto-purge#76
YunchuWang wants to merge 4 commits into
mainfrom
yunchuwang-add-purge-external-payloads-rpc

Conversation

@YunchuWang

Copy link
Copy Markdown
Member

Summary

Adds the authoritative proto definition for the large-payload blob auto-purge feature. This is the source-of-truth contract that two other PRs vendor locally and will re-sync from once this merges:

  • SDK: microsoft/durabletask-dotnet #758
  • DTS backend: AAPT-DTMB

Context

When orchestration payloads exceed a threshold they are externalized to Azure Blob as tokens blob:v1:<container>:<blobName> stored in the backend's SQL Payloads.Text column. When the backend deletes that row, the blob is orphaned (the backend has no storage credentials).

Fix: the backend soft-deletes blob-externalized payload rows; a connected worker (which has storage creds) receives the tombstoned tokens over a bidirectional stream, deletes each blob, and acks; the backend hard-deletes the row on ack. This RPC is that worker↔backend channel.

Change

Edits only protos/orchestrator_service.proto:

  • One bidirectional streaming RPC PurgeExternalPayloads(stream PayloadPurged) returns (stream TombstonedPayload) on TaskHubSidecarService, grouped with the worker-facing RPCs.
  • Two messages TombstonedPayload and PayloadPurged with exact field numbers/types matching the vendoring PRs' wire contract. Field names use camelCase to match this file's existing convention.

No language stubs were regenerated: this is a proto-only repo (SDKs consume the protos via git submodule and generate code at build time into gitignored build/ dirs; no generated stubs are checked in).

Draft — not ready for review until the vendoring PRs are aligned.

YunchuWang and others added 2 commits July 8, 2026 14:45
Adds the authoritative bidirectional streaming RPC and its two messages (TombstonedPayload, PayloadPurged) that the backend and worker use to purge externalized large-payload blobs. The backend soft-deletes blob-externalized payload rows and streams the tombstoned tokens to a connected worker (which has storage credentials); the worker deletes each blob and acks so the backend can hard-delete the row.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Pure rename of the client->server ack message; field numbers/types unchanged and wire-compatible. Reads correctly (the row is not yet purged when the worker sends it) and the ...Ack suffix signals the upstream direction in the bidi signature.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@YunchuWang YunchuWang marked this pull request as ready for review July 8, 2026 23:28
The DTFx AzureManaged SDK dials only BackendService, so it needs to drain externalized large-payload blobs via that service. Mirrors the RPC already on TaskHubSidecarService; reuses TombstonedPayload and PayloadPurgeAck, which are defined in orchestrator_service.proto (already imported here).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@YunchuWang YunchuWang marked this pull request as draft July 9, 2026 17:27
Replaces the bidirectional streaming PurgeExternalPayloads on both TaskHubSidecarService and BackendService with two unary RPCs: GetTombstonedPayloads(limit) to fetch tombstoned large-payload rows and AckPurgedPayloads(acks) to confirm blob deletion so the backend hard-deletes those rows. Adds the four request/response messages to orchestrator_service.proto; backend_service.proto reuses them via its existing import. TombstonedPayload and PayloadPurgeAck are unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
YunchuWang added a commit to microsoft/durabletask-dotnet that referenced this pull request Jul 13, 2026
Large orchestration payloads are externalized to Azure Blob Storage as
`blob:v1:<container>:<blobName>` tokens. The DTS backend stores those tokens but
cannot delete the backing blobs (it has no storage credentials) — only this SDK
can. This adds an opt-in, whole-scheduler singleton durable entity +
orchestration job (mirroring src/ExportHistory) that drains payload rows the
backend has soft-deleted and deletes their blobs, then acks so the backend can
hard-delete the rows.

Design:
- PayloadStore.DeleteAsync is virtual (default throws NotSupportedException so it
  is non-breaking for existing external subclasses); BlobPayloadStore overrides
  it to decode the token and call DeleteIfExistsAsync (idempotent).
- BlobPurgeJob (TaskEntity singleton): Create is a no-op when already Active so
  racing client processes don't disturb the running job; Run starts a fixed-id
  orchestrator.
- BlobPurgeJobOrchestrator (perpetual): fetch a batch of tombstones, delete the
  blobs with capped parallelism, ack the successful deletions (failed tokens stay
  tombstoned to retry), idle on a timer when empty, ContinueAsNew periodically.
- ExecuteBlobPurgeJobOperationOrchestrator bridges client -> entity.
- Two new unary RPCs on TaskHubSidecarService: GetTombstonedPayloads /
  AckPurgedPayloads (authoritative proto follow-up: microsoft/durabletask-protobuf#76).
- LargePayloadStorageOptions gains AutoPurge (opt-in, default false) and
  PayloadPurgeBatchSize (default 500).
- Client-side BlobPurgeJobStarter (IHostedService) ensures the singleton job when
  AutoPurge is enabled, without blocking host startup. Worker always registers the
  entity/orchestrators/activities so a client-enabled job has something to run.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
YunchuWang added a commit to microsoft/durabletask-dotnet that referenced this pull request Jul 13, 2026
Large orchestration payloads are externalized to Azure Blob Storage as
`blob:v1:<container>:<blobName>` tokens. The DTS backend stores those tokens but
cannot delete the backing blobs (it has no storage credentials) — only this SDK
can. This adds an opt-in, whole-scheduler singleton durable entity +
orchestration job (mirroring src/ExportHistory) that drains payload rows the
backend has soft-deleted and deletes their blobs, then acks so the backend can
hard-delete the rows.

Design:
- PayloadStore.DeleteAsync is virtual (default throws NotSupportedException so it
  is non-breaking for existing external subclasses); BlobPayloadStore overrides
  it to decode the token and call DeleteIfExistsAsync (idempotent).
- BlobPurgeJob (TaskEntity singleton): Create is a no-op when already Active so
  racing client processes don't disturb the running job; Run starts a fixed-id
  orchestrator.
- BlobPurgeJobOrchestrator (perpetual): fetch a batch of tombstones, delete the
  blobs with capped parallelism, ack the successful deletions (failed tokens stay
  tombstoned to retry), idle on a timer when empty, ContinueAsNew periodically.
- ExecuteBlobPurgeJobOperationOrchestrator bridges client -> entity.
- Two new unary RPCs on TaskHubSidecarService: GetTombstonedPayloads /
  AckPurgedPayloads (authoritative proto follow-up: microsoft/durabletask-protobuf#76).
- LargePayloadStorageOptions gains AutoPurge (opt-in, default false) and
  PayloadPurgeBatchSize (default 500).
- Client-side BlobPurgeJobStarter (IHostedService) ensures the singleton job when
  AutoPurge is enabled, without blocking host startup. Worker always registers the
  entity/orchestrators/activities so a client-enabled job has something to run.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
YunchuWang added a commit to microsoft/durabletask-dotnet that referenced this pull request Jul 13, 2026
Large orchestration payloads are externalized to Azure Blob Storage as
`blob:v1:<container>:<blobName>` tokens. The DTS backend stores those tokens but
cannot delete the backing blobs (it has no storage credentials) — only this SDK
can. This adds an opt-in, whole-scheduler singleton durable entity +
orchestration job (mirroring src/ExportHistory) that drains payload rows the
backend has soft-deleted and deletes their blobs, then acks so the backend can
hard-delete the rows.

Design:
- PayloadStore.DeleteAsync is virtual (default throws NotSupportedException so it
  is non-breaking for existing external subclasses); BlobPayloadStore overrides
  it to decode the token and call DeleteIfExistsAsync (idempotent).
- BlobPurgeJob (TaskEntity singleton): Create is a no-op when already Active so
  racing client processes don't disturb the running job; Run starts a fixed-id
  orchestrator.
- BlobPurgeJobOrchestrator (perpetual): fetch a batch of tombstones, delete the
  blobs with capped parallelism, ack the successful deletions (failed tokens stay
  tombstoned to retry), idle on a timer when empty, ContinueAsNew periodically.
- ExecuteBlobPurgeJobOperationOrchestrator bridges client -> entity.
- Two new unary RPCs on TaskHubSidecarService: GetTombstonedPayloads /
  AckPurgedPayloads (authoritative proto follow-up: microsoft/durabletask-protobuf#76).
- LargePayloadStorageOptions gains AutoPurge (opt-in, default false) and
  PayloadPurgeBatchSize (default 500).
- Client-side BlobPurgeJobStarter (IHostedService) ensures the singleton job when
  AutoPurge is enabled, without blocking host startup. Worker always registers the
  entity/orchestrators/activities so a client-enabled job has something to run.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.

1 participant