Skip to content

feat(deploy): auto-drop large deployment payload blobs after a successful deploy - #1496

Merged
kriszyp merged 3 commits into
mainfrom
kris/1495-deployment-payload-auto-drop
Jun 29, 2026
Merged

kriszyp merged 3 commits into
mainfrom
kris/1495-deployment-payload-auto-drop

Conversation

@kriszyp

@kriszyp kriszyp commented Jun 25, 2026 •

Copy link
Copy Markdown
Member

Closes #1495 (part 1 of 2).

Summary

After a deploy succeeds and every peer has installed from the replicated payload_blob, the upload tarball is no longer needed. For payloads larger than deployment_payloadRetention_maxSize (new config param, default 10 MiB) the origin drops payload_blob as part of the DeploymentRecorder's single terminal write. Because that null reference replicates, the blob is reclaimed cluster-wide — each node unlinks its local file via the existing RecordEncoder retained-blob check (the #641 path). Metadata (payload_size, payload_hash, event_log, status) is retained for the audit trail, and a payload_dropped event is recorded.

Purpose

We never actually reclaimed deploy payload blobs after replication — they accumulated on every node (observed: hundreds of orphaned payloads, multiple GB on a preprod cluster). This adds the threshold-based reclamation we'd intended.

Where to look / things to weigh

  • components/operations.js (success branch of deployComponent): the drop is gated on payload_size > threshold and getFailedPeers().length === 0, so a deploy that only reached "success" because ignore_replication_errors masked a failed peer keeps its payload for inspection/retry.
  • Cluster-wide reclamation via replication is intentional — one origin decision nulls the blob everywhere. For the post-deploy case this is desired (the payload's job is done); calling it out because it's the load-bearing semantic.
  • Threshold default 10 MiB, configurable; set very high to retain all payloads. Explicit 0 means "always drop"; non-numeric/blank/unset falls back to the default.
  • DeploymentRecorder.dropPayload() only mutates the in-memory record so the null folds into finish()'s terminal write (avoids an extra put and the Replication: out-of-order full update can revert a newer record (non-convergence) #1170 out-of-order-revert window).

Open follow-up (Part 2)

Issue #1495 also asks for a disk-pressure storage-reclamation hook (evict oldest payloads when space is low). The framework already exists (server/storageReclamation.ts onStorageReclamation); the eviction policy and the "one node's disk pressure reclaims audit blobs cluster-wide" semantics deserve their own PR + decision.

Gemini review also flagged a related gap that Part 2 should cover: a deploy where a peer was offline/failed keeps its payload (correct — that peer may still need it), but there is no later trigger to reclaim it once the cluster converges. The conservative keep-on-failure here is deliberate for Part 1; eventual/retroactive reclamation belongs in the Part 2 hook.

Tests

  • Unit: DeploymentRecorder.dropPayload (drop / no-op / after-finish / metadata-retention / sealed-terminal-write).
  • Integration: deploy-payload-reclaim.test.ts forces the threshold to 1 byte and asserts payload_blob_present === false with metadata + payload_dropped event retained after a successful deploy (polls get_deployment to a terminal status — no fixed sleep). Retain-side (default threshold keeps small payloads) is covered by the existing deploy-tracking.test.ts.

Cross-model reviewed (Codex + Gemini). All actionable findings fixed: ignore-errors gate, blank/coerced-config zero-threshold, and the integration-test sleep flake. Declined with rationale: record/row rename (pre-existing class convention) and human-readable byte units (raw bytes matches the sibling operationsApi_componentFile_maxSize).

🤖 Generated by Claude Opus 4.8.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request implements a payload reclamation feature that drops the deployment payload tarball for successful, large deployments to reclaim storage, while retaining audit metadata. It introduces a configurable threshold deployment_payloadRetention_maxSize (defaulting to 10 MiB) and adds comprehensive unit and integration tests. The review feedback suggests updating the integration test to pass the configuration value as a string to properly verify the coercion logic.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread integrationTests/deploy/deploy-payload-reclaim.test.ts Outdated
@claude

claude Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

@kriszyp
kriszyp marked this pull request as ready for review June 26, 2026 00:32

@Ethan-Arrowood Ethan-Arrowood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM — this is a clean, well-reasoned fix for the payload accumulation problem.

What I checked and liked:

  • dropPayload() is tight: no-ops if finished or already blob-less, returns the freed size, and only mutates the in-memory record so the null folds into finish()'s single terminal write — nicely avoids an extra put and the #1170 out-of-order-revert window.
  • The two retention guards are exactly the right call. Failed deploys never reach the branch, and gating on getFailedPeers().length === 0 means a deploy that only "succeeded" because ignore_replication_errors masked a down peer keeps its payload for retry/inspection.
  • getPayloadRetentionMaxSize() coercion is thorough — boolean/array/unset/blank fall back to the default, numeric strings are honored, explicit 0 means always-drop. Good defensive handling.
  • Great test coverage: the unit cases cover drop / no-op / unknown-size / post-finish / sealed-terminal-write, and the integration test forces the threshold to '1' as a string (exercising the coercion path) and polls to terminal status instead of sleeping.

I'm on board with the cluster-wide reclaim-via-replication semantic — for the post-deploy case the payload's job is genuinely done everywhere, and keeping it on failed-peer deploys is the right conservative default. Scoping the disk-pressure hook and retroactive reclaim to Part 2 makes sense.

One non-blocking note: the two red integration shards (Bun 5/6, Node 24 6/6) are unrelated job-queue flakes — csv_file_load 120s job timeout, concurrent claim, Job queue lifecycle, none of which touch deployment-payload code. Worth a re-run to get a green board before merge, but not a code concern.

Approving — nice work.

sent with Claude Opus 4.8

kriszyp and others added 3 commits June 29, 2026 09:22
…sful deploy

Once a deploy completes successfully and every peer has installed from the
replicated payload_blob, the tarball is no longer needed. For payloads above
deployment_payloadRetention_maxSize (default 10 MiB) the origin drops the
payload_blob as part of the recorder's terminal write: this unlinks the file
locally and replicates the null so peers drop their copies too, reclaiming the
bytes cluster-wide. Metadata (payload_size, payload_hash, event_log) is retained
for the audit trail, and a payload_dropped event is recorded.

The drop is gated on success and on no failed peers, so a deploy that only
reached success because ignore_replication_errors masked a failed peer keeps its
payload for inspection/retry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…test

Cross-model review (Gemini) follow-ups:
- getPayloadRetentionMaxSize now only accepts a number or numeric string; a
  boolean/array/blank value falls back to the default instead of Number()
  coercing true→1 or false/[]/""→0 (a near-/always-drop threshold).
- Replace the fixed sleep(200) in deploy-payload-reclaim with a poll on
  get_deployment until the row reaches a terminal status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ercion

Exercises getPayloadRetentionMaxSize's string-coercion path end-to-end, the
shape an env-var/string-sourced config override actually arrives in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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.

Auto-delete large deployment payload blobs after deploy; storage reclamation for older payloads

2 participants