forked from TrueCloudLab/frostfs-node
[#1516] metabase: Optimize ListWithCursor
for long listings
Cache buckets outside of the main loop and allocate memory for the resulting offset only once. ``` name old time/op new time/op delta ListWithCursor/1_item-8 6.45µs ±14% 5.79µs ±11% -10.24% (p=0.002 n=10+10) ListWithCursor/10_items-8 20.9µs ±17% 17.3µs ± 9% -17.27% (p=0.000 n=10+10) ListWithCursor/100_items-8 153µs ±12% 131µs ± 9% -14.63% (p=0.000 n=10+10) name old alloc/op new alloc/op delta ListWithCursor/1_item-8 2.31kB ± 0% 1.91kB ± 0% -17.46% (p=0.000 n=10+10) ListWithCursor/10_items-8 6.94kB ± 0% 5.50kB ± 0% -20.78% (p=0.000 n=8+8) ListWithCursor/100_items-8 53.3kB ± 0% 41.5kB ± 0% -22.18% (p=0.000 n=10+10) name old allocs/op new allocs/op delta ListWithCursor/1_item-8 40.0 ± 0% 34.0 ± 0% -15.00% (p=0.000 n=10+10) ListWithCursor/10_items-8 121 ± 0% 100 ± 0% -17.36% (p=0.000 n=10+10) ListWithCursor/100_items-8 930 ± 0% 758 ± 0% -18.49% (p=0.000 n=10+10) ``` Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
a93373fe71
commit
81f925d5a0
1 changed files with 36 additions and 35 deletions
|
@ -1,8 +1,8 @@
|
|||
package meta
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
|
@ -102,11 +102,17 @@ func (db *DB) listWithCursor(tx *bbolt.Tx, result []oid.Address, count int, curs
|
|||
}
|
||||
|
||||
var containerID cid.ID
|
||||
var offset []byte
|
||||
graveyardBkt := tx.Bucket(graveyardBucketName)
|
||||
garbageBkt := tx.Bucket(garbageBucketName)
|
||||
|
||||
const idSize = 44 // size of the stringified object and container ids
|
||||
var rawAddr = make([]byte, idSize*2+1)
|
||||
|
||||
loop:
|
||||
for ; name != nil; name, _ = c.Next() {
|
||||
postfix, ok := parseContainerIDWithPostfix(&containerID, name)
|
||||
if !ok {
|
||||
b58CID, postfix := parseContainerIDWithPostfix(&containerID, name)
|
||||
if b58CID == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -120,8 +126,13 @@ loop:
|
|||
continue
|
||||
}
|
||||
|
||||
prefix := containerID.EncodeToString() + "/"
|
||||
result, cursor = selectNFromBucket(tx, name, prefix, containerID, result, count, cursor, threshold)
|
||||
bkt := tx.Bucket(name)
|
||||
if bkt != nil {
|
||||
rawAddr = append(rawAddr[:0], b58CID...)
|
||||
rawAddr = append(rawAddr, '/')
|
||||
result, offset, cursor = selectNFromBucket(bkt, graveyardBkt, garbageBkt, rawAddr, containerID,
|
||||
result, count, cursor, threshold)
|
||||
}
|
||||
bucketName = name
|
||||
if len(result) >= count {
|
||||
break loop
|
||||
|
@ -132,6 +143,13 @@ loop:
|
|||
threshold = true
|
||||
}
|
||||
|
||||
if offset != nil {
|
||||
// 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)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return nil, nil, ErrEndOfListing
|
||||
}
|
||||
|
@ -146,20 +164,15 @@ loop:
|
|||
|
||||
// selectNFromBucket similar to selectAllFromBucket but uses cursor to find
|
||||
// object to start selecting from. Ignores inhumed objects.
|
||||
func selectNFromBucket(tx *bbolt.Tx,
|
||||
name []byte, // bucket name
|
||||
prefix string, // container ID prefix, optimization
|
||||
func selectNFromBucket(bkt *bbolt.Bucket, // main bucket
|
||||
graveyardBkt, garbageBkt *bbolt.Bucket, // cached graveyard buckets
|
||||
addrRaw []byte, // container ID prefix, optimization
|
||||
cnt cid.ID, // container ID
|
||||
to []oid.Address, // listing result
|
||||
limit int, // stop listing at `limit` items in result
|
||||
cursor *Cursor, // start from cursor object
|
||||
threshold bool, // ignore cursor and start immediately
|
||||
) ([]oid.Address, *Cursor) {
|
||||
bkt := tx.Bucket(name)
|
||||
if bkt == nil {
|
||||
return to, cursor
|
||||
}
|
||||
|
||||
) ([]oid.Address, []byte, *Cursor) {
|
||||
if cursor == nil {
|
||||
cursor = new(Cursor)
|
||||
}
|
||||
|
@ -175,12 +188,6 @@ func selectNFromBucket(tx *bbolt.Tx,
|
|||
k, _ = c.Next() // we are looking for objects _after_ the cursor
|
||||
}
|
||||
|
||||
addrRaw := make([]byte, len(prefix)+44)
|
||||
copy(addrRaw, prefix)
|
||||
|
||||
graveyardBkt := tx.Bucket(graveyardBucketName)
|
||||
garbageBkt := tx.Bucket(garbageBucketName)
|
||||
|
||||
for ; k != nil; k, _ = c.Next() {
|
||||
if count >= limit {
|
||||
break
|
||||
|
@ -192,8 +199,7 @@ func selectNFromBucket(tx *bbolt.Tx,
|
|||
}
|
||||
|
||||
offset = k
|
||||
addrRaw = append(addrRaw[:len(prefix)], k...)
|
||||
if inGraveyardWithKey(addrRaw, graveyardBkt, garbageBkt) > 0 {
|
||||
if inGraveyardWithKey(append(addrRaw, k...), graveyardBkt, garbageBkt) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -204,29 +210,24 @@ func selectNFromBucket(tx *bbolt.Tx,
|
|||
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, offset, cursor
|
||||
}
|
||||
|
||||
func parseContainerIDWithPostfix(containerID *cid.ID, name []byte) (string, bool) {
|
||||
func parseContainerIDWithPostfix(containerID *cid.ID, name []byte) ([]byte, string) {
|
||||
var (
|
||||
containerIDStr = string(name)
|
||||
postfix string
|
||||
containerIDStr = name
|
||||
postfix []byte
|
||||
)
|
||||
|
||||
ind := strings.Index(string(name), invalidBase58String)
|
||||
ind := bytes.IndexByte(name, invalidBase58String[0])
|
||||
if ind > 0 {
|
||||
postfix = containerIDStr[ind:]
|
||||
containerIDStr = containerIDStr[:ind]
|
||||
}
|
||||
|
||||
if err := containerID.DecodeString(containerIDStr); err != nil {
|
||||
return "", false
|
||||
if err := containerID.DecodeString(string(containerIDStr)); err != nil {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
return postfix, true
|
||||
return containerIDStr, string(postfix)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue