-
-
Notifications
You must be signed in to change notification settings - Fork 302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
object-store
-based Store implementation
#1661
base: main
Are you sure you want to change the base?
Conversation
Amazing @kylebarron! I'll spend some time playing with this today. |
With roeap/object-store-python#9 it should be possible to fetch multiple ranges within a file concurrently with range coalescing (using That PR also adds a |
src/zarr/v3/store/object_store.py
Outdated
async def get_partial_values( | ||
self, key_ranges: List[Tuple[str, Tuple[int, int]]] | ||
) -> List[bytes]: | ||
# TODO: use rust-based concurrency inside object-store |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How I did it in rfsspec: https://github.com/martindurant/rfsspec/blob/main/src/lib.rs#L141
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
object-store has a built-in function for this: get_ranges
. With the caveat that it only manages multiple ranges in a single file.
get_ranges also automatically handles request merging for nearby ranges in a file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I know, but mine already did the whole thing, so I am showing how I did that.
Great work @kylebarron! |
I suggest we see whether it makes any improvements first, so it's author's choice for now. |
While @rabernat has seen some impressive perf improvements in some settings when making many requests with Rust's tokio runtime, which would possibly also trickle down to a Python binding, the biggest advantage I see is improved ease of use in installation. A common hurdle I've seen is handling dependency management, especially around boto3, aioboto3, etc dependencies. Versions need to be compatible at runtime with any other libraries the user also has in their environment. And Python doesn't allow multiple versions of the same dependency at the same time in one environment. With a Python library wrapping a statically-linked Rust binary, you can remove all Python dependencies and remove this class of hardship. The underlying Rust object-store crate is stable and under open governance via the Apache Arrow project. We'll just have to wait on some discussion in object-store-python for exactly where that should live. I don't have an opinion myself on where this should live, but it should be on the order of 100 lines of code wherever it is (unless the v3 store api changes dramatically) |
👍
I want to keep an open mind about what the core stores provided by Zarr-Python are. My current thinking is that we should just do a |
This is no longer an issue, s3fs has much more relaxed deps than it used to. Furthermore, it's very likely to be already part of an installation environment. |
I agree with that. I think it is beneficial to keep the number of dependencies of core zarr-python small. But, I am open for discussion.
Sure! That is certainly useful. |
This is awesome work, thank you all!!! |
Co-authored-by: Deepak Cherian <[email protected]>
The I'd like to update this PR soonish to use that library instead. |
If the zarr group prefers object-store-rs, we can move it into the zarr-developers org, if you like. I would like to be involved in developing it, particularly if it can grow more explicit fsspec compatible functionality. |
I have a few questions because the
I like that |
This came up in the discussion at https://github.com/zarr-developers/zarr-python/pull/2426/files/5e0ffe80d039d9261517d96ce87220ce8d48e4f2#diff-bb6bb03f87fe9491ef78156256160d798369749b4b35c06d4f275425bdb6c4ad. By default, it's passed as Does it look compatible with what you need? |
Sorry for all the churn here, but I realized the actual difference seems to be that ByteRangeRequest is actually [start, length] rather than [start, end]. I guess this makes the inclusive/exclusive end debate moot, but was still super surprising to me. See #2437 for any further discussions on byte range representations. |
An update for anyone following this PR following a sync with @kylebarron today - the implementations obstore.store.LocalStore and obstore.store.MemoryStore backed Zarr stores seem good to go, but AzureStore, GCSStore, and S3Stores will require more work to make the Zarr storage backend work relative to a specified root prefix (the object_store rust library only supports relativity to the bucket). Kyle and I plan to pair on finishing up this PR in the new year. I hope the work early next year will unblock performance testing of Zarr V3 + obstore relative to Zarr V2 and Zarr V3 + icechunk, and would like to collaborate with Earthmover + others on those performance tests. |
This comment was marked as off-topic.
This comment was marked as off-topic.
@JoshCu - I think your comment is sufficiently different from the content of this PR to warrant a dedicated issue. Mind creating one and we can discuss there? |
|
||
return bool(self.store.__eq__(value.store)) | ||
|
||
def __init__(self, store: _ObjectStore, *, read_only: bool = False) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if store
is not picklable, you'll need to implement __getstate__
and __setstate__
below. The basic strategy you'll want to use is:
- in
__getstate__
, extract the needed config to recreate an the_ObjectStore
- in
__setstate__
, use the config from above to recreate the_ObjectStore
and setself.store
directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @jhamman. @kylebarron and I discussed xfailing the pickling tests in this PR and implementing __getstate__
and __setstate__
later on, since ObjectStore
could be used for non-distributed reading/writing in the meantime. Would you be open to punting this a bit down the road? IIUC Kyle was supportive of adding pickling eventually and I'd be glad to help but it doesn't seem crucial to us right now given that this store will be marked as experimental.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, let's add the __getstate__
methods with a NotImplementedError
so we can provide a clear error message when things go wrong.
Use new ByteRequest syntax and raise NotImplementedError on pickling
I released obstore 0.3 today, so I think we're primed to get this PR ready to go. The failing test is this? #1661 (comment) |
@@ -128,6 +128,7 @@ async def test_get(self, store: S, key: str, data: bytes, byte_range: ByteReques | |||
""" | |||
Ensure that data can be read from the store using the store.get method. | |||
""" | |||
data = b"\x01\x02\x03\x04" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
data = b"\x01\x02\x03\x04" |
We'll either need to xfail this or address the difference in behavior between stores mentioned in #2560, but I agree with Davis about not removing the test. Sorry this line snuck in with one of my PRs.
Amazing! There are two remaining issues that I know of:
IMO the two major decision affecting how much work is left are:
|
I'll leave this up to you. With the FsspecStore, we only test against moto (s3) and assume it works everywhere else.
I want to hear from others here at @zarr-developers/python-core-devs on this. Personally, I'd like to have it here as an "Experimental" store but it wouldn't be too hard to put in a standalone package either. |
I don't see why we would need to move the implementation elsewhere, so long as the documentation is clear about the current state. So long as it is "experimental", we are not supposing that other members of zarr can necessarily handle maintenance of this piece (not that I expect @kylebarron to abandon it for any reason). |
How mature is |
Catch some allowed exceptions
Eh, that's hard to answer. You could define maturity as the length of time between breaking releases? Yesterday's 0.3 release had a bunch of new features like improved DX and async streaming uploads, but it only had two very minor breaking changes: using
Yes, you could argue this isn't providing anything totally "new" that fsspec doesn't do. But I hope and expect it will provide performance improvements for large concurrent downloads, and thus be useful for Zarr. I'd certainly like to see similar numbers as what Earthmover found in their icechunk announcement. We have multiple efforts going on to perform obstore benchmarking. @maxrjones is doing awesome work in https://github.com/maxrjones/zarr-obstore-performance/ specific to Zarr and https://github.com/geospatial-jeff/pyasyncio-benchmark is progressing on totally-general high-throughput benchmarking. Footnotes
|
obs.store.HTTPStore, | ||
obs.store.S3Store, | ||
obs.store.LocalStore, | ||
obs.store.MemoryStore, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
obs.store.MemoryStore, | |
obs.store.MemoryStore, | |
obs.store.PrefixStore, |
For a super simple first estimate, ObjectStore loads a 3.7 GB array of shape (24, 37, 721, 1440) with chunks (1, 1, 721, 1440) in ~65% of the time required by FsspecStore (4.5s vs 7.0s with n=50; from https://github.com/maxrjones/zarr-obstore-performance). I'd like to add Icechunk to this comparison and possibly include other benchmarks, but IMO this is super promising for the value of an object-store (via obstore) backed Zarr store. |
A Zarr store based on
obstore
, which is a Python library that uses the Rustobject_store
crate under the hood.object-store is a rust crate for interoperating with remote object stores like S3, GCS, Azure, etc. See the highlights section of its docs.
obstore
maps async Rust functions to async Python functions, and is able to streamGET
andLIST
requests, which all make it a good candidate for use with the Zarr v3 Store protocol.You should be able to test this branch with the latest pre-release version of
obstore
:TODO: