Skip to content

Commit

Permalink
fstree: minimize memory required for combined objects
Browse files Browse the repository at this point in the history
Keeping whole combined file in memory is excessive.

Tests with the default write combining enabled.

HDD, before the patch:

goos: linux
goarch: amd64
pkg: github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor
cpu: AMD Ryzen 5 3600 6-Core Processor
BenchmarkGet/size=1,thread=1/fstree-12             33870             35109 ns/op           14054 B/op         26 allocs/op
BenchmarkGet/size=1,thread=1/peapod-12            104599             11292 ns/op            1496 B/op         27 allocs/op
BenchmarkGet/size=1,thread=20/peapod-12             9690            123428 ns/op           30027 B/op        591 allocs/op
BenchmarkGet/size=1,thread=20/fstree-12             6931            170086 ns/op          280829 B/op        501 allocs/op
BenchmarkGet/size=1,thread=100/peapod-12                    1978            586932 ns/op          148056 B/op       2683 allocs/op
BenchmarkGet/size=1,thread=100/fstree-12                    1828            656545 ns/op         1404125 B/op       2501 allocs/op
BenchmarkGet/size=1024,thread=1/peapod-12                  95288             12732 ns/op            3664 B/op         38 allocs/op
BenchmarkGet/size=1024,thread=1/fstree-12                  16780             71580 ns/op          117472 B/op         26 allocs/op
BenchmarkGet/size=1024,thread=20/peapod-12                  7676            147320 ns/op           73223 B/op        770 allocs/op
BenchmarkGet/size=1024,thread=20/fstree-12                  2733            440093 ns/op         2349173 B/op        501 allocs/op
BenchmarkGet/size=1024,thread=100/peapod-12                 1666            711513 ns/op          366425 B/op       3877 allocs/op
BenchmarkGet/size=1024,thread=100/fstree-12                  439           2773494 ns/op        11746542 B/op       2510 allocs/op
BenchmarkGet/size=102400,thread=1/peapod-12                15387             82956 ns/op          214422 B/op         30 allocs/op
BenchmarkGet/size=102400,thread=1/fstree-12                  898           1494701 ns/op         6583348 B/op         26 allocs/op
BenchmarkGet/size=102400,thread=20/peapod-12                1015           1056701 ns/op         4287961 B/op        564 allocs/op
BenchmarkGet/size=102400,thread=20/fstree-12                  57          18260165 ns/op        124299921 B/op       501 allocs/op
BenchmarkGet/size=102400,thread=100/peapod-12                199           6087298 ns/op        21442817 B/op       3066 allocs/op
BenchmarkGet/size=102400,thread=100/fstree-12                 12          91612104 ns/op        630612319 B/op      2501 allocs/op
PASS
ok      github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor       282.428s

HDD, after the patch:

goos: linux
goarch: amd64
pkg: github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor
cpu: AMD Ryzen 5 3600 6-Core Processor
BenchmarkGet/size=1,thread=1/fstree-12             13465             91189 ns/op            1687 B/op         26 allocs/op
BenchmarkGet/size=1,thread=1/peapod-12            107473             11447 ns/op            1516 B/op         30 allocs/op
BenchmarkGet/size=1,thread=20/peapod-12             9819            122212 ns/op           29637 B/op        542 allocs/op
BenchmarkGet/size=1,thread=20/fstree-12             4321            276909 ns/op           33456 B/op        501 allocs/op
BenchmarkGet/size=1,thread=100/peapod-12                    2032            582728 ns/op          147916 B/op       2666 allocs/op
BenchmarkGet/size=1,thread=100/fstree-12                    1224            983660 ns/op          167230 B/op       2501 allocs/op
BenchmarkGet/size=1024,thread=1/peapod-12                  93832             12741 ns/op            3676 B/op         39 allocs/op
BenchmarkGet/size=1024,thread=1/fstree-12                  11612             97948 ns/op            3776 B/op         26 allocs/op
BenchmarkGet/size=1024,thread=20/peapod-12                  8413            145797 ns/op           73244 B/op        772 allocs/op
BenchmarkGet/size=1024,thread=20/fstree-12                  4113            288727 ns/op           75216 B/op        501 allocs/op
BenchmarkGet/size=1024,thread=100/peapod-12                 1677            688383 ns/op          366354 B/op       3866 allocs/op
BenchmarkGet/size=1024,thread=100/fstree-12                 1131           1054335 ns/op          376035 B/op       2501 allocs/op
BenchmarkGet/size=102400,thread=1/peapod-12                16148             79871 ns/op          214414 B/op         29 allocs/op
BenchmarkGet/size=102400,thread=1/fstree-12                 6381            183071 ns/op          214592 B/op         26 allocs/op
BenchmarkGet/size=102400,thread=20/peapod-12                1029           1163020 ns/op         4288312 B/op        608 allocs/op
BenchmarkGet/size=102400,thread=20/fstree-12                1545            774230 ns/op         4291601 B/op        501 allocs/op
BenchmarkGet/size=102400,thread=100/peapod-12                204           5881155 ns/op        21448378 B/op       3629 allocs/op
BenchmarkGet/size=102400,thread=100/fstree-12                286           4097583 ns/op        21458835 B/op       2513 allocs/op

SSD, before the patch:

goos: linux
goarch: amd64
pkg: github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor
cpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics
BenchmarkGet/size=1,thread=1/peapod-16            206061              5598 ns/op            1550 B/op         34 allocs/op
BenchmarkGet/size=1,thread=1/fstree-16             43969             24736 ns/op           14056 B/op         26 allocs/op
BenchmarkGet/size=1,thread=20/peapod-16             8701            130963 ns/op           30784 B/op        684 allocs/op
BenchmarkGet/size=1,thread=20/fstree-16            10915            109432 ns/op          280837 B/op        501 allocs/op
BenchmarkGet/size=1,thread=100/peapod-16                    1615            697317 ns/op          153837 B/op       3395 allocs/op
BenchmarkGet/size=1,thread=100/fstree-16                    2515            459902 ns/op         1404191 B/op       2502 allocs/op
BenchmarkGet/size=1024,thread=1/peapod-16                 135268              7757 ns/op            3658 B/op         37 allocs/op
BenchmarkGet/size=1024,thread=1/fstree-16                  24434             47873 ns/op          117472 B/op         26 allocs/op
BenchmarkGet/size=1024,thread=20/peapod-16                  8172            148323 ns/op           73263 B/op        773 allocs/op
BenchmarkGet/size=1024,thread=20/fstree-16                  4244            243463 ns/op         2349182 B/op        501 allocs/op
BenchmarkGet/size=1024,thread=100/peapod-16                 1370            843700 ns/op          366623 B/op       3873 allocs/op
BenchmarkGet/size=1024,thread=100/fstree-16                  606           1917891 ns/op        11746920 B/op       2514 allocs/op
BenchmarkGet/size=102400,thread=1/fstree-16                  769           1667689 ns/op         8439914 B/op         26 allocs/op
BenchmarkGet/size=102400,thread=1/peapod-16                26174             41144 ns/op          214476 B/op         36 allocs/op
BenchmarkGet/size=102400,thread=20/fstree-16                  49          21872222 ns/op        169093205 B/op       501 allocs/op
BenchmarkGet/size=102400,thread=20/peapod-16                1674            636594 ns/op         4289612 B/op        758 allocs/op
BenchmarkGet/size=102400,thread=100/peapod-16                296           3582319 ns/op        21447448 B/op       3535 allocs/op
BenchmarkGet/size=102400,thread=100/fstree-16                 10         110982517 ns/op        841263811 B/op      2505 allocs/op
PASS
ok      github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor       203.115s

SSD, after the patch:

goos: linux
goarch: amd64
pkg: github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor
cpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics
BenchmarkGet/size=1,thread=1/fstree-16             26210             44577 ns/op            1687 B/op         26 allocs/op
BenchmarkGet/size=1,thread=1/peapod-16            203316              5551 ns/op            1550 B/op         34 allocs/op
BenchmarkGet/size=1,thread=20/peapod-16             8142            127892 ns/op           30745 B/op        679 allocs/op
BenchmarkGet/size=1,thread=20/fstree-16             8155            141522 ns/op           33457 B/op        501 allocs/op
BenchmarkGet/size=1,thread=100/fstree-16                    2160            526853 ns/op          167233 B/op       2501 allocs/op
BenchmarkGet/size=1,thread=100/peapod-16                    1633            695693 ns/op          153747 B/op       3383 allocs/op
BenchmarkGet/size=1024,thread=1/fstree-16                  24176             51102 ns/op            3776 B/op         26 allocs/op
BenchmarkGet/size=1024,thread=1/peapod-16                 139054              7587 ns/op            3675 B/op         39 allocs/op
BenchmarkGet/size=1024,thread=20/peapod-16                  8098            145081 ns/op           72656 B/op        697 allocs/op
BenchmarkGet/size=1024,thread=20/fstree-16                  7908            152477 ns/op           75216 B/op        501 allocs/op
BenchmarkGet/size=1024,thread=100/peapod-16                 1339            807344 ns/op          365303 B/op       3704 allocs/op
BenchmarkGet/size=1024,thread=100/fstree-16                 1930            578930 ns/op          376032 B/op       2501 allocs/op
BenchmarkGet/size=102400,thread=1/peapod-16                22363             49137 ns/op          214402 B/op         28 allocs/op
BenchmarkGet/size=102400,thread=1/fstree-16                 8257            143084 ns/op          214592 B/op         26 allocs/op
BenchmarkGet/size=102400,thread=20/peapod-16                1596            654267 ns/op         4289593 B/op        753 allocs/op
BenchmarkGet/size=102400,thread=20/fstree-16                2222            507259 ns/op         4291623 B/op        501 allocs/op
BenchmarkGet/size=102400,thread=100/peapod-16                286           3655491 ns/op        21447877 B/op       3592 allocs/op
BenchmarkGet/size=102400,thread=100/fstree-16                415           2938832 ns/op        21459193 B/op       2517 allocs/op
PASS
ok      github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor       213.832s

Notice that peapod degrades more when multi-threaded. It still wins for
super-small objects with low thread number, but that's the simplest scenario
and this case can be improved for FSTree as well.

Also, SSDs work better without write combining as we know from previous results,
non-combined reads work like this for SSDs:

goos: linux
goarch: amd64
pkg: github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor
cpu: AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics
BenchmarkGet/size=1,thread=1/peapod-16            150696              6881 ns/op            1552 B/op         35 allocs/op
BenchmarkGet/size=1,thread=1/fstree-16             60586             17133 ns/op            2279 B/op         26 allocs/op
BenchmarkGet/size=1,thread=20/peapod-16             9412            127615 ns/op           30750 B/op        679 allocs/op
BenchmarkGet/size=1,thread=20/fstree-16            19616             60384 ns/op           45298 B/op        501 allocs/op
BenchmarkGet/size=1,thread=100/fstree-16                    5278            203212 ns/op          226452 B/op       2501 allocs/op
BenchmarkGet/size=1,thread=100/peapod-16                    1600            691861 ns/op          153755 B/op       3385 allocs/op
BenchmarkGet/size=1024,thread=1/peapod-16                 148694              8222 ns/op            3677 B/op         39 allocs/op
BenchmarkGet/size=1024,thread=1/fstree-16                  55369             19654 ns/op            3936 B/op         26 allocs/op
BenchmarkGet/size=1024,thread=20/peapod-16                  7117            157147 ns/op           73252 B/op        772 allocs/op
BenchmarkGet/size=1024,thread=20/fstree-16                 17496             71552 ns/op           78417 B/op        501 allocs/op
BenchmarkGet/size=1024,thread=100/peapod-16                 1346            815546 ns/op          364150 B/op       3564 allocs/op
BenchmarkGet/size=1024,thread=100/fstree-16                 4941            242115 ns/op          392055 B/op       2501 allocs/op
BenchmarkGet/size=102400,thread=1/peapod-16                23816             43648 ns/op          214439 B/op         31 allocs/op
BenchmarkGet/size=102400,thread=1/fstree-16                16125             72096 ns/op          214752 B/op         26 allocs/op
BenchmarkGet/size=102400,thread=20/peapod-16                1748            658507 ns/op         4288086 B/op        580 allocs/op
BenchmarkGet/size=102400,thread=20/fstree-16                1838            617794 ns/op         4294815 B/op        501 allocs/op
BenchmarkGet/size=102400,thread=100/peapod-16                284           3732700 ns/op        21441371 B/op       2795 allocs/op
BenchmarkGet/size=102400,thread=100/fstree-16                438           2749439 ns/op        21475282 B/op       2518 allocs/op
PASS
ok      github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor       172.643s

Signed-off-by: Roman Khimov <[email protected]>
  • Loading branch information
