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
|
package meta
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
|
||||||
|
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/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 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:
|
loop:
|
||||||
for ; name != nil; name, _ = c.Next() {
|
for ; name != nil; name, _ = c.Next() {
|
||||||
postfix, ok := parseContainerIDWithPostfix(&containerID, name)
|
b58CID, postfix := parseContainerIDWithPostfix(&containerID, name)
|
||||||
if !ok {
|
if b58CID == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,8 +126,13 @@ loop:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix := containerID.EncodeToString() + "/"
|
bkt := tx.Bucket(name)
|
||||||
result, cursor = selectNFromBucket(tx, name, prefix, containerID, result, count, cursor, threshold)
|
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
|
bucketName = name
|
||||||
if len(result) >= count {
|
if len(result) >= count {
|
||||||
break loop
|
break loop
|
||||||
|
@ -132,6 +143,13 @@ loop:
|
||||||
threshold = true
|
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 {
|
if len(result) == 0 {
|
||||||
return nil, nil, ErrEndOfListing
|
return nil, nil, ErrEndOfListing
|
||||||
}
|
}
|
||||||
|
@ -146,20 +164,15 @@ loop:
|
||||||
|
|
||||||
// selectNFromBucket similar to selectAllFromBucket but uses cursor to find
|
// selectNFromBucket similar to selectAllFromBucket but uses cursor to find
|
||||||
// object to start selecting from. Ignores inhumed objects.
|
// object to start selecting from. Ignores inhumed objects.
|
||||||
func selectNFromBucket(tx *bbolt.Tx,
|
func selectNFromBucket(bkt *bbolt.Bucket, // main bucket
|
||||||
name []byte, // bucket name
|
graveyardBkt, garbageBkt *bbolt.Bucket, // cached graveyard buckets
|
||||||
prefix string, // container ID prefix, optimization
|
addrRaw []byte, // container ID prefix, optimization
|
||||||
cnt cid.ID, // container ID
|
cnt cid.ID, // container ID
|
||||||
to []oid.Address, // listing result
|
to []oid.Address, // listing result
|
||||||
limit int, // stop listing at `limit` items in result
|
limit int, // stop listing at `limit` items in result
|
||||||
cursor *Cursor, // start from cursor object
|
cursor *Cursor, // start from cursor object
|
||||||
threshold bool, // ignore cursor and start immediately
|
threshold bool, // ignore cursor and start immediately
|
||||||
) ([]oid.Address, *Cursor) {
|
) ([]oid.Address, []byte, *Cursor) {
|
||||||
bkt := tx.Bucket(name)
|
|
||||||
if bkt == nil {
|
|
||||||
return to, cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
if cursor == nil {
|
if cursor == nil {
|
||||||
cursor = new(Cursor)
|
cursor = new(Cursor)
|
||||||
}
|
}
|
||||||
|
@ -175,12 +188,6 @@ func selectNFromBucket(tx *bbolt.Tx,
|
||||||
k, _ = c.Next() // we are looking for objects _after_ the cursor
|
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() {
|
for ; k != nil; k, _ = c.Next() {
|
||||||
if count >= limit {
|
if count >= limit {
|
||||||
break
|
break
|
||||||
|
@ -192,8 +199,7 @@ func selectNFromBucket(tx *bbolt.Tx,
|
||||||
}
|
}
|
||||||
|
|
||||||
offset = k
|
offset = k
|
||||||
addrRaw = append(addrRaw[:len(prefix)], k...)
|
if inGraveyardWithKey(append(addrRaw, k...), graveyardBkt, garbageBkt) > 0 {
|
||||||
if inGraveyardWithKey(addrRaw, graveyardBkt, garbageBkt) > 0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,29 +210,24 @@ func selectNFromBucket(tx *bbolt.Tx,
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
|
|
||||||
// new slice is much faster but less memory efficient
|
return to, offset, cursor
|
||||||
// we need to copy, because offset exists during bbolt tx
|
|
||||||
cursor.inBucketOffset = make([]byte, len(offset))
|
|
||||||
copy(cursor.inBucketOffset, offset)
|
|
||||||
|
|
||||||
return to, cursor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseContainerIDWithPostfix(containerID *cid.ID, name []byte) (string, bool) {
|
func parseContainerIDWithPostfix(containerID *cid.ID, name []byte) ([]byte, string) {
|
||||||
var (
|
var (
|
||||||
containerIDStr = string(name)
|
containerIDStr = name
|
||||||
postfix string
|
postfix []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
ind := strings.Index(string(name), invalidBase58String)
|
ind := bytes.IndexByte(name, invalidBase58String[0])
|
||||||
if ind > 0 {
|
if ind > 0 {
|
||||||
postfix = containerIDStr[ind:]
|
postfix = containerIDStr[ind:]
|
||||||
containerIDStr = containerIDStr[:ind]
|
containerIDStr = containerIDStr[:ind]
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := containerID.DecodeString(containerIDStr); err != nil {
|
if err := containerID.DecodeString(string(containerIDStr)); err != nil {
|
||||||
return "", false
|
return nil, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return postfix, true
|
return containerIDStr, string(postfix)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue