Stream object downloads instead of buffering objects in memory - #829
Conversation
Fixes fsouza#828. This PR makes it so objects in the FS backed are streamed from disk instead of being loaded into memory. This drastically reduces memory usage when working with large files, especially when performing small concurrent range reads on a large file. A StreamingObject type has been added to both the fakestorage and API packages. This type contains a io.ReadSeekCloser that can be used to access object data. This change sets the stage for fixing fsouza#669 and fsouza#397. fsouza#669 isn't fixed because it will require re-working of how resumable uploads are stored (though other upload types have been made streaming in this PR). Also, fixing fsouza#397 would require changing how initial objects are specified when starting the server, which with this approach would require changing the public API. Regarding the public API: the server methods for working with objects have been changed to use StreamingObject instead of Object. This is a breaking change. It would be possible to make streaming backward compatible by adding some metadata and methods to Object (to distinguish between streaming and buffered objects), but I went with this approach because it made identifying the code that needed to be updated easier; the compiler pointed out the locations to update instead of needing to track down runtime and test errors. An additional improvement, which is compatible with the backward compatible approach, would be to add an OpenForReading method on Object so callers that are currently responsible for closing objects also explicitly open objects (which would make the responsibility clearer). Anyway, I wanted to get this initial approach up for feedback before putting in additional work.
f650f66 to
3d78ac9
Compare
3da2a5c to
b15c8a5
Compare
|
@fsouza what do you think of this? It has made a very large performance improvement in our internal testing. |
|
Our engineers have been using this change in our dev environment for about a week (probably millions of requests). Folks use Linux and macOS. It would be helpful if someone in the community could also test the change on Windows. Windows has very different file open, rename and delete semantics. I've used github.com/alexbrainman/goissue34681 (found via golang/go#34681), though I'm not sure if this works well with multiple readers. The strategy taken in this code is as follows:
This allows a slow reader (or multiple readers) to download the original file even if the backing file has been modified or deleted. |
On a quick glance, this looks sane. I'm a bit slow to respond right now, but I'll try to come back to it later today or some time tomorrow. I also don't have access to any Windows hosts, so if we don't get any volunteers the best strategy will probably be to merge the PR and wait for bug reports 🙈 |
fsouza
left a comment
There was a problem hiding this comment.
Looks good overall, just want to make sure we don't ship a breaking change in the lib API.
| Size int64 `json:"size,string"` | ||
| ContentType string `json:"contentType"` | ||
| ContentEncoding string `json:"contentEncoding"` | ||
| Content []byte `json:"-"` |
There was a problem hiding this comment.
lol didn't even realize we did this
|
@fsouza are you still interested in merging this PR? I'm curious what your opinion is about other breaking changes in this PR. You mentioned CreateObject, and @StevenACoffman changed the name back in #882, though the function signature is still different: it takes a StreamingObject. Some of the other functions also have different signatures. Are you okay with any breaking changes, or do you want the exported functions to stay backward compatible (which would mean creating new streaming variants)? |
Hey, apologies for the silence, I was on a long break, but coming back now! Yeah, I wanted to keep it fully backwards compatible. We could keep the streaming variant private unless yall are already using fake-gcs-server as a library for this. Like, keep it private unless someone actually needs it? Not sure what you think. |
No worries! And apologies for taking a while to circle back to this myself. Yeah, that seems reasonable. I'll work on reverting the existing APIs and I'll double check our usage to see if anything new needs to be exported (I suspect not). |
|
@dnerdy I'm curious on how you and @StevenACoffman would like to proceed. Should we close #882 and continue working on this PR? |
|
@fsouza The original buffered APIs have been restored:
The new streaming variants are named
Internally, the server uses streaming objects and the objects are converted to buffered objects in the restored methods. I've moved the documentation from the buffered variants to the streaming variants, to encourage their use, and updated the documentation for the buffered methods to point to the streaming variants. One additional change I made to CreateObjectStreaming is that it returns an error instead of panicking when an error occurs. I've also merged in main and updated the code to use the new metadata interface. |
fsouza
left a comment
There was a problem hiding this comment.
This looks good in general, I have some minor comments left. Thank you very much for pushing this through!
|
|
||
| if err = os.WriteFile(path, obj.Content, 0o600); err != nil { | ||
| return Object{}, err | ||
| tempFile, err := os.CreateTemp("", "fake-gcs-object") |
There was a problem hiding this comment.
If we want to guarantee that rename will work, we'll likely want to specify the rename, otherwise the temporary directory may be in a different mount point. No need to overthink it though, we could address it later if someone raises the issue :)
| @@ -0,0 +1,21 @@ | |||
| //go:build !windows | |||
| // +build !windows | |||
There was a problem hiding this comment.
Hmm now that we only need to support Go 1.18 and 1.19, we can get rid of +build (go fix should do it automatically I think).
| "path/filepath" | ||
| "time" | ||
|
|
||
| "github.com/alexbrainman/goissue34681" |
There was a problem hiding this comment.
This feels wrong lol I wonder if the author will keep that around. I guess that in the worst case it'll be cached in the goproxy and we can fork it.
Applying it myself so we can ship this. I'll run `go fix` after merging. Co-authored-by: Steve Coffman <StevenACoffman@users.noreply.github.com>
fsouza
left a comment
There was a problem hiding this comment.
Thank you very much for contributing!
Fixes #828.
This PR makes it so objects in the FS backend are streamed from disk
instead of being loaded into memory. This drastically reduces memory
usage when working with large files, especially when performing small
concurrent range reads on a large file.
A StreamingObject type has been added to both the fakestorage and
backend packages. This type contains a io.ReadSeekCloser that can be
used to access object data.
This change sets the stage for fixing #669 and #397. #669 isn't fixed
because it will require re-working of how resumable uploads are stored
(though other upload types have been made streaming in this PR). Also,
fixing #397 would require changing how initial objects are specified
when starting the server, which with this approach would require
changing the public API.
Regarding the public API: the server methods for working with objects
have been changed to use StreamingObject instead of Object. This is a
breaking change. It would be possible to make streaming backward
compatible by adding some metadata and methods to Object (to distinguish
between streaming and buffered objects), but I went with this approach
because it made identifying the code that needed to be updated easier;
the compiler pointed out the locations to update instead of needing to
track down runtime and test errors. An additional improvement, which is
compatible with the backward compatible approach, would be to add an
OpenForReading method on Object so callers that are currently
responsible for closing objects also explicitly open objects (which
would make the responsibility clearer). Anyway, I wanted to get this
initial approach up for feedback before putting in additional work.