roman-khimov committed Aug 28, 2024
1 parent 391cfb4 commit 1b40ec4
Showing 1 changed file with 51 additions and 18 deletions.
69 changes: 51 additions & 18 deletions pkg/local_object_storage/blobstor/fstree/fstree.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"encoding/binary"
"errors"
"fmt"
"io"
"io/fs"
"math"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -330,14 +332,15 @@ func (t *FSTree) getObjBytes(addr oid.Address) ([]byte, error) {
// getRawObjectBytes extracts raw object bytes from the storage by path. No
// decompression is performed.
func getRawObjectBytes(id oid.ID, p string) ([]byte, error) {
data, err := os.ReadFile(p)
f, err := os.Open(p)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil, logicerr.Wrap(apistatus.ObjectNotFound{})
}
return nil, fmt.Errorf("read file %q: %w", p, err)

Check warning on line 340 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L340

Added line #L340 was not covered by tests
}
data, err = extractCombinedObject(id, data)
defer f.Close()
data, err := extractCombinedObject(id, f)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil, logicerr.Wrap(apistatus.ObjectNotFound{})

Check warning on line 346 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L345-L346

Added lines #L345 - L346 were not covered by tests
Expand All @@ -347,7 +350,7 @@ func getRawObjectBytes(id oid.ID, p string) ([]byte, error) {
return data, nil
}

func extractCombinedObject(id oid.ID, data []byte) ([]byte, error) {
func extractCombinedObject(id oid.ID, f *os.File) ([]byte, error) {
const (
prefixSize = 1
idSize = sha256.Size
Expand All @@ -358,25 +361,55 @@ func extractCombinedObject(id oid.ID, data []byte) ([]byte, error) {
dataOff = lengthOff + lengthSize
)

var notFound bool
var (
comBuf [dataOff]byte
data []byte
isCombined bool
)

for len(data) > dataOff && data[0] == combinedPrefix {
notFound = true // The file _is_ combined, so the object _must_ be there.
var l = binary.BigEndian.Uint32(data[lengthOff:dataOff])
if bytes.Equal(data[idOff:lengthOff], id[:]) {
data = data[dataOff : dataOff+int(l)]
notFound = false
break
for {
n, err := io.ReadFull(f, comBuf[:])
if err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
if !isCombined {
return comBuf[:n], nil

Check warning on line 375 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L373-L375

Added lines #L373 - L375 were not covered by tests
}
return nil, fs.ErrNotExist

Check warning on line 377 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L377

Added line #L377 was not covered by tests
}
return nil, err

Check warning on line 379 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L379

Added line #L379 was not covered by tests
}
if comBuf[0] != combinedPrefix {
st, err := f.Stat()
if err != nil {
return nil, err

Check warning on line 384 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L382-L384

Added lines #L382 - L384 were not covered by tests
}
sz := st.Size()
if sz > math.MaxInt {
return nil, errors.New("too large file")

Check warning on line 388 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L386-L388

Added lines #L386 - L388 were not covered by tests
}
data = make([]byte, int(sz))
copy(data, comBuf[:])
_, err = io.ReadFull(f, data[len(comBuf):])
if err != nil {
return nil, err

Check warning on line 394 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L390-L394

Added lines #L390 - L394 were not covered by tests
}
return data, nil

Check warning on line 396 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L396

Added line #L396 was not covered by tests
}
isCombined = true
var l = binary.BigEndian.Uint32(comBuf[lengthOff:dataOff])
if bytes.Equal(comBuf[idOff:lengthOff], id[:]) {
data = make([]byte, l)
_, err = io.ReadFull(f, data)
if err != nil {
return nil, err

Check warning on line 404 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L404

Added line #L404 was not covered by tests
}
return data, nil
}
if len(data) < dataOff+int(l) {
break
_, err = f.Seek(int64(l), 1)
if err != nil {
return nil, err

Check warning on line 410 in pkg/local_object_storage/blobstor/fstree/fstree.go

View check run for this annotation

Codecov / codecov/patch

pkg/local_object_storage/blobstor/fstree/fstree.go#L408-L410

Added lines #L408 - L410 were not covered by tests
}
data = data[dataOff+int(l):]
}
if notFound {
return nil, fs.ErrNotExist // Quite similar in meaning.
}
return data, nil
}

// GetRange implements common.Storage.
Expand Down

0 comments on commit 1b40ec4

Please sign in to comment.