feat(snapshot s3): download objects in parallel within a count and byte budget - #1191
Conversation
|
Claude finished @mbevc1's task in 2m 6s —— View job Follow-up review — commits
|
Follow-up review — commits c00225c..b2f25f7Checked the 5 prior findings against the two new commits (
No new bugs, security issues, or regressions found in the lines touched by these two commits. 0 new inline comments. 🤖 Generated with Claude Code |
b2f25f7 to
bb13f0c
Compare
Follow-up review — commit
|
…te budget Objects that need content now download concurrently. A slot channel bounds how many are in flight and a weighted semaphore bounds the sum of their listed sizes, so peak temp disk stays around the budget rather than growing with the bucket; an object larger than the whole budget takes all of it and runs alone. Results are written by listing index, and the first failure cancels the shared context so in-flight transfers stop and no further one starts. Defaults are eight objects and 512 MiB, which fits the default Lambda /tmp, and the transfer manager's per-object part concurrency is lowered to three so the connection count stays modest. The root .kosli_ignore is still fetched first, since its rules decide what else to download.
…lags The parallel download limits were compile-time constants. How much temp disk and how many connections a snapshot may use depends on where it runs, from a default Lambda /tmp of 512 MiB to a fat CI runner, so both are now flags with the previous values as defaults. As with every flag, KOSLI_DOWNLOAD_BUDGET and KOSLI_DOWNLOAD_CONCURRENCY set them from the environment. `--download-budget` reads a size: a bare number is megabytes, matching how Lambda's ephemeral storage is expressed, and a K, M, G or T suffix with an optional B picks the unit, so 512, 512M, 512MB and 0.5G all mean the same. Sizes are binary. Both flags are validated before any request is made, and the budget's default string is pinned to the aws default by a test so the two cannot drift. `aws.DownloadLimits` is exported and threaded from the command through GetS3Data, which tests use to prove the values reach the fan-out.
… goroutine per object The fan-out started one goroutine per object needing content and parked all but Concurrency of them on a slot channel. Parked goroutines keep their stacks, so memory grew with the bucket: around 2000 goroutines for a 2000-object bucket, hundreds of megabytes at a hundred thousand. Exactly Concurrency workers now pull indexes from a channel fed in listing order. The byte budget, the by-index results and the first-error cancellation are unchanged; the producer stops feeding once the context is cancelled and the workers drain out. A test pins goroutines during a 2000-object run to the concurrency plus a small fixed overhead.
Parallel downloads and their flags are in, so the sentences describing them as pending state what shipped, and the status moves to Accepted.
…bject The override to three parts kept the connection product modest, but the SDK's five is well within what S3 serves, and object concurrency is the knob operators tune. The value stays pinned here so the product of objects and parts is visible in one place and does not move with an SDK upgrade.
…ode cannot Section labels that restated test rows, doc lines that restated a signature, and duplicated reasoning are gone; the remaining comments state one invariant or reason each. The test hook's comment said it ran in place of the delegate; it runs before it and can only fail it.
…y what concurrency costs in memory The pool started the full concurrency even for three objects or none. Each object in flight can also buffer up to five 8 MiB parts, which the byte budget does not count, so the type docs and flag help now say so.
Stripping the optional suffix let "5ib" fall through to the bare-number entry as megabytes and "5bb" to bytes. Only one unit letter may precede the suffix. The budget parse error is wrapped rather than flattened.
…m a download goroutine S3ParallelTestSuite.mu() referenced suite.lock without the field existing, failing vet. TestObjectsNeverLandUnderTheirKeyAndDoNotLinger's onDownload hook also called require.* from a worker goroutine, the same shape fixed elsewhere in this package: a failure there calls Goexit on that goroutine instead of failing the test, so the download silently never completes and a later, unrelated 'no content digest' error hides the real one. The hook now only records facts under a mutex; the assertions run after the call returns, on the test goroutine.
cdc1250 to
08113d1
Compare
Second half of the virtual-tree fingerprint (#1180).
kosli snapshot s3now downloads the objects that contribute to the fingerprint in parallel instead of one after another. A fixed worker pool bounds the number of objects in flight and a weighted semaphore bounds their listed bytes, so peak temp disk is the budget rather than one object; the first transport error cancels the rest. Each download still writes its digest into its own manifest slot, so the fingerprint is identical however the downloads interleave, and the pinned and attested-directory tests from #1180 hold unchanged.Two flags tune the limits, both settable as
KOSLI_environment variables like every other flag:--download-concurrency, default 8 objects in flight.--download-budget, default 512 MB of listed bytes in flight. A bare number is megabytes;K,M,GandTsuffixes with an optionalBare binary units, so8Gand8GBare the same.The transfer manager's per-object part concurrency is pinned at the SDK's default of 5, so the worst case is 40 connections. The gain is largest for buckets of many small objects, where per-request latency dominates and up to eight requests overlap; large objects were already fetched in parallel parts and gain little. The listing phase is unchanged.
The ADR moves to Accepted and its two sentences describing this work as pending now state what shipped. Still open from the #1180 review and deliberately not in this PR: a prepared-tree handle so the tree and ignore rules are resolved once for both the download set and the digest.
Closes #1167.
Checklist
charts/k8s-reporter/) updated, if needed. Note: these changes live in a separate PR