Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
This commit is contained in:
Evgenii Stratonikov 2024-11-28 09:09:58 +03:00
parent 984b51a86d
commit 0bc4f6871c
Signed by: fyrchik
SSH key fingerprint: SHA256:m/TTwCzjnRkXgnzEx9X92ccxy1CcVeinOgDb3NPWWmg
3 changed files with 125 additions and 18 deletions

View file

@ -1168,13 +1168,13 @@ func (t *boltForest) TreeSortedByFilename(ctx context.Context, cid cidSDK.ID, tr
} }
type ListKeysCursor struct { type ListKeysCursor struct {
prefix string prefix []string
finished bool finished bool
stack stack stack stack
} }
func (c *ListKeysCursor) SetPrefix(prefix string) { func (c *ListKeysCursor) SetPrefix(prefix string) {
c.prefix = prefix c.prefix = strings.Split(prefix, "/")
} }
func (t *boltForest) TreeListKeys(_ context.Context, cid cidSDK.ID, treeID string, cursor ListKeysCursor, count int) ([]KeyInfo, ListKeysCursor, error) { func (t *boltForest) TreeListKeys(_ context.Context, cid cidSDK.ID, treeID string, cursor ListKeysCursor, count int) ([]KeyInfo, ListKeysCursor, error) {
@ -1198,7 +1198,7 @@ func (t *boltForest) TreeListKeys(_ context.Context, cid cidSDK.ID, treeID strin
b := treeRoot.Bucket(dataBucket) b := treeRoot.Bucket(dataBucket)
var err error var err error
result, err = t.listKeys(b, &cursor, count, cursor.prefix, MultiNode{RootID}) result, err = t.listKeys(b, &cursor, count, MultiNode{RootID})
return err return err
}) })
@ -1208,7 +1208,7 @@ func (t *boltForest) TreeListKeys(_ context.Context, cid cidSDK.ID, treeID strin
return result, cursor, metaerr.Wrap(err) return result, cursor, metaerr.Wrap(err)
} }
func (t *boltForest) listKeys(b *bbolt.Bucket, cursor *ListKeysCursor, count int, filterPrefix string, root MultiNode) ([]KeyInfo, error) { func (t *boltForest) listKeys(b *bbolt.Bucket, cursor *ListKeysCursor, count int, root MultiNode) ([]KeyInfo, error) {
var result []KeyInfo var result []KeyInfo
if cursor.stack.empty() { if cursor.stack.empty() {
cursor.stack.push(treeLevel{ cursor.stack.push(treeLevel{
@ -1232,11 +1232,18 @@ func (t *boltForest) listKeys(b *bbolt.Bucket, cursor *ListKeysCursor, count int
// } // }
for !cursor.stack.empty() { for !cursor.stack.empty() {
depth := len(cursor.stack.values)
node, _, prefix := cursor.stack.popSingle() node, _, prefix := cursor.stack.popSingle()
if strings.HasSuffix(node.filename, "/") { if strings.HasSuffix(node.filename, "/") {
h := newHeap(nil, count*5) h := newHeap(nil, count*5)
h.withSlash = true h.withSlash = true
if depth < len(cursor.prefix) {
t.fillSortedChildrenExact(b, node.id, h, cursor.prefix[depth-1])
} else if depth == len(cursor.prefix) {
t.fillSortedChildrenWithPrefix(b, node.id, h, cursor.prefix[depth-1])
} else {
t.fillSortedChildren(b, node.id, h) t.fillSortedChildren(b, node.id, h)
}
var children []heapInfo var children []heapInfo
for hi, ok := h.pop(); ok; hi, ok = h.pop() { for hi, ok := h.pop(); ok; hi, ok = h.pop() {
@ -1249,12 +1256,9 @@ func (t *boltForest) listKeys(b *bbolt.Bucket, cursor *ListKeysCursor, count int
children: children, children: children,
}) })
} }
} else { cursor.stack.trim()
} else if len(cursor.prefix) < depth {
treeKey := strings.TrimPrefix(prefix, "/") + node.filename treeKey := strings.TrimPrefix(prefix, "/") + node.filename
if !strings.HasPrefix(treeKey, filterPrefix) {
continue
}
meta, err := t.getLatestMeta(b, node.id) meta, err := t.getLatestMeta(b, node.id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1396,6 +1400,102 @@ func (t *boltForest) getChildInfo(b *bbolt.Bucket, key []byte, childID Node) (No
return childInfo, nil return childInfo, nil
} }
func (t *boltForest) fillSortedChildrenExact(b *bbolt.Bucket, nodeIDs MultiNode, h *fixedHeap, pp string) {
c := b.Cursor()
length := uint16(len(pp))
prefix := internalKeyPrefix(nil, AttributeFilename)
prefix = binary.LittleEndian.AppendUint16(prefix, length)
prefix = append(prefix, pp...)
var nodes []uint64
for k, _ := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, _ = c.Next() {
parentID := binary.LittleEndian.Uint64(k[len(k)-16:])
if !slices.Contains(nodeIDs, parentID) {
continue
}
childID := binary.LittleEndian.Uint64(k[len(k)-8:])
nodes = append(nodes, childID)
}
if len(nodes) != 0 {
h.push(nodes, pp)
}
}
func (t *boltForest) fillSortedChildrenWithPrefix(b *bbolt.Bucket, nodeIDs MultiNode, h *fixedHeap, pp string) {
c := b.Cursor()
length := uint16(len(pp))
prefix := internalKeyPrefix(nil, AttributeFilename)
offset := len(prefix)
prefix = binary.LittleEndian.AppendUint16(prefix, length)
prefix = append(prefix, pp...)
count := 0
var nodes []uint64
var lastFilename *string
for k, _ := c.Seek(prefix); len(k) > 0 && k[0] == 'i'; k, _ = c.Next() {
if len(k) < len(prefix)+16 {
continue
}
actualLength := binary.LittleEndian.Uint16(k[offset:])
childID := binary.LittleEndian.Uint64(k[len(k)-8:])
filename := string(k[offset+2 : len(k)-16])
if !strings.HasPrefix(filename, pp) {
if lastFilename != nil {
h.push(nodes, *lastFilename)
}
lastFilename = nil
nodes = nil
length = actualLength + 1
count = 0
binary.LittleEndian.PutUint16(prefix[offset:], length)
c.Seek(prefix)
c.Prev() // c.Next() will be performed by for loop
continue
}
parentID := binary.LittleEndian.Uint64(k[len(k)-16:])
if !slices.Contains(nodeIDs, parentID) {
continue
}
if lastFilename == nil {
lastFilename = &filename
nodes = append(nodes, childID)
} else if *lastFilename == filename {
nodes = append(nodes, childID)
} else {
processed := h.push(nodes, *lastFilename)
nodes = MultiNode{childID}
lastFilename = &filename
if actualLength != length {
length = actualLength
count = 1
} else if processed {
if count++; count > h.count {
lastFilename = nil
nodes = nil
length = actualLength + 1
count = 0
binary.LittleEndian.PutUint16(prefix[offset:], length)
c.Seek(prefix)
c.Prev() // c.Next() will be performed by for loop
}
}
}
}
if len(nodes) != 0 && lastFilename != nil {
h.push(nodes, *lastFilename)
}
}
func (t *boltForest) fillSortedChildren(b *bbolt.Bucket, nodeIDs MultiNode, h *fixedHeap) { func (t *boltForest) fillSortedChildren(b *bbolt.Bucket, nodeIDs MultiNode, h *fixedHeap) {
c := b.Cursor() c := b.Cursor()
prefix := internalKeyPrefix(nil, AttributeFilename) prefix := internalKeyPrefix(nil, AttributeFilename)

View file

@ -8,7 +8,6 @@ import (
"testing" "testing"
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
"github.com/google/uuid"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -44,7 +43,7 @@ func testForestTreeListKeys(t *testing.T, s *boltForest) {
for i := range keys { for i := range keys {
path := strings.Split(keys[i].Key, "/") path := strings.Split(keys[i].Key, "/")
keys[i].Meta = []KeyValue{ keys[i].Meta = []KeyValue{
{Key: AttributeVersion, Value: []byte(uuid.New().String())}, {Key: AttributeVersion, Value: []byte{byte(i)}}, // uuid.New().String())},
{Key: AttributeFilename, Value: []byte(path[len(path)-1])}, {Key: AttributeFilename, Value: []byte(path[len(path)-1])},
} }
_, err := s.TreeAddByPath(ctx, d, treeID, AttributeFilename, path[:len(path)-1], keys[i].Meta) _, err := s.TreeAddByPath(ctx, d, treeID, AttributeFilename, path[:len(path)-1], keys[i].Meta)
@ -56,14 +55,13 @@ func testForestTreeListKeys(t *testing.T, s *boltForest) {
return strings.Compare(a.Key, b.Key) return strings.Compare(a.Key, b.Key)
}) })
cursor := ListKeysCursor{}
listKeys := func(t *testing.T, cursor ListKeysCursor, count int) ([]KeyInfo, ListKeysCursor) { listKeys := func(t *testing.T, cursor ListKeysCursor, count int) ([]KeyInfo, ListKeysCursor) {
res, cursor, err := s.TreeListKeys(ctx, cid, treeID, cursor, count) res, cursor, err := s.TreeListKeys(ctx, cid, treeID, cursor, count)
require.NoError(t, err) require.NoError(t, err)
return res, cursor return res, cursor
} }
res, _ := listKeys(t, cursor, 20) res, _ := listKeys(t, ListKeysCursor{}, 20)
require.Equal(t, sortedKeys, res) require.Equal(t, sortedKeys, res)
t.Run("multiple invocations", func(t *testing.T) { t.Run("multiple invocations", func(t *testing.T) {

View file

@ -1,6 +1,8 @@
package pilorama package pilorama
import "go.etcd.io/bbolt" import (
"go.etcd.io/bbolt"
)
type treeLevel struct { type treeLevel struct {
parentPrefix string parentPrefix string
@ -13,7 +15,12 @@ type stack struct {
} }
func (s *stack) empty() bool { func (s *stack) empty() bool {
return len(s.values) == 0 for i := range s.values {
if len(s.values[i].children) != 0 {
return false
}
}
return true
} }
func (s *stack) popSingle() (heapInfo, MultiNode, string) { func (s *stack) popSingle() (heapInfo, MultiNode, string) {
@ -23,11 +30,13 @@ func (s *stack) popSingle() (heapInfo, MultiNode, string) {
head, tail := lastLevel.children[0], lastLevel.children[1:] head, tail := lastLevel.children[0], lastLevel.children[1:]
lastLevel.children = tail lastLevel.children = tail
return head, parents, prefix
}
func (s *stack) trim() {
for len(s.values) > 0 && len(s.values[len(s.values)-1].children) == 0 { for len(s.values) > 0 && len(s.values[len(s.values)-1].children) == 0 {
s.values = s.values[:len(s.values)-1] s.values = s.values[:len(s.values)-1]
} }
return head, parents, prefix
} }
func (s *stack) push(level treeLevel) { func (s *stack) push(level treeLevel) {