You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Client uploads send a file straight from the browser to storage, bypassing the Payload server. The server still reads the file afterward, to save it locally or to read metadata such as width and height. It previously always downloaded the whole file into one in-memory buffer for that step, with no size limit. This is a real risk for the multi-gigabyte files that client uploads exist to support. The server now reads only the bytes each save operation actually needs, and streams any full-file read straight to disk instead of memory.
Why
Azure's client-upload support removes the old five-gigabyte upload ceiling, so a single upload can now be far larger than the server's available memory. Re-downloading that whole file into a buffer, just to check its size or resave it unchanged, does not scale with that change.
How
A new content-requirement check decides how much of a file the server needs before it fetches anything: nothing, when the client-reported metadata is enough; a small byte range, when only the image dimensions are needed; or the full file, when local storage needs the real bytes, the file will be resized or reformatted, or a configured mime-type allow list needs to inspect the content.
A full-file read now streams straight to a temporary file on disk, replacing the single in-memory buffer.
An unmodified temporary file that only needs saving to disk-disabled storage is left untouched, instead of being read into memory and written back unchanged.
Fixed a check for the disableLocalStorage option. Collections that leave it unset, the most common case, could take the reduced-fetch path meant only for disabled local storage. That risked saving a truncated or empty file.
Fixed the byte-range probe's request clone. It broke native request properties that real storage handlers read, such as an abort signal. Every adapter in this repository that reads that property, including S3 and Azure, crashed against a client upload needing only its image dimensions.
Corrected the list of image formats treated as animated. It skipped multi-page TIFF files and wrongly flagged every AVIF file as animated, even single-frame ones.
Testing
Added tests for the new content-requirement decision and the streaming behavior. Added integration tests that complete a real client upload against the S3 and Azure storage adapters for a file needing only its dimensions, the exact path the request-clone fix above corrects. No integration test exercised that path before, which is how the bug shipped unnoticed.
Alternatives considered
Tried splitting a full-file read into repeated smaller range requests, on the idea that shorter-lived reads would free memory sooner. Measured memory during the change and found no improvement, so it was not kept.
Related work
Related to #17318 and #17319, which enabled Azure client uploads larger than 5 GB.
The reason will be displayed to describe this comment to others. Learn more.
I might be missing where this gets accounted for, but does the content requirement here know about request-level uploadEdits? I am wondering about a scenario where a collection which should enter the header only path is followed up by a request that includes a crop. Could we end up passing only the header bytes into cropImage? Would crop/resize edits need to promote this to a full fetch?
I'm worried about a timing problem here where the decision for how much content to fetch only takes into account the collection configuration and MIME type which results in Sharp later processing truncated bytes.
The reason will be displayed to describe this comment to others. Learn more.
Our storage adapters set disableLocalStorage: true, so I think their upload paths avoid this read. Is this branch mainly supporting custom upload handlers that keep local storage enabled?
If so, it looks like we stream the upload to a temp file and then load the whole thing back into memory here before saving it locally which walks us back into the same issue we are trying to avoid I think.
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
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
Client uploads send a file straight from the browser to storage, bypassing the Payload server. The server still reads the file afterward, to save it locally or to read metadata such as width and height. It previously always downloaded the whole file into one in-memory buffer for that step, with no size limit. This is a real risk for the multi-gigabyte files that client uploads exist to support. The server now reads only the bytes each save operation actually needs, and streams any full-file read straight to disk instead of memory.
Why
Azure's client-upload support removes the old five-gigabyte upload ceiling, so a single upload can now be far larger than the server's available memory. Re-downloading that whole file into a buffer, just to check its size or resave it unchanged, does not scale with that change.
How
disableLocalStorageoption. Collections that leave it unset, the most common case, could take the reduced-fetch path meant only for disabled local storage. That risked saving a truncated or empty file.Testing
Added tests for the new content-requirement decision and the streaming behavior. Added integration tests that complete a real client upload against the S3 and Azure storage adapters for a file needing only its dimensions, the exact path the request-clone fix above corrects. No integration test exercised that path before, which is how the bug shipped unnoticed.
Alternatives considered
Tried splitting a full-file read into repeated smaller range requests, on the idea that shorter-lived reads would free memory sooner. Measured memory during the change and found no improvement, so it was not kept.
Related work
Related to #17318 and #17319, which enabled Azure client uploads larger than 5 GB.