feat(storage): opt-in eager merge so the first restore can be a direct download - #267
Merged
Merged
Conversation
Created with AI. Verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With EAGER_MERGE=true the merge starts right after upload completion instead of on the first download, so the first restore can be a direct download. On S3 the merge runs inside the bucket via UploadPartCopy when every part but the last is at least 5 MiB; otherwise the existing streaming merge runs immediately. Lazy and eager merges share one lease-fenced runner. Upload completion lists the parts folder once to derive part count, size and per-part sizes. Refs #262 Created with AI. Verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Folds up to 32 sources per call into a top-level temp object and composes `merged` in one final call. No minimum part size, so GCS never falls back to streaming for size reasons. Created with AI. Verified by a human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
LouisHaftmann
force-pushed
the
feat/eager-merge
branch
from
September 13, 2026 13:09
3e6a2ab to
dc214bf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📝 Summary
With
ENABLE_DIRECT_DOWNLOADSthe first restore of every cache entry is still proxied through the server, because the merge only runs on the first download and a signed URL needs the merged object. Contributor jrarmstro raised this in #262 with a fork that composes parts on S3 viaUploadPartCopy.This adds
EAGER_MERGE(defaultfalse). When on, the merge starts right after upload completion. On S3 it runs inside the bucket when every part but the last is at least 5 MiB, none is above 5 GiB and there are at most 10,000 parts. On GCS it runs inside the bucket viacomposewith no size limits. Otherwise, and always on the filesystem driver, the existing streaming merge runs immediately. The strategy is chosen up front from the part sizes listed at completion, no exception-driven fallback. Completion waits only for the merge lease, not the merge.Lazy and eager merges now share one lease-fenced runner, so ADR-0002 and ADR-0004 apply unchanged. ADR-0009 documents the decision and the costs. Buildx uploads 1 MiB blocks and always takes the streaming path.
📦 Changes
EAGER_MERGEenv var, Helmconfig.eagerMerge(chart 1.4.0)StorageAdapter.composePartson S3 (CreateMultipartUpload/UploadPartCopy/CompleteMultipartUpload, aborted on failure) and GCS (compose, 32 sources per call folded through a top-level temp object)StorageAdapter.listFolderreplacesgetFolderSize; upload completion does one LIST for part count, size and per-part sizesstartMergerunner used by the download path and the eager path✅ Verification
pnpm type-check: passpnpm lint: passpnpm test:run(filesystem + sqlite): 43 passed, 4 skippedVITEST_STORAGE_DRIVER=s3 pnpm test:run(minio + sqlite): 45 passed, 2 skipped, compose path coveredVITEST_STORAGE_DRIVER=gcs pnpm test:run(fake-gcs-server + sqlite): 44 passed, 3 skipped, compose and 33-part fold covered🔍 Reviews
Unfixed, out of scope:
countFilesInFoldercould becomelistFolder().length(pre-existing, many test callers);catch (err: any)matches five pre-existing sites; a warn log when eviction removes the location before the eager merge lease is taken (rare, harmless); the compose limits could be named constants.🔗 Issues
Closes #262
cleanup:partsruns, for every entry, not only downloaded ones.sizeBytesstill counts parts only.AbortIncompleteMultipartUploadlifecycle rule. On GCS it leaves a top-level temp object that orphaned-storage cleanup reclaims.EAGER_MERGEadded.🤖 Generated with Claude Code