forked from TrueCloudLab/frostfs-node
[#948] metabase: Simplify cursor structure
Use bbolt bucket keys to seek offset in root bucket and container buckets. Signed-off-by: Alex Vanin <alexey@nspcc.ru>
This commit is contained in:
parent
468caa83d9
commit
c80c83b0b8
1 changed files with 55 additions and 36 deletions
|
@ -1,15 +1,18 @@
|
||||||
package meta
|
package meta
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
core "github.com/nspcc-dev/neofs-node/pkg/core/object"
|
core "github.com/nspcc-dev/neofs-node/pkg/core/object"
|
||||||
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||||
"go.etcd.io/bbolt"
|
"go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cursor is a type for continuous object listing.
|
// Cursor is a type for continuous object listing.
|
||||||
type Cursor struct {
|
type Cursor struct {
|
||||||
bucket uint8
|
bucketName []byte
|
||||||
address *object.Address
|
inBucketOffset []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPrm contains parameters for ListWithCursor operation.
|
// ListPrm contains parameters for ListWithCursor operation.
|
||||||
|
@ -48,12 +51,6 @@ func (l ListRes) Cursor() *Cursor {
|
||||||
return l.cursor
|
return l.cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
cursorBucketPrimary = iota
|
|
||||||
cursorBucketTombstone
|
|
||||||
cursorBucketSG
|
|
||||||
)
|
|
||||||
|
|
||||||
// ListWithCursor lists physical objects available in metabase starting from
|
// ListWithCursor lists physical objects available in metabase starting from
|
||||||
// cursor. Includes regular, tombstone and storage group objects. Does not
|
// cursor. Includes regular, tombstone and storage group objects. Does not
|
||||||
// include inhumed objects. Use cursor value from response for consecutive requests.
|
// include inhumed objects. Use cursor value from response for consecutive requests.
|
||||||
|
@ -88,55 +85,50 @@ func (db *DB) ListWithCursor(prm *ListPrm) (res *ListRes, err error) {
|
||||||
func (db *DB) listWithCursor(tx *bbolt.Tx, count int, cursor *Cursor) ([]*object.Address, *Cursor, error) {
|
func (db *DB) listWithCursor(tx *bbolt.Tx, count int, cursor *Cursor) ([]*object.Address, *Cursor, error) {
|
||||||
threshold := cursor == nil // threshold is a flag to ignore cursor
|
threshold := cursor == nil // threshold is a flag to ignore cursor
|
||||||
result := make([]*object.Address, 0, count)
|
result := make([]*object.Address, 0, count)
|
||||||
unique := make(map[string]struct{}) // do not parse the same containerID twice
|
var bucketName []byte
|
||||||
|
|
||||||
c := tx.Cursor()
|
c := tx.Cursor()
|
||||||
name, _ := c.First()
|
name, _ := c.First()
|
||||||
|
|
||||||
if !threshold {
|
if !threshold {
|
||||||
name, _ = c.Seek([]byte(cursor.address.ContainerID().String()))
|
name, _ = c.Seek(cursor.bucketName)
|
||||||
}
|
}
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
for ; name != nil; name, _ = c.Next() {
|
for ; name != nil; name, _ = c.Next() {
|
||||||
containerID := parseContainerID(name, unique)
|
containerID, postfix := parseContainerIDWithPostfix(name)
|
||||||
if containerID == nil {
|
if containerID == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
unique[containerID.String()] = struct{}{}
|
switch postfix {
|
||||||
|
case "", storageGroupPostfix, tombstonePostfix:
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
prefix := containerID.String() + "/"
|
prefix := containerID.String() + "/"
|
||||||
|
|
||||||
lookupBuckets := [...]struct {
|
result, cursor = selectNFromBucket(tx, name, prefix, result, count, cursor, threshold)
|
||||||
name []byte
|
bucketName = name
|
||||||
bucket uint8
|
if len(result) >= count {
|
||||||
}{
|
break loop
|
||||||
{primaryBucketName(containerID), cursorBucketPrimary},
|
|
||||||
{tombstoneBucketName(containerID), cursorBucketTombstone},
|
|
||||||
{storageGroupBucketName(containerID), cursorBucketSG},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, lb := range lookupBuckets {
|
// set threshold flag after first `selectNFromBucket` invocation
|
||||||
if !threshold && cursor.bucket != lb.bucket {
|
// first invocation must look for cursor object
|
||||||
continue // start from the bucket, specified in the cursor bucket
|
threshold = true
|
||||||
}
|
|
||||||
|
|
||||||
result, cursor = selectNFromBucket(tx, lb.name, prefix, result, count, cursor, threshold)
|
|
||||||
cursor.bucket = lb.bucket
|
|
||||||
if len(result) >= count {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
|
|
||||||
// set threshold flag after first `selectNFromBucket` invocation
|
|
||||||
// first invocation must look for cursor object
|
|
||||||
threshold = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(result) == 0 {
|
if len(result) == 0 {
|
||||||
return nil, nil, core.ErrEndOfListing
|
return nil, nil, core.ErrEndOfListing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new slice is much faster but less memory efficient
|
||||||
|
// we need to copy, because bucketName exists during bbolt tx
|
||||||
|
cursor.bucketName = make([]byte, len(bucketName))
|
||||||
|
copy(cursor.bucketName, bucketName)
|
||||||
|
|
||||||
return result, cursor, nil
|
return result, cursor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,8 +155,10 @@ func selectNFromBucket(tx *bbolt.Tx,
|
||||||
c := bkt.Cursor()
|
c := bkt.Cursor()
|
||||||
k, _ := c.First()
|
k, _ := c.First()
|
||||||
|
|
||||||
|
offset := cursor.inBucketOffset
|
||||||
|
|
||||||
if !threshold {
|
if !threshold {
|
||||||
c.Seek([]byte(cursor.address.ObjectID().String()))
|
c.Seek(offset)
|
||||||
k, _ = c.Next() // we are looking for objects _after_ the cursor
|
k, _ = c.Next() // we are looking for objects _after_ the cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +170,7 @@ func selectNFromBucket(tx *bbolt.Tx,
|
||||||
if err := a.Parse(prefix + string(k)); err != nil {
|
if err := a.Parse(prefix + string(k)); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
cursor.address = a
|
offset = k
|
||||||
if inGraveyard(tx, a) > 0 {
|
if inGraveyard(tx, a) > 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -184,5 +178,30 @@ func selectNFromBucket(tx *bbolt.Tx,
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// new slice is much faster but less memory efficient
|
||||||
|
// we need to copy, because offset exists during bbolt tx
|
||||||
|
cursor.inBucketOffset = make([]byte, len(offset))
|
||||||
|
copy(cursor.inBucketOffset, offset)
|
||||||
|
|
||||||
return to, cursor
|
return to, cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseContainerIDWithPostfix(name []byte) (*cid.ID, string) {
|
||||||
|
var (
|
||||||
|
containerID = cid.New()
|
||||||
|
containerIDStr = string(name)
|
||||||
|
postfix string
|
||||||
|
)
|
||||||
|
|
||||||
|
ind := strings.Index(string(name), invalidBase58String)
|
||||||
|
if ind > 0 {
|
||||||
|
postfix = containerIDStr[ind:]
|
||||||
|
containerIDStr = containerIDStr[:ind]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := containerID.Parse(containerIDStr); err != nil {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return containerID, postfix
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue