Skip to content

Commit 6d7d3bc

Browse files
authored
Migrate to Icechunk 1.0 ForkSessions (#742)
* Migrate to Icechunk 1.0 ForkSessions * Update Icechunk example docs
1 parent ca2beb1 commit 6d7d3bc

3 files changed

Lines changed: 36 additions & 69 deletions

File tree

cubed/icechunk.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from typing import TYPE_CHECKING, Any, List, Sequence, Union
22

33
import zarr
4-
from icechunk import Session
4+
from icechunk.distributed import merge_sessions
5+
from icechunk.session import ForkSession
56

67
from cubed import compute
78
from cubed.core.array import CoreArray
@@ -13,13 +14,12 @@
1314

1415

1516
def store_icechunk(
16-
session: Session,
1717
*,
1818
sources: Union["Array", Sequence["Array"]],
1919
targets: List[zarr.Array],
2020
executor=None,
2121
**kwargs: Any,
22-
) -> None:
22+
) -> ForkSession:
2323
if isinstance(sources, CoreArray):
2424
sources = [sources]
2525
targets = [targets] # type: ignore
@@ -62,21 +62,22 @@ def store_icechunk(
6262
**kwargs,
6363
)
6464

65-
# merge back into the session passed into this function
66-
merged_session = store_callback.session
67-
session.merge(merged_session)
65+
return store_callback.merged_sessions
6866

6967

7068
class IcechunkStoreCallback(Callback):
7169
def on_compute_start(self, event):
72-
self.session = None
70+
self.sessions = []
7371

7472
def on_task_end(self, event):
7573
result = event.result
7674
if result is None:
7775
return
78-
for store in result:
79-
if self.session is None:
80-
self.session = store.session
81-
else:
82-
self.session.merge(store.session)
76+
else:
77+
self.sessions.append(merge_sessions(*[store.session for store in result]))
78+
79+
def on_compute_end(self, event):
80+
if len(self.sessions) == 0:
81+
self.merged_sessions = None
82+
else:
83+
self.merged_sessions = merge_sessions(self.sessions)

cubed/tests/test_icechunk.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,12 @@ def test_store_icechunk(icechunk_storage, executor):
7373

7474
repo = Repository.create(storage=icechunk_storage)
7575
session = repo.writable_session("main")
76-
with session.allow_pickling():
77-
store = session.store
78-
group = zarr.group(store=store, overwrite=True)
79-
target = group.create_array(
80-
"a", shape=a.shape, dtype=a.dtype, chunks=a.chunksize
81-
)
82-
store_icechunk(session, sources=a, targets=target, executor=executor)
76+
fork = session.fork()
77+
store = fork.store
78+
group = zarr.group(store=store, overwrite=True)
79+
target = group.create_array("a", shape=a.shape, dtype=a.dtype, chunks=a.chunksize)
80+
merged_session = store_icechunk(sources=a, targets=target, executor=executor)
81+
session.merge(merged_session)
8382
session.commit("commit 1")
8483

8584
# reopen store and check contents of array

docs/examples/icechunk.md

Lines changed: 17 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ kernelspec:
66
# Icechunk
77

88
This example shows how to perform large-scale distributed writes to Icechunk using Cubed
9-
(based on the examples for using [Icechunk with Dask](https://icechunk.io/en/latest/icechunk-python/dask/)).
9+
(based on the examples for using [Icechunk with Dask](https://icechunk.io/en/latest/dask/)).
1010

1111
Install the package pre-requisites by running the following:
1212

@@ -22,8 +22,8 @@ import tempfile
2222
2323
# initialize the icechunk store
2424
storage = icechunk.local_filesystem_storage(tempfile.TemporaryDirectory().name)
25-
icechunk_repo = icechunk.Repository.create(storage)
26-
icechunk_session = icechunk_repo.writable_session("main")
25+
repo = icechunk.Repository.create(storage)
26+
session = repo.writable_session("main")
2727
```
2828

2929
## Write to Icechunk
@@ -46,7 +46,7 @@ Now create the Zarr array you will write to.
4646
import zarr
4747
4848
zarr_chunks = (10, 10)
49-
group = zarr.group(store=icechunk_session.store, overwrite=True)
49+
group = zarr.group(store=session.store, overwrite=True)
5050
5151
zarray = group.create_array(
5252
"array",
@@ -55,74 +55,41 @@ zarray = group.create_array(
5555
dtype="f8",
5656
fill_value=float("nan"),
5757
)
58+
session.commit("initialize array")
5859
```
5960

6061
Note that the chunks in the store are a divisor of the Cubed chunks. This means each individual write task is independent, and will not conflict. It is your responsibility to ensure that such conflicts are avoided.
6162

62-
Now write
63+
First remember to fork the session before re-opening the Zarr array. `store_icechunk` will merge all the remote write sessions on the cluster before returning back a single merged `ForkSession`.
6364

6465
```{code-cell} ipython3
6566
from cubed.icechunk import store_icechunk
6667
67-
store_icechunk(
68-
icechunk_session,
68+
session = repo.writable_session("main")
69+
fork = session.fork()
70+
zarray = zarr.open_array(fork.store, path="array")
71+
remote_session = store_icechunk(
6972
sources=[cubed_array],
7073
targets=[zarray]
7174
)
7275
```
7376

74-
Finally commit your changes!
75-
77+
Merge the remote session in to the local Session
7678
```{code-cell} ipython3
77-
print(icechunk_session.commit("wrote a cubed array!"))
79+
session.merge(remote_session)
7880
```
7981

80-
## Read from Icechunk
8182

82-
Use {py:func}`cubed.from_zarr` to read from Icechunk - note that no special Icechunk-specific function is needed in this case.
83+
Finally commit your changes!
8384

8485
```{code-cell} ipython3
85-
cubed.from_zarr(store=icechunk_session.store, path="array")
86+
print(session.commit("wrote a cubed array!"))
8687
```
8788

88-
## Distributed writes
89-
90-
In distributed contexts where the Session, and Zarr Array objects are sent across the network, you must opt-in to successful pickling of a writable store.
91-
`cubed.icechunk.store_icechunk` takes care of the hard bit of merging Sessions but it is required that you opt-in to pickling prior to creating the target Zarr array objects.
89+
## Read from Icechunk
9290

93-
Here is an example:
91+
Use {py:func}`cubed.from_zarr` to read from Icechunk - note that no special Icechunk-specific function is needed in this case.
9492

9593
```{code-cell} ipython3
96-
from cubed import config
97-
98-
# start a new session. Old session is readonly after committing
99-
100-
icechunk_session = icechunk_repo.writable_session("main")
101-
zarr_chunks = (10, 10)
102-
103-
# use the Cubed processes executor which requires pickling
104-
with config.set({"spec.executor_name": "processes"}):
105-
with icechunk_session.allow_pickling():
106-
cubed_array = cubed.random.random(shape, chunks=cubed_chunks)
107-
108-
group = zarr.group(
109-
store=icechunk_session.store,
110-
overwrite=True
111-
)
112-
113-
zarray = group.create_array(
114-
"array",
115-
shape=shape,
116-
chunks=zarr_chunks,
117-
dtype="f8",
118-
fill_value=float("nan"),
119-
)
120-
121-
store_icechunk(
122-
icechunk_session,
123-
sources=[cubed_array],
124-
targets=[zarray]
125-
)
126-
127-
print(icechunk_session.commit("wrote a cubed array!"))
94+
cubed.from_zarr(store=session.store, path="array")
12895
```

0 commit comments

Comments
 (0)