Skip to content

Home / guides / moving-nodes

Moving and Renaming Nodes#

Made a typo in an array name? Created data in the wrong group? With traditional cloud storage, fixing these mistakes means downloading the entire dataset, writing it to a new location, and deleting the original—potentially gigabytes of transfers for a one-character fix.

With Icechunk, renaming or moving any node is instant—just a small metadata update. See the move API reference for details.

Moving Nodes vs Moving Chunks

This page covers moving nodes (arrays and groups) in the hierarchy. To move chunks within an array (for rolling windows, etc.), see Moving Chunks.

Basic Usage#

import icechunk as ic
import zarr
import numpy as np

# Create a repository with some data
repo = ic.Repository.create(ic.in_memory_storage())
session = repo.writable_session("main")
root = zarr.group(session.store)
root.create_group("data/raw")
arr = root.create_array("data/raw/temperature", shape=(100,), dtype="f4")
arr[:] = np.random.randn(100)
session.commit("Initial structure")

print("Before:")
print(root.tree())
Before:
/
└── data
    └── raw
        └── temperature (100,) float32
# Move requires a rearrange session
session = repo.rearrange_session("main")
session.move("/data/raw", "/data/v1")
session.commit("Renamed raw to v1")

root = zarr.group(repo.writable_session("main").store)
print("After:")
print(root.tree())
After:
/
└── data
    └── v1
        └── temperature (100,) float32

Rearrange Sessions#

Node operations require a rearrange session—a special session type for structural changes only. This separation keeps your commit history clean: data modifications and structural reorganizations are distinct commits.

# Regular session for data
session = repo.writable_session("main")
arr[:] = new_data
session.commit("Updated data")

# Rearrange session for structure
session = repo.rearrange_session("main")
session.move("/old/path", "/new/path")
session.commit("Reorganized")

Groups Move with Children#

When you move a group, everything inside moves with it:

repo = ic.Repository.create(ic.in_memory_storage())
session = repo.writable_session("main")
root = zarr.group(session.store)
root.create_group("experiments/exp001")
root.create_array("experiments/exp001/results", shape=(10,), dtype="f4")
root.create_array("experiments/exp001/config", shape=(5,), dtype="i4")
session.commit("Create experiment")

print("Before:")
print(root.tree())
Before:
/
└── experiments
    └── exp001
        ├── config (5,) int32
        └── results (10,) float32
# Create the destination parent first
session = repo.writable_session("main")
root = zarr.group(session.store)
root.create_group("archive")
session.commit("Create archive group")

session = repo.rearrange_session("main")
session.move("/experiments/exp001", "/archive/exp001")
session.commit("Archive experiment")

root = zarr.open_group(repo.readonly_session(branch="main").store, mode="r")
print("After:")
print(root.tree())
After:
/
├── archive
│   └── exp001
│       ├── config (5,) int32
│       └── results (10,) float32
└── experiments

No Overwriting#

Moves will not overwrite existing nodes—delete the destination first if needed:

repo = ic.Repository.create(ic.in_memory_storage())
session = repo.writable_session("main")
root = zarr.group(session.store)
root.create_array("source", shape=(10,), dtype="f4")
root.create_array("destination", shape=(10,), dtype="f4")
session.commit("Create arrays")

session = repo.rearrange_session("main")
try:
    session.move("/source", "/destination")
except ic.IcechunkError as e:
    print(f"IcechunkError: {e}")
IcechunkError:   x session error: move cannot overwrite existing node at `/destination`
  | 
  | context:
  |    0: icechunk::session::move_node
  |            with from=/source to=/destination
  |              at icechunk/src/session.rs:813
  | 

Constraints#

Icechunk's move is a rename primitive — it never synthesizes groups. That means:

  1. The destination's parent must already exist. move("/a", "/x/y/z") fails if /x or /x/y don't exist. Create them first in a regular writable_session, then do the move in a rearrange_session.
  2. A node cannot be moved into itself or any of its own descendants. move("/a", "/a/c") is rejected — such a move is not representable (the node would need to be both an ancestor and a descendant of itself). To nest a group's contents under a new descendant, create the new group yourself and move each child into it explicitly.
  3. Moves do not overwrite. If a node already exists at the destination, the move is rejected — move will not silently replace it. Delete or rename the existing node first if you want it gone.
  4. The destination's parent must be a group. move("/a", "/arr/x") fails when /arr is an array, since arrays cannot have children.

Async API#

session = await repo.rearrange_session_async("main")
await session.move_async("/old/path", "/new/path")
await session.commit_async("Moved node")