diff --git a/pkg/services/tree/heap/ordered_slice.go b/pkg/services/tree/heap/ordered_slice.go new file mode 100644 index 000000000..6752bb685 --- /dev/null +++ b/pkg/services/tree/heap/ordered_slice.go @@ -0,0 +1,47 @@ +package heap + +import ( + "bytes" + "sort" + + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama" +) + +type OrderedSlice struct { + nodes []pilorama.NodeInfo + sorted bool +} + +func NewOrderedSlice() *OrderedSlice { + return new(OrderedSlice) +} + +func (s *OrderedSlice) Insert(infos ...pilorama.NodeInfo) { + s.sorted = false + s.nodes = append(s.nodes, infos...) +} + +func (s *OrderedSlice) IsEmpty() bool { + return len(s.nodes) == 0 +} + +func (s *OrderedSlice) ExtractMin() pilorama.NodeInfo { + if !s.sorted { + sortByFilename(s.nodes) + s.sorted = true + } + + node := s.nodes[0] + s.nodes = s.nodes[1:] + return node +} + +func sortByFilename(nodes []pilorama.NodeInfo) { + if len(nodes) == 0 { + return + } + less := func(i, j int) bool { + return bytes.Compare(nodes[i].Meta.GetAttr(pilorama.AttributeFilename), nodes[j].Meta.GetAttr(pilorama.AttributeFilename)) < 0 + } + sort.Slice(nodes, less) +} diff --git a/pkg/services/tree/heap/unordered_slice.go b/pkg/services/tree/heap/unordered_slice.go new file mode 100644 index 000000000..300216537 --- /dev/null +++ b/pkg/services/tree/heap/unordered_slice.go @@ -0,0 +1,25 @@ +package heap + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama" +) + +type UnorderedSlice []pilorama.NodeInfo + +func NewUnorderedSlice() *UnorderedSlice { + return &UnorderedSlice{} +} + +func (s *UnorderedSlice) Insert(infos ...pilorama.NodeInfo) { + *s = append(*s, infos...) +} + +func (s *UnorderedSlice) IsEmpty() bool { + return len(*s) == 0 +} + +func (s *UnorderedSlice) ExtractMin() pilorama.NodeInfo { + node := (*s)[0] + *s = (*s)[1:] + return node +} diff --git a/pkg/services/tree/service.go b/pkg/services/tree/service.go index 59b01796e..b11459822 100644 --- a/pkg/services/tree/service.go +++ b/pkg/services/tree/service.go @@ -5,11 +5,11 @@ import ( "context" "errors" "fmt" - "sort" "sync" "sync/atomic" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/tree/heap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" cidSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" @@ -440,29 +440,48 @@ func (s *Service) GetSubTree(req *GetSubTreeRequest, srv TreeService_GetSubTreeS return getSubTree(srv.Context(), srv, cid, b, s.forest) } +type Heap interface { + Insert(...pilorama.NodeInfo) + IsEmpty() bool + ExtractMin() pilorama.NodeInfo +} + +func makeHeap(ordered bool) Heap { + if ordered { + return heap.NewOrderedSlice() + } + return heap.NewUnorderedSlice() +} + func getSubTree(ctx context.Context, srv TreeService_GetSubTreeServer, cid cidSDK.ID, b *GetSubTreeRequest_Body, forest pilorama.Forest) error { + ordered, err := needOrder(b.GetOrderBy().GetDirection()) + if err != nil { + return err + } + // Traverse the tree in a DFS manner. Because we need to support arbitrary depth, // recursive implementation is not suitable here, so we maintain explicit stack. m, p, err := forest.TreeGetMeta(ctx, cid, b.GetTreeId(), b.GetRootId()) if err != nil { return err } - stack := [][]pilorama.NodeInfo{{{ + + stack := []Heap{makeHeap(ordered)} + stack[0].Insert(pilorama.NodeInfo{ ID: b.GetRootId(), Meta: m, ParentID: p, - }}} + }) for { if len(stack) == 0 { break - } else if len(stack[len(stack)-1]) == 0 { + } else if stack[len(stack)-1].IsEmpty() { stack = stack[:len(stack)-1] continue } - node := stack[len(stack)-1][0] - stack[len(stack)-1] = stack[len(stack)-1][1:] + node := stack[len(stack)-1].ExtractMin() err = srv.Send(&GetSubTreeResponse{ Body: &GetSubTreeResponse_Body{ @@ -481,33 +500,24 @@ func getSubTree(ctx context.Context, srv TreeService_GetSubTreeServer, cid cidSD if err != nil { return err } - children, err = sortByFilename(children, b.GetOrderBy().GetDirection()) - if err != nil { - return err - } if len(children) != 0 { - stack = append(stack, children) + h := makeHeap(ordered) + h.Insert(children...) + stack = append(stack, h) } } } return nil } -func sortByFilename(nodes []pilorama.NodeInfo, d GetSubTreeRequest_Body_Order_Direction) ([]pilorama.NodeInfo, error) { +func needOrder(d GetSubTreeRequest_Body_Order_Direction) (bool, error) { switch d { case GetSubTreeRequest_Body_Order_None: - return nodes, nil + return false, nil case GetSubTreeRequest_Body_Order_Asc: - if len(nodes) == 0 { - return nodes, nil - } - less := func(i, j int) bool { - return bytes.Compare(nodes[i].Meta.GetAttr(pilorama.AttributeFilename), nodes[j].Meta.GetAttr(pilorama.AttributeFilename)) < 0 - } - sort.Slice(nodes, less) - return nodes, nil + return true, nil default: - return nil, fmt.Errorf("unsupported order direction: %s", d.String()) + return false, fmt.Errorf("unsupported order direction: %s", d.String()) } }