Simplify in-memory pilorama implementation #288

Merged
fyrchik merged 5 commits from fyrchik/frostfs-node:pilorama-simplify-inmemory into master 2023-04-28 07:56:21 +00:00
3 changed files with 46 additions and 76 deletions

View file

@ -12,7 +12,7 @@ import (
// memoryForest represents multiple replicating trees sharing a single storage. // memoryForest represents multiple replicating trees sharing a single storage.
type memoryForest struct { type memoryForest struct {
// treeMap maps tree identifier (container ID + name) to the replicated log. // treeMap maps tree identifier (container ID + name) to the replicated log.
treeMap map[string]*state treeMap map[string]*memoryTree
} }
var _ Forest = (*memoryForest)(nil) var _ Forest = (*memoryForest)(nil)
@ -21,7 +21,7 @@ var _ Forest = (*memoryForest)(nil)
// TODO: this function will eventually be removed and is here for debugging. // TODO: this function will eventually be removed and is here for debugging.
func NewMemoryForest() ForestStorage { func NewMemoryForest() ForestStorage {
return &memoryForest{ return &memoryForest{
treeMap: make(map[string]*state), treeMap: make(map[string]*memoryTree),
} }
} }
@ -34,7 +34,7 @@ func (f *memoryForest) TreeMove(_ context.Context, d CIDDescriptor, treeID strin
fullID := d.CID.String() + "/" + treeID fullID := d.CID.String() + "/" + treeID
s, ok := f.treeMap[fullID] s, ok := f.treeMap[fullID]
if !ok { if !ok {
s = newState() s = newMemoryTree()
f.treeMap[fullID] = s f.treeMap[fullID] = s
} }
@ -60,7 +60,7 @@ func (f *memoryForest) TreeAddByPath(_ context.Context, d CIDDescriptor, treeID
fullID := d.CID.String() + "/" + treeID fullID := d.CID.String() + "/" + treeID
s, ok := f.treeMap[fullID] s, ok := f.treeMap[fullID]
if !ok { if !ok {
s = newState() s = newMemoryTree()
f.treeMap[fullID] = s f.treeMap[fullID] = s
} }
@ -89,6 +89,7 @@ func (f *memoryForest) TreeAddByPath(_ context.Context, d CIDDescriptor, treeID
}, },
Child: s.findSpareID(), Child: s.findSpareID(),
}) })
s.operations = append(s.operations, op)
lm[len(lm)-1] = op.Move lm[len(lm)-1] = op.Move
return lm, nil return lm, nil
} }
@ -98,7 +99,7 @@ func (f *memoryForest) TreeApply(_ context.Context, cnr cid.ID, treeID string, o
fullID := cnr.String() + "/" + treeID fullID := cnr.String() + "/" + treeID
s, ok := f.treeMap[fullID] s, ok := f.treeMap[fullID]
if !ok { if !ok {
s = newState() s = newMemoryTree()
f.treeMap[fullID] = s f.treeMap[fullID] = s
} }
@ -131,7 +132,7 @@ func (f *memoryForest) TreeGetByPath(_ context.Context, cid cid.ID, treeID strin
return nil, ErrTreeNotFound return nil, ErrTreeNotFound
} }
return s.get(attr, path, latest), nil return s.getByPath(attr, path, latest), nil
} }
// TreeGetMeta implements the Forest interface. // TreeGetMeta implements the Forest interface.
@ -142,7 +143,7 @@ func (f *memoryForest) TreeGetMeta(_ context.Context, cid cid.ID, treeID string,
return Meta{}, 0, ErrTreeNotFound return Meta{}, 0, ErrTreeNotFound
} }
return s.getMeta(nodeID), s.infoMap[nodeID].Parent, nil return s.infoMap[nodeID].Meta, s.infoMap[nodeID].Parent, nil
} }
// TreeGetChildren implements the Forest interface. // TreeGetChildren implements the Forest interface.
@ -153,11 +154,7 @@ func (f *memoryForest) TreeGetChildren(_ context.Context, cid cid.ID, treeID str
return nil, ErrTreeNotFound return nil, ErrTreeNotFound
} }
children, ok := s.childMap[nodeID] children := s.tree.getChildren(nodeID)
if !ok {
return nil, nil
}
res := make([]Node, len(children)) res := make([]Node, len(children))
copy(res, children) copy(res, children)
return res, nil return res, nil

View file

@ -322,6 +322,10 @@ func testForestTreeAddByPath(t *testing.T, s Forest) {
firstID := lm[2].Child firstID := lm[2].Child
testMeta(t, s, cid, treeID, firstID, lm[2].Parent, Meta{Time: lm[2].Time, Items: meta}) testMeta(t, s, cid, treeID, firstID, lm[2].Parent, Meta{Time: lm[2].Time, Items: meta})
// TreeAddByPath must return operations in increasing time order.
require.True(t, lm[0].Time < lm[1].Time)
require.True(t, lm[1].Time < lm[2].Time)
meta[0].Value = []byte("YYY") meta[0].Value = []byte("YYY")
lm, err = s.TreeAddByPath(context.Background(), d, treeID, AttributeFilename, []string{"path", "to"}, meta) lm, err = s.TreeAddByPath(context.Background(), d, treeID, AttributeFilename, []string{"path", "to"}, meta)
require.NoError(t, err) require.NoError(t, err)
@ -714,13 +718,6 @@ func compareForests(t *testing.T, expected, actual Forest, cid cidSDK.ID, treeID
require.True(t, ok) require.True(t, ok)
require.Equal(t, se.operations, sa.operations) require.Equal(t, se.operations, sa.operations)
require.Equal(t, se.infoMap, sa.infoMap) require.Equal(t, se.infoMap, sa.infoMap)
require.Equal(t, len(se.childMap), len(sa.childMap))
for ck, la := range sa.childMap {
le, ok := se.childMap[ck]
require.True(t, ok)
require.ElementsMatch(t, le, la)
}
} }
require.Equal(t, expected, actual, i) require.Equal(t, expected, actual, i)
} }

View file

@ -1,5 +1,7 @@
package pilorama package pilorama
import "sort"
// nodeInfo couples parent and metadata. // nodeInfo couples parent and metadata.
type nodeInfo struct { type nodeInfo struct {
Parent Node Parent Node
@ -12,42 +14,25 @@ type move struct {
Old nodeInfo Old nodeInfo
} }
// state represents state being replicated. // memoryTree represents memoryTree being replicated.
type state struct { type memoryTree struct {
operations []move operations []move
tree tree
} }
// newState constructs new empty tree. // newMemoryTree constructs new empty tree.
func newState() *state { func newMemoryTree() *memoryTree {
return &state{ return &memoryTree{
tree: *newTree(), tree: tree{
infoMap: make(map[Node]nodeInfo),
},
} }
} }
// undo un-does op and changes s in-place. // undo un-does op and changes s in-place.
func (s *state) undo(op *move) { func (s *memoryTree) undo(op *move) {
children := s.tree.childMap[op.Parent]
for i := range children {
if children[i] == op.Child {
if len(children) > 1 {
s.tree.childMap[op.Parent] = append(children[:i], children[i+1:]...)
} else {
delete(s.tree.childMap, op.Parent)
}
break
}
}
if op.HasOld { if op.HasOld {
s.tree.infoMap[op.Child] = op.Old s.tree.infoMap[op.Child] = op.Old
oldChildren := s.tree.childMap[op.Old.Parent]
for i := range oldChildren {
if oldChildren[i] == op.Child {
return
}
}
s.tree.childMap[op.Old.Parent] = append(oldChildren, op.Child)
} else { } else {
delete(s.tree.infoMap, op.Child) delete(s.tree.infoMap, op.Child)
} }
@ -55,7 +40,7 @@ func (s *state) undo(op *move) {
// Apply puts op in log at a proper position, re-applies all subsequent operations // Apply puts op in log at a proper position, re-applies all subsequent operations
// from log and changes s in-place. // from log and changes s in-place.
func (s *state) Apply(op *Move) error { func (s *memoryTree) Apply(op *Move) error {
var index int var index int
for index = len(s.operations); index > 0; index-- { for index = len(s.operations); index > 0; index-- {
if s.operations[index-1].Time <= op.Time { if s.operations[index-1].Time <= op.Time {
@ -82,7 +67,7 @@ func (s *state) Apply(op *Move) error {
} }
// do performs a single move operation on a tree. // do performs a single move operation on a tree.
func (s *state) do(op *Move) move { func (s *memoryTree) do(op *Move) move {
lm := move{ lm := move{
Move: Move{ Move: Move{
Parent: op.Parent, Parent: op.Parent,
@ -104,36 +89,23 @@ func (s *state) do(op *Move) move {
if !ok { if !ok {
p.Meta.Time = op.Time p.Meta.Time = op.Time
} else {
s.removeChild(op.Child, p.Parent)
} }
p.Meta = op.Meta p.Meta = op.Meta
p.Parent = op.Parent p.Parent = op.Parent
s.tree.infoMap[op.Child] = p s.tree.infoMap[op.Child] = p
s.tree.childMap[op.Parent] = append(s.tree.childMap[op.Parent], op.Child)
return lm return lm
} }
func (s *state) removeChild(child, parent Node) { func (s *memoryTree) timestamp(pos, size int) Timestamp {
oldChildren := s.tree.childMap[parent]
for i := range oldChildren {
if oldChildren[i] == child {
s.tree.childMap[parent] = append(oldChildren[:i], oldChildren[i+1:]...)
break
}
}
}
func (s *state) timestamp(pos, size int) Timestamp {
if len(s.operations) == 0 { if len(s.operations) == 0 {
return nextTimestamp(0, uint64(pos), uint64(size)) return nextTimestamp(0, uint64(pos), uint64(size))
} }
return nextTimestamp(s.operations[len(s.operations)-1].Time, uint64(pos), uint64(size)) return nextTimestamp(s.operations[len(s.operations)-1].Time, uint64(pos), uint64(size))
} }
func (s *state) findSpareID() Node { func (s *memoryTree) findSpareID() Node {
id := uint64(1) id := uint64(1)
for _, ok := s.infoMap[id]; ok; _, ok = s.infoMap[id] { for _, ok := s.infoMap[id]; ok; _, ok = s.infoMap[id] {
id++ id++
@ -145,14 +117,22 @@ func (s *state) findSpareID() Node {
type tree struct { type tree struct {
syncHeight uint64 syncHeight uint64
infoMap map[Node]nodeInfo infoMap map[Node]nodeInfo
childMap map[Node][]Node
} }
func newTree() *tree { func (t tree) getChildren(parent Node) []Node {
return &tree{ var children []Node
childMap: make(map[Node][]Node), for c, info := range t.infoMap {
infoMap: make(map[Node]nodeInfo), if info.Parent == parent {
children = append(children, c)
}
} }
sort.Slice(children, func(i, j int) bool {
a := t.infoMap[children[i]]
b := t.infoMap[children[j]]
return a.Meta.Time < b.Meta.Time
})
return children
} }
// isAncestor returns true if parent is an ancestor of a child. // isAncestor returns true if parent is an ancestor of a child.
@ -176,7 +156,7 @@ func (t tree) getPathPrefix(attr string, path []string) (int, Node) {
loop: loop:
for i := range path { for i := range path {
children := t.childMap[curNode] children := t.getChildren(curNode)
for j := range children { for j := range children {
meta := t.infoMap[children[j]].Meta meta := t.infoMap[children[j]].Meta
f := meta.GetAttr(attr) f := meta.GetAttr(attr)
@ -191,9 +171,10 @@ loop:
return len(path), curNode return len(path), curNode
} }
// get returns list of nodes which have the specified path from root // getByPath returns list of nodes which have the specified path from root
// descending by values of attr from meta. // descending by values of attr from meta.
func (t tree) get(attr string, path []string, latest bool) []Node { // If latest is true, only the latest node is returned.
func (t tree) getByPath(attr string, path []string, latest bool) []Node {
if len(path) == 0 { if len(path) == 0 {
return nil return nil
} }
@ -206,7 +187,7 @@ func (t tree) get(attr string, path []string, latest bool) []Node {
var nodes []Node var nodes []Node
var lastTs Timestamp var lastTs Timestamp
children := t.childMap[curNode] children := t.getChildren(curNode)
for i := range children { for i := range children {
info := t.infoMap[children[i]] info := t.infoMap[children[i]]
fileName := string(info.Meta.GetAttr(attr)) fileName := string(info.Meta.GetAttr(attr))
@ -223,8 +204,3 @@ func (t tree) get(attr string, path []string, latest bool) []Node {
return nodes return nodes
} }
// getMeta returns meta information of node n.
func (t tree) getMeta(n Node) Meta {
return t.infoMap[n].Meta
}