Repository navigation
Conversation
Merging this PR will not alter performance
|
454fdc6 to
1cc552d
Compare
1cc552d to
a2f09f3
Compare
|
a2f09f3 to
c60ccf8
Compare
c60ccf8 to
b2177c0
Compare
|
Want your agent to iterate on Greptile's feedback? Start a greploop in Claude Code and it will work through the open comments and keep going until this PR reviews clean. |
b2177c0 to
148e6dd
Compare
adriencaccia
left a comment
There was a problem hiding this comment.
Seen together, let's remove the single part upload path and always use multipart, albeit with a single part for archives under 64MiB.
S3 rejects single uploads above 5 GiB, and a single connection to S3 only reaches about 20-25 MiB/s on GitHub-hosted runners, so large profile archives were slow or impossible to upload. Every archive is now sent as an S3 multipart upload, replacing the single-request upload: about two parts per concurrent upload, each between 16 MiB and 256 MiB, so a small archive is a single part. The md5 of every part is computed in the same pass as the archive md5 and sent in the upload metadata (version 12) as `profileMultipart`. The API answers with `multipartUploadUrls`: the parts are uploaded 8 at a time (overridable with `CODSPEED_UPLOAD_CONCURRENCY`), each with its own retries, then the upload is completed with the part ETags in order. This applies to both on-disk and in-memory (gzip) archives. This requires an upload endpoint that accepts metadata version 12 and answers with `multipartUploadUrls`. Walltime profile folders above 5 GiB are no longer gzipped on disk to fit in a single request, and the runner no longer caps the archive size itself: the upload endpoint rejects archives above its limit, with the reason shown in the runner output, so the limit can change without a runner release. Archives are now hashed while streaming on the blocking thread pool, instead of being read whole into memory on the async runtime. Closes COD-3700 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148e6dd to
810a9e5
Compare
Upload profile archives of 64 MiB and more as S3 multipart uploads, with parts sent concurrently.
S3 rejects a single upload request above 5 GiB, so large walltime and memory profile archives could not be uploaded (walltime folders above 5 GiB were gzipped on disk to try to fit). Even below that limit, a single connection to S3 only reaches about 20-25 MiB/s on GitHub-hosted runners, so a 1 GiB archive took close to a minute to upload.
How it works
profileMultipart(size,partSize,partMd5s).multipartUploadUrls(partUrls,completeUrl) instead ofuploadUrl. Each part URL is presigned with the part'sContent-MD5, so S3 checks every part. Parts are uploaded 8 at a time, each with its own retries, then the ETags are sent in part order to the completion URL. S3 can report a failed completion with a 200 status, so the response body is also checked for an error.upload::s3module.CODSPEED_UPLOAD_CONCURRENCYoverrides the number of concurrent part uploads.Archives below 64 MiB keep the single upload request, and their metadata is unchanged apart from the version.
Other changes
Measurements
Concurrency sweep on a 6 GiB walltime archive (25 parts of 256 MiB), which led to the default of 8:
ubuntu-latestSmaller archives with the final part layout, concurrency 1 (close to the previous single request) vs 8:
ubuntu-latestubuntu-latestThe backend support for
profileMultipartis not released yet, so the multipart path cannot be verified end to end against production for now.Closes COD-3700