Skip to content
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

Feat movable tree #120

Merged
merged 114 commits into from
Oct 30, 2023
Merged

Feat movable tree #120

merged 114 commits into from
Oct 30, 2023

Conversation

Leeeon233
Copy link
Member

@Leeeon233 Leeeon233 commented Oct 12, 2023

Movable Tree

I implemented the highly-available movable tree crdt made by M. Kleppmann, et al [paper], to support the tree-structure data model. This tree structure supports the following operations:

  • Create a node.
  • Moving a node to be a descendant of another node.
  • Delete a node.
  • Attach the metadata to the node.
  • Check out any version.

Concept

I model the tree using a flat structure (HashMap<TreeID, Option<TreeID>>) and two additional defined specific root nodes, DELETED_TREE_ROOT and UNEXIST_TREE_ROOT so that all operations of the movable tree can be modeled by a move action.

  • TreeID: the unique id of a tree node, its fields are the same as ID so we can get the associated metadata container by TreeID and Map ContainerType.
  • DELETED_TREE_ROOT: The root of all deleted nodes. The delete action can be modeled as move the node to DELETED_TREE_ROOT.
  • UNEXIST_TREE_ROOT: The root of all non-existent nodes. When we create a node and then check out the previous vision, we need to delete it from the state instead of moving it to DELETED_TREE_ROOT.
  • None parent id: The parent of a root node is None.

Conflicts

Conflicts may occur when we receive updates from other peers, such as Peer A moving node a as the successor of node b, but Peer B does the opposite.

Generally speaking, our method of resolving conflicts is to arrange all operations in a unique order(Lamport and PeerID) and apply them one by one. If the operation causes a circular reference, mark the operation as uneffected(do not apply this operation and ignore its impact). After all operations are applied, it is the final state.

Specifically, when a new update is received, we will retreat the operation from back to front until the version is the LCA version. Then arrange these operations and new operations and apply them by the method mentioned above.

Performance

Profile by M2 Max.

time
1000 nodes move 10^5 times randomly 58 ms
1000 nodes checkout 10^3 times randomly 53 ms move 1000 times first
300 depth tree random checkout 10^3 times 158 ms The new node is a child node of the previous node

Note:

When making changes to a movable tree, we need to check whether the new operation will cause a cycle, so the deeper the tree, the more time-consuming it is to traverse it.
imageonline-co-linechart

Demo

An online movable-tree-based todo app can be found here.

TODO

  • more API for tree
  • remove deleted field from get_value() of tree state
    • we need to retain the deleted field to check consistency when checking out the version before deleting the node.
    • Or we could use a specific node to represent that all nodes don't exist instead of moving directly to the DELETE_ROOT
  • cache all deleted nodes of the tree state to accelerate the filtering of existing nodes.

Breaking Change

  • Encoding Format
    • add a field for caching TreeIDs

Leeeon233 and others added 26 commits October 16, 2023 15:30
# Conflicts:
#	.vscode/settings.json
#	crates/loro-common/src/error.rs
#	crates/loro-common/src/lib.rs
#	crates/loro-internal/src/arena.rs
#	crates/loro-internal/src/container.rs
#	crates/loro-internal/src/delta.rs
#	crates/loro-internal/src/diff_calc.rs
#	crates/loro-internal/src/encoding/encode_enhanced.rs
#	crates/loro-internal/src/event.rs
#	crates/loro-internal/src/fuzz/recursive_refactored.rs
#	crates/loro-internal/src/handler.rs
#	crates/loro-internal/src/loro.rs
#	crates/loro-internal/src/snapshot_encode.rs
#	crates/loro-internal/src/state.rs
#	crates/loro-internal/src/state/list_state.rs
#	crates/loro-internal/src/state/map_state.rs
#	crates/loro-internal/src/state/text_state.rs
#	crates/loro-internal/src/txn.rs
#	crates/loro-internal/src/value.rs
#	crates/loro-internal/tests/test.rs
#	crates/loro-preload/src/encode.rs
#	crates/loro-wasm/src/lib.rs
# Conflicts:
#	.vscode/settings.json
#	Cargo.lock
#	crates/loro-common/src/error.rs
#	crates/loro-common/src/lib.rs
#	crates/loro-internal/Cargo.toml
#	crates/loro-internal/examples/many_actors.rs
#	crates/loro-internal/examples/obs.rs
#	crates/loro-internal/fuzz/Cargo.lock
#	crates/loro-internal/src/arena.rs
#	crates/loro-internal/src/arena/str_arena.rs
#	crates/loro-internal/src/container.rs
#	crates/loro-internal/src/container/list/list_op.rs
#	crates/loro-internal/src/container/richtext.rs
#	crates/loro-internal/src/container/richtext/fugue_span.rs
#	crates/loro-internal/src/container/richtext/query_by_len.rs
#	crates/loro-internal/src/container/richtext/richtext_state.rs
#	crates/loro-internal/src/container/richtext/style_range_map.rs
#	crates/loro-internal/src/container/richtext/tracker.rs
#	crates/loro-internal/src/container/richtext/tracker/crdt_rope.rs
#	crates/loro-internal/src/container/richtext/tracker/id_to_cursor.rs
#	crates/loro-internal/src/container/text/mod.rs
#	crates/loro-internal/src/delta.rs
#	crates/loro-internal/src/delta/text.rs
#	crates/loro-internal/src/diff_calc.rs
#	crates/loro-internal/src/encoding/encode_changes.rs
#	crates/loro-internal/src/encoding/encode_enhanced.rs
#	crates/loro-internal/src/event.rs
#	crates/loro-internal/src/fuzz/recursive_refactored.rs
#	crates/loro-internal/src/handler.rs
#	crates/loro-internal/src/loro.rs
#	crates/loro-internal/src/oplog.rs
#	crates/loro-internal/src/snapshot_encode.rs
#	crates/loro-internal/src/state.rs
#	crates/loro-internal/src/state/list_state.rs
#	crates/loro-internal/src/state/map_state.rs
#	crates/loro-internal/src/state/richtext_state.rs
#	crates/loro-internal/src/txn.rs
#	crates/loro-internal/src/utils/bitmap.rs
#	crates/loro-internal/src/utils/string_slice.rs
#	crates/loro-internal/src/value.rs
#	crates/loro-internal/tests/test.rs
#	crates/loro-preload/src/encode.rs
#	crates/loro-wasm/src/lib.rs
@Leeeon233 Leeeon233 merged commit e01e984 into main Oct 30, 2023
1 check passed
@Leeeon233 Leeeon233 deleted the feat-movable-tree branch June 13, 2024 07:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants