diff --git a/allocator.go b/allocator.go index 2f8a789..78a3fed 100644 --- a/allocator.go +++ b/allocator.go @@ -28,25 +28,25 @@ type Allocator struct { config AllocatorConfig data []byte zeroNode []byte - nextNodeToAllocate uint64 + nextNodeToAllocate NodeAddress } // Node returns node bytes. -func (a *Allocator) Node(n uint64) []byte { - return a.data[n*a.config.NodeSize : (n+1)*a.config.NodeSize] +func (a *Allocator) Node(nodeAddress NodeAddress) []byte { + return a.data[uint64(nodeAddress)*a.config.NodeSize : uint64(nodeAddress+1)*a.config.NodeSize] } // Allocate allocates new node. -func (a *Allocator) Allocate() (uint64, []byte) { +func (a *Allocator) Allocate() (NodeAddress, []byte) { return a.allocate(a.zeroNode) } // Copy allocates new node and copies content from existing one. -func (a *Allocator) Copy(data []byte) (uint64, []byte) { +func (a *Allocator) Copy(data []byte) (NodeAddress, []byte) { return a.allocate(data) } -func (a *Allocator) allocate(copyFrom []byte) (uint64, []byte) { +func (a *Allocator) allocate(copyFrom []byte) (NodeAddress, []byte) { n := a.nextNodeToAllocate a.nextNodeToAllocate++ node := a.Node(n) @@ -54,11 +54,46 @@ func (a *Allocator) allocate(copyFrom []byte) (uint64, []byte) { return n, node } -// NewNodeAllocator creates new node allocator. -func NewNodeAllocator[T comparable](allocator *Allocator) (NodeAllocator[T], error) { - headerSize := uint64(unsafe.Sizeof(NodeHeader{})) +// NewDeallocator returns new deallocator. +func NewDeallocator( + snapshotID SnapshotID, + deallocationLists *Space[SnapshotID, NodeAddress], + listNodeAllocator ListNodeAllocator, +) Deallocator { + return Deallocator{ + snapshotID: snapshotID, + deallocationLists: deallocationLists, + listNodeAllocator: listNodeAllocator, + } +} + +// Deallocator tracks nodes to deallocate. +type Deallocator struct { + snapshotID SnapshotID + deallocationLists *Space[SnapshotID, NodeAddress] + listNodeAllocator ListNodeAllocator +} + +// Deallocate adds node to the deallocation list. +func (d Deallocator) Deallocate(nodeAddress NodeAddress, srcSnapshotID SnapshotID) { + listNodeAddress, _ := d.deallocationLists.Get(srcSnapshotID) + list := NewList(ListConfig{ + SnapshotID: d.snapshotID, + Item: listNodeAddress, + NodeAllocator: d.listNodeAllocator, + Deallocator: d, + }) + list.Add(nodeAddress) + if list.config.Item != listNodeAddress { + d.deallocationLists.Set(srcSnapshotID, list.config.Item) + } +} + +// NewSpaceNodeAllocator creates new space node allocator. +func NewSpaceNodeAllocator[T comparable](allocator *Allocator) (SpaceNodeAllocator[T], error) { + headerSize := uint64(unsafe.Sizeof(SpaceNodeHeader{})) if headerSize >= allocator.config.NodeSize { - return NodeAllocator[T]{}, errors.New("node size is too small") + return SpaceNodeAllocator[T]{}, errors.New("node size is too small") } stateOffset := headerSize + headerSize%uint64Length @@ -70,7 +105,7 @@ func NewNodeAllocator[T comparable](allocator *Allocator) (NodeAllocator[T], err numOfItems := spaceLeft / (itemSize + 1) // 1 is for slot state if numOfItems == 0 { - return NodeAllocator[T]{}, errors.New("node size is too small") + return SpaceNodeAllocator[T]{}, errors.New("node size is too small") } numOfItems, _ = highestPowerOfTwo(numOfItems) spaceLeft -= numOfItems @@ -79,10 +114,10 @@ func NewNodeAllocator[T comparable](allocator *Allocator) (NodeAllocator[T], err numOfItems = spaceLeft / itemSize numOfItems, bitsPerHop := highestPowerOfTwo(numOfItems) if numOfItems == 0 { - return NodeAllocator[T]{}, errors.New("node size is too small") + return SpaceNodeAllocator[T]{}, errors.New("node size is too small") } - return NodeAllocator[T]{ + return SpaceNodeAllocator[T]{ allocator: allocator, numOfItems: int(numOfItems), stateOffset: stateOffset, @@ -92,8 +127,8 @@ func NewNodeAllocator[T comparable](allocator *Allocator) (NodeAllocator[T], err }, nil } -// NodeAllocator converts nodes from bytes to objects. -type NodeAllocator[T comparable] struct { +// SpaceNodeAllocator converts nodes from bytes to space objects. +type SpaceNodeAllocator[T comparable] struct { allocator *Allocator numOfItems int @@ -104,41 +139,96 @@ type NodeAllocator[T comparable] struct { } // Get returns object for node. -func (na NodeAllocator[T]) Get(n uint64) ([]byte, Node[T]) { - node := na.allocator.Node(n) +func (na SpaceNodeAllocator[T]) Get(nodeAddress NodeAddress) ([]byte, SpaceNode[T]) { + node := na.allocator.Node(nodeAddress) return node, na.project(node) } // Allocate allocates new object. -func (na NodeAllocator[T]) Allocate() (uint64, Node[T]) { +func (na SpaceNodeAllocator[T]) Allocate() (NodeAddress, SpaceNode[T]) { n, node := na.allocator.Allocate() return n, na.project(node) } // Copy allocates copy of existing object. -func (na NodeAllocator[T]) Copy(data []byte) (uint64, Node[T]) { +func (na SpaceNodeAllocator[T]) Copy(data []byte) (NodeAddress, SpaceNode[T]) { n, node := na.allocator.Copy(data) return n, na.project(node) } // Index returns element index based on hash. -func (na NodeAllocator[T]) Index(hash uint64) uint64 { - return hash & na.indexMask +func (na SpaceNodeAllocator[T]) Index(hash Hash) uint64 { + return uint64(hash) & na.indexMask } // Shift shifts bits in hash. -func (na NodeAllocator[T]) Shift(hash uint64) uint64 { +func (na SpaceNodeAllocator[T]) Shift(hash Hash) Hash { return hash >> na.bitsPerHop } -func (na NodeAllocator[T]) project(node []byte) Node[T] { - return Node[T]{ - Header: photon.FromBytes[NodeHeader](node), +func (na SpaceNodeAllocator[T]) project(node []byte) SpaceNode[T] { + return SpaceNode[T]{ + Header: photon.FromBytes[SpaceNodeHeader](node), States: photon.SliceFromBytes[State](node[na.stateOffset:], na.numOfItems), Items: photon.SliceFromBytes[T](node[na.itemOffset:], na.numOfItems), } } +// NewListNodeAllocator creates new list node allocator. +func NewListNodeAllocator(allocator *Allocator) (ListNodeAllocator, error) { + headerSize := uint64(unsafe.Sizeof(ListNodeHeader{})) + if headerSize >= allocator.config.NodeSize { + return ListNodeAllocator{}, errors.New("node size is too small") + } + + stateOffset := headerSize + headerSize%uint64Length + spaceLeft := allocator.config.NodeSize - stateOffset + + numOfItems := spaceLeft / uint64Length + if numOfItems < 2 { + return ListNodeAllocator{}, errors.New("node size is too small") + } + + return ListNodeAllocator{ + allocator: allocator, + numOfItems: int(numOfItems), + itemOffset: allocator.config.NodeSize - spaceLeft, + }, nil +} + +// ListNodeAllocator converts nodes from bytes to list objects. +type ListNodeAllocator struct { + allocator *Allocator + + numOfItems int + itemOffset uint64 +} + +// Get returns object for node. +func (na ListNodeAllocator) Get(nodeAddress NodeAddress) ([]byte, ListNode) { + node := na.allocator.Node(nodeAddress) + return node, na.project(node) +} + +// Allocate allocates new object. +func (na ListNodeAllocator) Allocate() (NodeAddress, ListNode) { + n, node := na.allocator.Allocate() + return n, na.project(node) +} + +// Copy allocates copy of existing object. +func (na ListNodeAllocator) Copy(data []byte) (NodeAddress, ListNode) { + n, node := na.allocator.Copy(data) + return n, na.project(node) +} + +func (na ListNodeAllocator) project(node []byte) ListNode { + return ListNode{ + Header: photon.FromBytes[ListNodeHeader](node), + Items: photon.SliceFromBytes[NodeAddress](node[na.itemOffset:], na.numOfItems), + } +} + func highestPowerOfTwo(n uint64) (uint64, uint64) { var m uint64 = 1 var p uint64 diff --git a/go.mod b/go.mod index 2c41a45..58bf0ac 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/outofforest/quantum -go 1.22 +go 1.23 require ( github.com/cespare/xxhash v1.1.0 diff --git a/list.go b/list.go new file mode 100644 index 0000000..3b22487 --- /dev/null +++ b/list.go @@ -0,0 +1,118 @@ +package quantum + +// ListConfig stores list configuration. +type ListConfig struct { + SnapshotID SnapshotID + Item NodeAddress + NodeAllocator ListNodeAllocator + Deallocator Deallocator +} + +// NewList creates new list. +func NewList(config ListConfig) *List { + return &List{ + config: config, + } +} + +// List represents the list of node addresses. +type List struct { + config ListConfig +} + +// Add adds address to the list. +func (l *List) Add(nodeAddress NodeAddress) { + if l.config.Item == 0 { + newNodeAddress, newNode := l.config.NodeAllocator.Allocate() + newNode.Items[0] = nodeAddress + newNode.Header.SnapshotID = l.config.SnapshotID + newNode.Header.NumOfItems = 1 + l.config.Item = newNodeAddress + + return + } + listNodeData, listNode := l.config.NodeAllocator.Get(l.config.Item) + if listNode.Header.NumOfItems+listNode.Header.NumOfSideLists < uint64(len(listNode.Items)) { + if listNode.Header.SnapshotID < l.config.SnapshotID { + newNodeAddress, newNode := l.config.NodeAllocator.Copy(listNodeData) + newNode.Header.SnapshotID = l.config.SnapshotID + oldNodeAddress := l.config.Item + l.config.Item = newNodeAddress + l.config.Deallocator.Deallocate(oldNodeAddress, listNode.Header.SnapshotID) + listNode = newNode + } + + listNode.Items[listNode.Header.NumOfItems] = nodeAddress + listNode.Header.NumOfItems++ + + return + } + + newNodeAddress, newNode := l.config.NodeAllocator.Allocate() + newNode.Items[0] = nodeAddress + newNode.Items[len(newNode.Items)-1] = l.config.Item + newNode.Header.SnapshotID = l.config.SnapshotID + newNode.Header.NumOfItems = 1 + newNode.Header.NumOfSideLists = 1 + l.config.Item = newNodeAddress +} + +// Attach attaches another list. +func (l *List) Attach(nodeAddress NodeAddress) { + if l.config.Item == 0 { + l.config.Item = nodeAddress + return + } + listNodeData, listNode := l.config.NodeAllocator.Get(l.config.Item) + if listNode.Header.NumOfItems+listNode.Header.NumOfSideLists < uint64(len(listNode.Items)) { + if listNode.Header.SnapshotID < l.config.SnapshotID { + newNodeAddress, newNode := l.config.NodeAllocator.Copy(listNodeData) + newNode.Header.SnapshotID = l.config.SnapshotID + oldNodeAddress := l.config.Item + l.config.Item = newNodeAddress + l.config.Deallocator.Deallocate(oldNodeAddress, listNode.Header.SnapshotID) + listNode = newNode + } + + listNode.Items[uint64(len(listNode.Items))-listNode.Header.NumOfSideLists-1] = nodeAddress + listNode.Header.NumOfSideLists++ + + return + } + + newNodeAddress, newNode := l.config.NodeAllocator.Allocate() + newNode.Items[uint64(len(listNode.Items))-1] = l.config.Item + newNode.Items[uint64(len(listNode.Items))-2] = nodeAddress + newNode.Header.SnapshotID = l.config.SnapshotID + newNode.Header.NumOfSideLists = 2 + l.config.Item = newNodeAddress +} + +// Iterator returns iterator iterating over elements in the list. +func (l *List) Iterator() func(func(NodeAddress) bool) { + return func(yield func(NodeAddress) bool) { + if l.config.Item == 0 { + return + } + + // FIXME (wojciech): Optimize heap allocations. + stack := []NodeAddress{l.config.Item} + for { + if len(stack) == 0 { + return + } + + n := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + _, listNode := l.config.NodeAllocator.Get(n) + for i := range listNode.Header.NumOfItems { + if !yield(listNode.Items[i]) { + return + } + } + + stack = append(stack, listNode.Items[uint64(len(listNode.Items))-listNode.Header.NumOfSideLists:]...) + } + } +} diff --git a/list_test.go b/list_test.go new file mode 100644 index 0000000..0de019c --- /dev/null +++ b/list_test.go @@ -0,0 +1,54 @@ +package quantum + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestList(t *testing.T) { + allocator := NewAllocator(AllocatorConfig{ + TotalSize: 1024 * 1024, + NodeSize: 512, + }) + allocator.Allocate() + nodeAllocator, err := NewListNodeAllocator(allocator) + require.NoError(t, err) + + list := NewList(ListConfig{ + NodeAllocator: nodeAllocator, + }) + + nItems := 3*nodeAllocator.numOfItems + nodeAllocator.numOfItems/2 + originalItems := []NodeAddress{} + + var item NodeAddress + for range 10 { + for range nItems { + item++ + list.Add(item) + originalItems = append(originalItems, item) + } + list2 := NewList(ListConfig{ + NodeAllocator: nodeAllocator, + }) + for range nItems { + item++ + list2.Add(item) + originalItems = append(originalItems, item) + } + list.Attach(list2.config.Item) + } + + items := make([]NodeAddress, 0, len(originalItems)) + for item := range list.Iterator() { + items = append(items, item) + } + + sort.Slice(items, func(i, j int) bool { + return items[i] < items[j] + }) + + require.Equal(t, originalItems, items) +} diff --git a/snapshot.go b/snapshot.go index 5b8cd0c..216cd7d 100644 --- a/snapshot.go +++ b/snapshot.go @@ -12,12 +12,12 @@ import ( type spaceToCommit struct { HashMod *uint64 PInfo ParentInfo - OriginalItem uint64 + OriginalItem NodeAddress } // SnapshotConfig stores snapshot configuration. type SnapshotConfig struct { - SnapshotID uint64 + SnapshotID SnapshotID Allocator *Allocator } @@ -33,31 +33,42 @@ func NewSnapshot(config SnapshotConfig) (Snapshot, error) { return Snapshot{}, errors.Errorf("snapshot %d does not exist", config.SnapshotID) } - pointerNodeAllocator, err := NewNodeAllocator[uint64](config.Allocator) + pointerNodeAllocator, err := NewSpaceNodeAllocator[NodeAddress](config.Allocator) if err != nil { return Snapshot{}, err } - snapshotInfoNodeAllocator, err := NewNodeAllocator[DataItem[uint64, SnapshotInfo]](config.Allocator) + snapshotInfoNodeAllocator, err := NewSpaceNodeAllocator[DataItem[SnapshotID, SnapshotInfo]](config.Allocator) if err != nil { return Snapshot{}, err } - snapshots, err := NewSpace[uint64, SnapshotInfo](SpaceConfig[uint64, SnapshotInfo]{ + spaceInfoNodeAllocator, err := NewSpaceNodeAllocator[DataItem[SpaceID, SpaceInfo]](config.Allocator) + if err != nil { + return Snapshot{}, err + } + + snapshotToNodeNodeAllocator, err := NewSpaceNodeAllocator[DataItem[SnapshotID, NodeAddress]](config.Allocator) + if err != nil { + return Snapshot{}, err + } + + listNodeAllocator, err := NewListNodeAllocator(config.Allocator) + if err != nil { + return Snapshot{}, err + } + + snapshots := NewSpace[SnapshotID, SnapshotInfo](SpaceConfig[SnapshotID, SnapshotInfo]{ SnapshotID: config.SnapshotID, HashMod: &singularityNode.SnapshotRoot.HashMod, SpaceRoot: ParentInfo{ State: lo.ToPtr(singularityNode.SnapshotRoot.State), - Item: lo.ToPtr[uint64](singularityNode.SnapshotRoot.Node), + Item: lo.ToPtr(singularityNode.SnapshotRoot.Node), }, PointerNodeAllocator: pointerNodeAllocator, DataNodeAllocator: snapshotInfoNodeAllocator, }) - if err != nil { - return Snapshot{}, err - } - var snapshotInfo SnapshotInfo if singularityNode.SnapshotRoot.State != stateFree { snapshotID := config.SnapshotID @@ -72,12 +83,24 @@ func NewSnapshot(config SnapshotConfig) (Snapshot, error) { } } - spaceInfoNodeAllocator, err := NewNodeAllocator[DataItem[uint64, SpaceInfo]](config.Allocator) - if err != nil { - return Snapshot{}, err - } - - spaces, err := NewSpace[uint64, SpaceInfo](SpaceConfig[uint64, SpaceInfo]{ + deallocator := NewDeallocator( + config.SnapshotID, + NewSpace[SnapshotID, NodeAddress](SpaceConfig[SnapshotID, NodeAddress]{ + SnapshotID: config.SnapshotID, + HashMod: &snapshotInfo.DeallocationRoot.HashMod, + SpaceRoot: ParentInfo{ + State: lo.ToPtr(snapshotInfo.DeallocationRoot.State), + Item: lo.ToPtr(snapshotInfo.DeallocationRoot.Node), + }, + PointerNodeAllocator: pointerNodeAllocator, + DataNodeAllocator: snapshotToNodeNodeAllocator, + }), + listNodeAllocator, + ) + snapshots.config.Deallocator = deallocator + deallocator.deallocationLists.config.Deallocator = deallocator + + spaces := NewSpace[SpaceID, SpaceInfo](SpaceConfig[SpaceID, SpaceInfo]{ SnapshotID: config.SnapshotID, HashMod: &snapshotInfo.SpaceRoot.HashMod, SpaceRoot: ParentInfo{ @@ -86,32 +109,31 @@ func NewSnapshot(config SnapshotConfig) (Snapshot, error) { }, PointerNodeAllocator: pointerNodeAllocator, DataNodeAllocator: spaceInfoNodeAllocator, + Deallocator: deallocator, }) - if err != nil { - return Snapshot{}, err - } - s := Snapshot{ config: config, - spaces: spaces, snapshots: snapshots, - spacesToCommit: map[uint64]spaceToCommit{}, + spaces: spaces, + deallocator: deallocator, + spacesToCommit: map[SpaceID]spaceToCommit{}, } return s, nil } // Snapshot represents the state at particular point in time. type Snapshot struct { - config SnapshotConfig - spaces *Space[uint64, SpaceInfo] - snapshots *Space[uint64, SnapshotInfo] + config SnapshotConfig + snapshots *Space[SnapshotID, SnapshotInfo] + spaces *Space[SpaceID, SpaceInfo] + deallocator Deallocator - spacesToCommit map[uint64]spaceToCommit + spacesToCommit map[SpaceID]spaceToCommit } // Space retrieves information about space. -func (s Snapshot) Space(spaceID uint64) SpaceInfo { +func (s Snapshot) Space(spaceID SpaceID) SpaceInfo { spaceRootInfo, exists := s.spaces.Get(spaceID) if !exists { return SpaceInfo{} @@ -122,7 +144,7 @@ func (s Snapshot) Space(spaceID uint64) SpaceInfo { // Commit commits current snapshot and returns next one. func (s Snapshot) Commit() (Snapshot, error) { - spaces := make([]uint64, 0, len(s.spacesToCommit)) + spaces := make([]SpaceID, 0, len(s.spacesToCommit)) for spaceID := range s.spacesToCommit { spaces = append(spaces, spaceID) } @@ -144,6 +166,11 @@ func (s Snapshot) Commit() (Snapshot, error) { s.snapshots.Set(s.config.SnapshotID, SnapshotInfo{ SnapshotID: s.config.SnapshotID, + DeallocationRoot: SpaceInfo{ + HashMod: *s.deallocator.deallocationLists.config.HashMod, + State: *s.deallocator.deallocationLists.config.SpaceRoot.State, + Node: *s.deallocator.deallocationLists.config.SpaceRoot.Item, + }, SpaceRoot: SpaceInfo{ HashMod: *s.spaces.config.HashMod, State: *s.spaces.config.SpaceRoot.State, @@ -167,7 +194,7 @@ func (s Snapshot) Commit() (Snapshot, error) { } // GetSpace retrieves space from snapshot. -func GetSpace[K, V comparable](spaceID uint64, snapshot Snapshot) (*Space[K, V], error) { +func GetSpace[K, V comparable](spaceID SpaceID, snapshot Snapshot) (*Space[K, V], error) { space, exists := snapshot.spacesToCommit[spaceID] if !exists { spaceInfo := snapshot.Space(spaceID) @@ -182,12 +209,12 @@ func GetSpace[K, V comparable](spaceID uint64, snapshot Snapshot) (*Space[K, V], snapshot.spacesToCommit[spaceID] = space } - pointerNodeAllocator, err := NewNodeAllocator[uint64](snapshot.config.Allocator) + pointerNodeAllocator, err := NewSpaceNodeAllocator[NodeAddress](snapshot.config.Allocator) if err != nil { return nil, err } - dataNodeAllocator, err := NewNodeAllocator[DataItem[K, V]](snapshot.config.Allocator) + dataNodeAllocator, err := NewSpaceNodeAllocator[DataItem[K, V]](snapshot.config.Allocator) if err != nil { return nil, err } @@ -198,5 +225,6 @@ func GetSpace[K, V comparable](spaceID uint64, snapshot Snapshot) (*Space[K, V], SpaceRoot: space.PInfo, PointerNodeAllocator: pointerNodeAllocator, DataNodeAllocator: dataNodeAllocator, - }) + Deallocator: snapshot.deallocator, + }), nil } diff --git a/snapshot_test.go b/snapshot_test.go index 3743cc5..004e68e 100644 --- a/snapshot_test.go +++ b/snapshot_test.go @@ -37,7 +37,7 @@ const spaceID = 0x00 func TestCollisions(t *testing.T) { for _, set := range collisions { - m := map[uint64]struct{}{} + m := map[Hash]struct{}{} for _, i := range set { m[hashKey(i, 0)] = struct{}{} } @@ -203,7 +203,7 @@ func TestFindCollisions(t *testing.T) { fmt.Println("started") - m := map[uint64][]int{} + m := map[Hash][]int{} for i := range math.MaxInt { h := hashKey(i, 0) if h2 := m[h]; len(h2) == 4 { @@ -218,7 +218,7 @@ func TestFindCollisions(t *testing.T) { func collect(space *Space[int, int]) []int { values := []int{} typeStack := []State{*space.config.SpaceRoot.State} - nodeStack := []uint64{*space.config.SpaceRoot.Item} + nodeStack := []NodeAddress{*space.config.SpaceRoot.Item} for { if len(nodeStack) == 0 { diff --git a/space.go b/space.go index e75c61f..4d5d9d8 100644 --- a/space.go +++ b/space.go @@ -8,19 +8,20 @@ import ( // SpaceConfig stores space configuration. type SpaceConfig[K, V comparable] struct { - SnapshotID uint64 + SnapshotID SnapshotID HashMod *uint64 SpaceRoot ParentInfo - PointerNodeAllocator NodeAllocator[uint64] - DataNodeAllocator NodeAllocator[DataItem[K, V]] + PointerNodeAllocator SpaceNodeAllocator[NodeAddress] + DataNodeAllocator SpaceNodeAllocator[DataItem[K, V]] + Deallocator Deallocator } // NewSpace creates new space. -func NewSpace[K, V comparable](config SpaceConfig[K, V]) (*Space[K, V], error) { +func NewSpace[K, V comparable](config SpaceConfig[K, V]) *Space[K, V] { return &Space[K, V]{ config: config, defaultValue: *new(V), - }, nil + } } // Space represents the substate where values V are stored by key K. @@ -84,11 +85,11 @@ func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) { for { switch *pInfo.State { case stateFree: - dataNodeIndex, dataNode := s.config.DataNodeAllocator.Allocate() + dataNodeAddress, dataNode := s.config.DataNodeAllocator.Allocate() dataNode.Header.SnapshotID = s.config.SnapshotID *pInfo.State = stateData - *pInfo.Item = dataNodeIndex + *pInfo.Item = dataNodeAddress index := s.config.DataNodeAllocator.Index(item.Hash) dataNode.States[index] = stateData @@ -98,10 +99,12 @@ func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) { case stateData: dataNodeData, dataNode := s.config.DataNodeAllocator.Get(*pInfo.Item) if dataNode.Header.SnapshotID < s.config.SnapshotID { - newNodeIndex, newNode := s.config.DataNodeAllocator.Copy(dataNodeData) + newNodeAddress, newNode := s.config.DataNodeAllocator.Copy(dataNodeData) + newNode.Header.SnapshotID = s.config.SnapshotID + oldNodeAddress := *pInfo.Item + *pInfo.Item = newNodeAddress + s.config.Deallocator.Deallocate(oldNodeAddress, dataNode.Header.SnapshotID) dataNode = newNode - dataNode.Header.SnapshotID = s.config.SnapshotID - *pInfo.Item = newNodeIndex } if dataNode.Header.HashMod > 0 { item.Hash = hashKey(item.Key, dataNode.Header.HashMod) @@ -134,10 +137,12 @@ func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) { default: pointerNodeData, pointerNode := s.config.PointerNodeAllocator.Get(*pInfo.Item) if pointerNode.Header.SnapshotID < s.config.SnapshotID { - newNodeIndex, newNode := s.config.PointerNodeAllocator.Copy(pointerNodeData) + newNodeAddress, newNode := s.config.PointerNodeAllocator.Copy(pointerNodeData) + newNode.Header.SnapshotID = s.config.SnapshotID + oldNodeAddress := *pInfo.Item + *pInfo.Item = newNodeAddress + s.config.Deallocator.Deallocate(oldNodeAddress, pointerNode.Header.SnapshotID) pointerNode = newNode - pointerNode.Header.SnapshotID = s.config.SnapshotID - *pInfo.Item = newNodeIndex } if pointerNode.Header.HashMod > 0 { item.Hash = hashKey(item.Key, pointerNode.Header.HashMod) @@ -157,14 +162,14 @@ func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) { func (s *Space[K, V]) redistributeNode(pInfo ParentInfo) { _, dataNode := s.config.DataNodeAllocator.Get(*pInfo.Item) - pointerNodeIndex, pointerNode := s.config.PointerNodeAllocator.Allocate() - *pointerNode.Header = NodeHeader{ + pointerNodeAddress, pointerNode := s.config.PointerNodeAllocator.Allocate() + *pointerNode.Header = SpaceNodeHeader{ SnapshotID: s.config.SnapshotID, HashMod: dataNode.Header.HashMod, } *pInfo.State = statePointer - *pInfo.Item = pointerNodeIndex + *pInfo.Item = pointerNodeAddress for i, state := range dataNode.States { if state == stateFree { @@ -175,17 +180,17 @@ func (s *Space[K, V]) redistributeNode(pInfo ParentInfo) { } } -func hashKey[K comparable](key K, hashMod uint64) uint64 { - var hash uint64 +func hashKey[K comparable](key K, hashMod uint64) Hash { + var hash Hash p := photon.NewFromValue[K](&key) if hashMod == 0 { - hash = xxhash.Sum64(p.B) + hash = Hash(xxhash.Sum64(p.B)) } else { // FIXME (wojciech): Remove heap allocation b := make([]byte, uint64Length+len(p.B)) copy(b, photon.NewFromValue(&hashMod).B) copy(b[uint64Length:], photon.NewFromValue[K](&key).B) - hash = xxhash.Sum64(b) + hash = Hash(xxhash.Sum64(b)) } if isTesting { @@ -195,6 +200,6 @@ func hashKey[K comparable](key K, hashMod uint64) uint64 { return hash } -func testHash(hash uint64) uint64 { +func testHash(hash Hash) Hash { return hash & 0x7fffffff } diff --git a/types.go b/types.go index 7fe0665..ba2dbaa 100644 --- a/types.go +++ b/types.go @@ -11,22 +11,49 @@ const ( statePointer ) -// NodeHeader is the header common to all node types. -type NodeHeader struct { +type ( + // SnapshotID is the type for snapshot ID. SnapshotID uint64 + + // NodeAddress is the type for node address. + NodeAddress uint64 + + // Hash is the type for key hash. + Hash uint64 + + // SpaceID is the type for space ID. + SpaceID uint64 +) + +// SpaceNodeHeader is the header common to all space node types. +type SpaceNodeHeader struct { + SnapshotID SnapshotID HashMod uint64 } -// Node represents data stored inside node. -type Node[T comparable] struct { - Header *NodeHeader +// SpaceNode represents data stored inside space node. +type SpaceNode[T comparable] struct { + Header *SpaceNodeHeader States []State Items []T } +// ListNodeHeader is the header of the list node. +type ListNodeHeader struct { + SnapshotID SnapshotID + NumOfItems uint64 + NumOfSideLists uint64 +} + +// ListNode represents data stored inside list node. +type ListNode struct { + Header *ListNodeHeader + Items []NodeAddress +} + // DataItem stores single key-value pair. type DataItem[K, V comparable] struct { - Hash uint64 + Hash Hash Key K Value V } @@ -34,24 +61,25 @@ type DataItem[K, V comparable] struct { // ParentInfo stores state and item of the slot used to retrieve node from parent pointer. type ParentInfo struct { State *State - Item *uint64 + Item *NodeAddress } // SpaceInfo stores information required to retrieve space. type SpaceInfo struct { State State - Node uint64 + Node NodeAddress HashMod uint64 } // SnapshotInfo stores information required to retrieve snapshot. type SnapshotInfo struct { - SnapshotID uint64 - SpaceRoot SpaceInfo + SnapshotID SnapshotID + DeallocationRoot SpaceInfo + SpaceRoot SpaceInfo } // SingularityNode is the root of the store. type SingularityNode struct { - SnapshotID uint64 + SnapshotID SnapshotID SnapshotRoot SpaceInfo }