Skip to content

Commit 654e9d3

Browse files
committed
fstree: make a new object storage structure
Store objects in the format "len(Header)+len(Payload)+header+payload", for quick reading of Head, GetRange, GetStream. ``` goos: linux goarch: amd64 pkg: github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics │ oldGet.txt │ newGet.txt │ │ sec/op │ sec/op vs base │ FSTree_Get/Empty/Get_regular-16 73.79µ ± 4% 74.72µ ± 4% ~ (p=0.394 n=6) FSTree_Get/Empty/Get_combined-16 88.50µ ± 6% 90.19µ ± 5% ~ (p=0.310 n=6) FSTree_Get/Empty/Get_compressed-16 37.87µ ± 7% 37.01µ ± 6% ~ (p=0.589 n=6) FSTree_Get/100B/Get_regular-16 72.22µ ± 3% 75.45µ ± 8% ~ (p=0.065 n=6) FSTree_Get/100B/Get_combined-16 86.45µ ± 3% 90.19µ ± 3% +4.34% (p=0.002 n=6) FSTree_Get/100B/Get_compressed-16 39.92µ ± 6% 38.23µ ± 6% ~ (p=0.240 n=6) FSTree_Get/4KB/Get_regular-16 82.17µ ± 6% 86.20µ ± 2% +4.91% (p=0.002 n=6) FSTree_Get/4KB/Get_combined-16 101.0µ ± 3% 102.6µ ± 2% +1.55% (p=0.041 n=6) FSTree_Get/4KB/Get_compressed-16 55.93µ ± 6% 59.37µ ± 13% ~ (p=0.093 n=6) FSTree_Get/16KB/Get_regular-16 100.6µ ± 3% 112.5µ ± 3% +11.75% (p=0.002 n=6) FSTree_Get/16KB/Get_combined-16 120.9µ ± 4% 128.3µ ± 2% +6.12% (p=0.002 n=6) FSTree_Get/16KB/Get_compressed-16 80.41µ ± 4% 87.57µ ± 3% +8.90% (p=0.002 n=6) FSTree_Get/32KB/Get_regular-16 125.9µ ± 5% 151.7µ ± 2% +20.48% (p=0.002 n=6) FSTree_Get/32KB/Get_combined-16 148.0µ ± 4% 167.9µ ± 4% +13.50% (p=0.002 n=6) FSTree_Get/32KB/Get_compressed-16 108.3µ ± 3% 120.2µ ± 5% +10.94% (p=0.002 n=6) FSTree_Get/100KB/Get_regular-16 213.2µ ± 2% 282.3µ ± 2% +32.37% (p=0.002 n=6) FSTree_Get/100KB/Get_combined-16 235.2µ ± 4% 304.5µ ± 2% +29.45% (p=0.002 n=6) FSTree_Get/100KB/Get_compressed-16 176.0µ ± 3% 203.4µ ± 1% +15.55% (p=0.002 n=6) FSTree_Get/1MB/Get_regular-16 1.442m ± 10% 2.463m ± 14% +70.89% (p=0.002 n=6) FSTree_Get/1MB/Get_combined-16 1.548m ± 5% 2.417m ± 5% +56.10% (p=0.002 n=6) FSTree_Get/1MB/Get_compressed-16 928.2µ ± 7% 1166.5µ ± 3% +25.68% (p=0.002 n=6) geomean 139.2µ 158.6µ +13.94% │ oldGet.txt │ newGet.txt │ │ B/op │ B/op vs base │ FSTree_Get/Empty/Get_regular-16 9.270Ki ± 4% 10.738Ki ± 7% +15.84% (p=0.002 n=6) FSTree_Get/Empty/Get_combined-16 9.157Ki ± 2% 10.637Ki ± 1% +16.17% (p=0.002 n=6) FSTree_Get/Empty/Get_compressed-16 10.47Ki ± 2% 10.64Ki ± 2% ~ (p=0.225 n=6) FSTree_Get/100B/Get_regular-16 9.590Ki ± 7% 11.398Ki ± 10% +18.86% (p=0.002 n=6) FSTree_Get/100B/Get_combined-16 9.378Ki ± 1% 11.139Ki ± 1% +18.77% (p=0.002 n=6) FSTree_Get/100B/Get_compressed-16 11.07Ki ± 4% 11.52Ki ± 6% ~ (p=0.058 n=6) FSTree_Get/4KB/Get_regular-16 17.62Ki ± 3% 23.67Ki ± 1% +34.34% (p=0.002 n=6) FSTree_Get/4KB/Get_combined-16 17.69Ki ± 0% 23.67Ki ± 1% +33.84% (p=0.002 n=6) FSTree_Get/4KB/Get_compressed-16 23.70Ki ± 1% 28.54Ki ± 1% +20.44% (p=0.002 n=6) FSTree_Get/16KB/Get_regular-16 41.66Ki ± 1% 59.66Ki ± 1% +43.20% (p=0.002 n=6) FSTree_Get/16KB/Get_combined-16 41.64Ki ± 0% 59.69Ki ± 0% +43.36% (p=0.002 n=6) FSTree_Get/16KB/Get_compressed-16 59.54Ki ± 1% 77.64Ki ± 0% +30.40% (p=0.002 n=6) FSTree_Get/32KB/Get_regular-16 79.73Ki ± 0% 119.80Ki ± 0% +50.27% (p=0.002 n=6) FSTree_Get/32KB/Get_combined-16 79.64Ki ± 0% 119.66Ki ± 0% +50.26% (p=0.002 n=6) FSTree_Get/32KB/Get_compressed-16 119.6Ki ± 0% 159.9Ki ± 0% +33.69% (p=0.002 n=6) FSTree_Get/100KB/Get_regular-16 215.7Ki ± 0% 319.6Ki ± 0% +48.15% (p=0.002 n=6) FSTree_Get/100KB/Get_combined-16 215.6Ki ± 0% 319.7Ki ± 0% +48.27% (p=0.002 n=6) FSTree_Get/100KB/Get_compressed-16 319.7Ki ± 0% 423.7Ki ± 0% +32.52% (p=0.002 n=6) FSTree_Get/1MB/Get_regular-16 2.015Mi ± 0% 3.023Mi ± 0% +50.01% (p=0.002 n=6) FSTree_Get/1MB/Get_combined-16 2.015Mi ± 0% 3.023Mi ± 0% +50.01% (p=0.002 n=6) FSTree_Get/1MB/Get_compressed-16 3.023Mi ± 0% 4.031Mi ± 0% +33.34% (p=0.002 n=6) geomean 64.51Ki 84.73Ki +31.35% │ oldGet.txt │ newGet.txt │ │ allocs/op │ allocs/op vs base │ FSTree_Get/Empty/Get_regular-16 138.5 ± 7% 139.0 ± 9% ~ (p=0.619 n=6) FSTree_Get/Empty/Get_combined-16 136.0 ± 2% 137.0 ± 1% ~ (p=0.206 n=6) FSTree_Get/Empty/Get_compressed-16 135.5 ± 4% 137.0 ± 6% ~ (p=0.524 n=6) FSTree_Get/100B/Get_regular-16 139.5 ± 7% 142.0 ± 11% ~ (p=0.379 n=6) FSTree_Get/100B/Get_combined-16 136.0 ± 2% 138.5 ± 2% ~ (p=0.065 n=6) FSTree_Get/100B/Get_compressed-16 140.0 ± 9% 140.0 ± 6% ~ (p=0.916 n=6) FSTree_Get/4KB/Get_regular-16 135.5 ± 9% 139.0 ± 5% ~ (p=0.232 n=6) FSTree_Get/4KB/Get_combined-16 138.0 ± 1% 138.5 ± 3% ~ (p=0.556 n=6) FSTree_Get/4KB/Get_compressed-16 138.5 ± 5% 143.5 ± 5% ~ (p=0.071 n=6) FSTree_Get/16KB/Get_regular-16 137.5 ± 8% 138.5 ± 8% ~ (p=0.848 n=6) FSTree_Get/16KB/Get_combined-16 136.5 ± 3% 138.5 ± 1% ~ (p=0.197 n=6) FSTree_Get/16KB/Get_compressed-16 135.0 ± 6% 140.0 ± 1% +3.70% (p=0.026 n=6) FSTree_Get/32KB/Get_regular-16 139.5 ± 7% 142.0 ± 3% ~ (p=0.784 n=6) FSTree_Get/32KB/Get_combined-16 136.5 ± 1% 138.0 ± 1% ~ (p=0.106 n=6) FSTree_Get/32KB/Get_compressed-16 136.5 ± 5% 145.5 ± 12% ~ (p=0.162 n=6) FSTree_Get/100KB/Get_regular-16 137.5 ± 8% 135.0 ± 10% ~ (p=0.617 n=6) FSTree_Get/100KB/Get_combined-16 135.5 ± 3% 138.0 ± 2% ~ (p=0.537 n=6) FSTree_Get/100KB/Get_compressed-16 140.5 ± 6% 141.5 ± 10% ~ (p=0.831 n=6) FSTree_Get/1MB/Get_regular-16 135.0 ± 8% 137.5 ± 7% ~ (p=0.922 n=6) FSTree_Get/1MB/Get_combined-16 137.5 ± 3% 138.5 ± 2% ~ (p=0.760 n=6) FSTree_Get/1MB/Get_compressed-16 139.5 ± 8% 143.0 ± 10% ~ (p=0.513 n=6) geomean 137.3 139.5 +1.59% │ oldHead.txt │ newHead.txt │ │ sec/op │ sec/op vs base │ FSTree_Head/Empty/Head_regular-16 84.14µ ± 4% 91.17µ ± 5% +8.35% (p=0.002 n=6) FSTree_Head/Empty/Head_combined-16 87.86µ ± 10% 95.58µ ± 2% +8.78% (p=0.002 n=6) FSTree_Head/Empty/Head_compressed-16 73.02µ ± 5% 70.08µ ± 8% -4.03% (p=0.026 n=6) FSTree_Head/100B/Head_regular-16 85.14µ ± 2% 93.50µ ± 7% +9.81% (p=0.002 n=6) FSTree_Head/100B/Head_combined-16 88.09µ ± 4% 94.98µ ± 1% +7.82% (p=0.002 n=6) FSTree_Head/100B/Head_compressed-16 72.49µ ± 3% 70.35µ ± 3% -2.96% (p=0.002 n=6) FSTree_Head/4KB/Head_regular-16 87.55µ ± 4% 93.28µ ± 1% +6.56% (p=0.002 n=6) FSTree_Head/4KB/Head_combined-16 97.75µ ± 1% 101.06µ ± 4% ~ (p=0.132 n=6) FSTree_Head/4KB/Head_compressed-16 77.53µ ± 11% 69.92µ ± 8% -9.82% (p=0.026 n=6) FSTree_Head/16KB/Head_regular-16 86.53µ ± 4% 95.13µ ± 1% +9.95% (p=0.002 n=6) FSTree_Head/16KB/Head_combined-16 116.2µ ± 1% 124.9µ ± 1% +7.50% (p=0.002 n=6) FSTree_Head/16KB/Head_compressed-16 152.53µ ± 2% 70.19µ ± 4% -53.98% (p=0.002 n=6) FSTree_Head/32KB/Head_regular-16 91.39µ ± 6% 93.54µ ± 2% +2.36% (p=0.002 n=6) FSTree_Head/32KB/Head_combined-16 118.2µ ± 1% 123.9µ ± 16% ~ (p=0.394 n=6) FSTree_Head/32KB/Head_compressed-16 175.73µ ± 2% 70.52µ ± 14% -59.87% (p=0.002 n=6) FSTree_Head/100KB/Head_regular-16 91.08µ ± 4% 93.91µ ± 2% +3.11% (p=0.009 n=6) FSTree_Head/100KB/Head_combined-16 118.6µ ± 3% 123.8µ ± 1% +4.32% (p=0.002 n=6) FSTree_Head/100KB/Head_compressed-16 243.42µ ± 2% 71.17µ ± 9% -70.77% (p=0.002 n=6) FSTree_Head/1MB/Head_regular-16 90.26µ ± 2% 91.08µ ± 6% ~ (p=0.394 n=6) FSTree_Head/1MB/Head_combined-16 124.2µ ± 5% 118.3µ ± 5% -4.75% (p=0.041 n=6) FSTree_Head/1MB/Head_compressed-16 799.86µ ± 1% 53.73µ ± 7% -93.28% (p=0.002 n=6) geomean 113.1µ 88.78µ -21.54% │ oldHead.txt │ newHead.txt │ │ B/op │ B/op vs base │ FSTree_Head/Empty/Head_regular-16 39.91Ki ± 1% 39.60Ki ± 1% -0.76% (p=0.004 n=6) FSTree_Head/Empty/Head_combined-16 40.01Ki ± 0% 39.79Ki ± 0% -0.55% (p=0.002 n=6) FSTree_Head/Empty/Head_compressed-16 41.56Ki ± 1% 39.81Ki ± 1% -4.21% (p=0.002 n=6) FSTree_Head/100B/Head_regular-16 40.14Ki ± 1% 39.85Ki ± 1% ~ (p=0.071 n=6) FSTree_Head/100B/Head_combined-16 40.13Ki ± 0% 39.75Ki ± 0% -0.95% (p=0.002 n=6) FSTree_Head/100B/Head_compressed-16 41.91Ki ± 2% 39.87Ki ± 1% -4.86% (p=0.002 n=6) FSTree_Head/4KB/Head_regular-16 44.07Ki ± 1% 39.71Ki ± 1% -9.88% (p=0.002 n=6) FSTree_Head/4KB/Head_combined-16 44.08Ki ± 0% 39.78Ki ± 0% -9.75% (p=0.002 n=6) FSTree_Head/4KB/Head_compressed-16 50.08Ki ± 1% 39.78Ki ± 1% -20.56% (p=0.002 n=6) FSTree_Head/16KB/Head_regular-16 39.59Ki ± 1% 39.78Ki ± 0% ~ (p=0.121 n=6) FSTree_Head/16KB/Head_combined-16 39.66Ki ± 0% 39.81Ki ± 0% ~ (p=0.065 n=6) FSTree_Head/16KB/Head_compressed-16 119.44Ki ± 0% 39.74Ki ± 1% -66.73% (p=0.002 n=6) FSTree_Head/32KB/Head_regular-16 39.89Ki ± 2% 39.58Ki ± 1% ~ (p=0.132 n=6) FSTree_Head/32KB/Head_combined-16 39.66Ki ± 0% 39.79Ki ± 0% +0.33% (p=0.004 n=6) FSTree_Head/32KB/Head_compressed-16 173.47Ki ± 0% 39.73Ki ± 1% -77.10% (p=0.002 n=6) FSTree_Head/100KB/Head_regular-16 39.78Ki ± 1% 39.70Ki ± 1% ~ (p=0.368 n=6) FSTree_Head/100KB/Head_combined-16 39.66Ki ± 0% 39.78Ki ± 0% +0.29% (p=0.009 n=6) FSTree_Head/100KB/Head_compressed-16 373.68Ki ± 0% 39.86Ki ± 1% -89.33% (p=0.002 n=6) FSTree_Head/1MB/Head_regular-16 39.86Ki ± 1% 39.84Ki ± 1% ~ (p=0.848 n=6) FSTree_Head/1MB/Head_combined-16 39.64Ki ± 0% 39.80Ki ± 0% +0.41% (p=0.002 n=6) FSTree_Head/1MB/Head_compressed-16 2661.62Ki ± 0% 39.70Ki ± 1% -98.51% (p=0.002 n=6) geomean 62.72Ki 39.76Ki -36.60% │ oldHead.txt │ newHead.txt │ │ allocs/op │ allocs/op vs base │ FSTree_Head/Empty/Head_regular-16 135.0 ± 8% 136.0 ± 4% ~ (p=0.918 n=6) FSTree_Head/Empty/Head_combined-16 136.5 ± 3% 141.0 ± 2% +3.30% (p=0.022 n=6) FSTree_Head/Empty/Head_compressed-16 139.0 ± 4% 141.0 ± 5% ~ (p=0.545 n=6) FSTree_Head/100B/Head_regular-16 138.0 ± 4% 143.5 ± 7% ~ (p=0.165 n=6) FSTree_Head/100B/Head_combined-16 138.0 ± 3% 140.0 ± 1% ~ (p=0.208 n=6) FSTree_Head/100B/Head_compressed-16 141.0 ± 9% 143.5 ± 4% ~ (p=0.074 n=6) FSTree_Head/4KB/Head_regular-16 139.5 ± 8% 140.0 ± 4% ~ (p=1.000 n=6) FSTree_Head/4KB/Head_combined-16 140.0 ± 3% 141.0 ± 2% ~ (p=0.093 n=6) FSTree_Head/4KB/Head_compressed-16 141.0 ± 7% 141.0 ± 6% ~ (p=0.537 n=6) FSTree_Head/16KB/Head_regular-16 136.0 ± 8% 141.0 ± 3% ~ (p=0.110 n=6) FSTree_Head/16KB/Head_combined-16 138.5 ± 2% 142.0 ± 3% ~ (p=0.058 n=6) FSTree_Head/16KB/Head_compressed-16 176.5 ± 4% 140.0 ± 9% -20.68% (p=0.002 n=6) FSTree_Head/32KB/Head_regular-16 145.5 ± 12% 136.0 ± 4% ~ (p=0.084 n=6) FSTree_Head/32KB/Head_combined-16 138.5 ± 2% 141.0 ± 1% +1.81% (p=0.045 n=6) FSTree_Head/32KB/Head_compressed-16 176.0 ± 6% 140.0 ± 9% -20.45% (p=0.002 n=6) FSTree_Head/100KB/Head_regular-16 142.0 ± 10% 138.0 ± 6% ~ (p=0.290 n=6) FSTree_Head/100KB/Head_combined-16 139.0 ± 1% 141.0 ± 3% +1.44% (p=0.030 n=6) FSTree_Head/100KB/Head_compressed-16 182.0 ± 9% 143.5 ± 4% -21.15% (p=0.002 n=6) FSTree_Head/1MB/Head_regular-16 143.5 ± 5% 143.5 ± 4% ~ (p=1.000 n=6) FSTree_Head/1MB/Head_combined-16 138.5 ± 1% 141.5 ± 2% +2.17% (p=0.013 n=6) FSTree_Head/1MB/Head_compressed-16 183.5 ± 6% 139.5 ± 7% -23.98% (p=0.002 n=6) geomean 146.2 140.7 -3.82% │ oldPut.txt │ newPut.txt │ │ sec/op │ sec/op vs base │ Put/size=1,thread=1/fstree-16 15.85m ± 7% 15.96m ± 2% ~ (p=0.818 n=6) Put/size=1,thread=20/fstree-16 18.90m ± 25% 17.41m ± 10% ~ (p=0.180 n=6) Put/size=1,thread=100/fstree-16 23.64m ± 5% 22.87m ± 3% ~ (p=0.180 n=6) Put/size=1024,thread=1/fstree-16 16.11m ± 5% 15.91m ± 20% ~ (p=0.589 n=6) Put/size=1024,thread=20/fstree-16 17.77m ± 25% 17.51m ± 4% ~ (p=0.180 n=6) Put/size=1024,thread=100/fstree-16 24.27m ± 10% 23.86m ± 12% ~ (p=0.589 n=6) Put/size=102400,thread=1/fstree-16 16.59m ± 8% 16.63m ± 3% ~ (p=0.699 n=6) Put/size=102400,thread=20/fstree-16 31.49m ± 25% 27.74m ± 28% ~ (p=0.310 n=6) Put/size=102400,thread=100/fstree-16 175.4m ± 39% 150.3m ± 13% ~ (p=0.065 n=6) geomean 25.47m 24.28m -4.67% │ oldPut.txt │ newPut.txt │ │ B/op │ B/op vs base │ Put/size=1,thread=1/fstree-16 3.405Ki ± 1% 3.430Ki ± 1% ~ (p=0.084 n=6) Put/size=1,thread=20/fstree-16 55.81Ki ± 4% 56.18Ki ± 4% ~ (p=0.485 n=6) Put/size=1,thread=100/fstree-16 260.5Ki ± 2% 263.4Ki ± 2% ~ (p=0.240 n=6) Put/size=1024,thread=1/fstree-16 3.388Ki ± 1% 4.524Ki ± 1% +33.54% (p=0.002 n=6) Put/size=1024,thread=20/fstree-16 55.82Ki ± 1% 78.59Ki ± 1% +40.78% (p=0.002 n=6) Put/size=1024,thread=100/fstree-16 264.2Ki ± 1% 377.9Ki ± 0% +43.04% (p=0.002 n=6) Put/size=102400,thread=1/fstree-16 3.419Ki ± 0% 107.436Ki ± 0% +3041.91% (p=0.002 n=6) Put/size=102400,thread=20/fstree-16 57.58Ki ± 2% 2137.83Ki ± 0% +3612.71% (p=0.002 n=6) Put/size=102400,thread=100/fstree-16 290.5Ki ± 2% 10690.7Ki ± 0% +3580.47% (p=0.002 n=6) geomean 37.35Ki 136.8Ki +266.18% │ oldPut.txt │ newPut.txt │ │ allocs/op │ allocs/op vs base │ Put/size=1,thread=1/fstree-16 37.00 ± 3% 38.00 ± 3% +2.70% (p=0.032 n=6) Put/size=1,thread=20/fstree-16 559.0 ± 1% 577.5 ± 1% +3.31% (p=0.002 n=6) Put/size=1,thread=100/fstree-16 2.596k ± 1% 2.692k ± 0% +3.68% (p=0.002 n=6) Put/size=1024,thread=1/fstree-16 37.00 ± 3% 37.50 ± 1% ~ (p=0.076 n=6) Put/size=1024,thread=20/fstree-16 556.0 ± 1% 576.0 ± 1% +3.60% (p=0.002 n=6) Put/size=1024,thread=100/fstree-16 2.603k ± 1% 2.699k ± 0% +3.65% (p=0.002 n=6) Put/size=102400,thread=1/fstree-16 37.00 ± 3% 37.50 ± 1% ~ (p=0.076 n=6) Put/size=102400,thread=20/fstree-16 569.0 ± 1% 587.0 ± 1% +3.16% (p=0.002 n=6) Put/size=102400,thread=100/fstree-16 2.854k ± 2% 2.941k ± 1% +3.03% (p=0.002 n=6) geomean 381.9 392.9 +2.87% ``` Signed-off-by: Andrey Butusov <[email protected]>
1 parent 20e7ac6 commit 654e9d3

File tree

17 files changed

+219
-43
lines changed

17 files changed

+219
-43
lines changed

pkg/local_object_storage/blobstor/common/storage.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,17 @@ type Storage interface {
2828
GetRange(oid.Address, uint64, uint64) ([]byte, error)
2929
Head(oid.Address) (*objectSDK.Object, error)
3030
Exists(oid.Address) (bool, error)
31-
Put(oid.Address, []byte) error
32-
PutBatch(map[oid.Address][]byte) error
31+
// Put stores object in the storage. Arguments are:
32+
//
33+
// - addr oid.Address is the address of the object to store.
34+
//
35+
// - data []byte is the object payload in a binary format or
36+
// full data if header is nil.
37+
//
38+
// - header []byte is the optional object header in a binary format.
39+
// If nil, the header is extracted from the payload.
40+
Put(addr oid.Address, data []byte, header []byte) error
41+
PutBatch(map[oid.Address][2][]byte) error
3342
Delete(oid.Address) error
3443
Iterate(func(oid.Address, []byte) error, func(oid.Address, error) error) error
3544
IterateAddresses(func(oid.Address) error, bool) error
@@ -69,7 +78,7 @@ func CopyBatched(dst, src Storage, batchSize int) error {
6978
return fmt.Errorf("initialize destination sub-storage: %w", err)
7079
}
7180

72-
var objBatch = make(map[oid.Address][]byte)
81+
var objBatch = make(map[oid.Address][2][]byte)
7382

7483
err = src.Iterate(func(addr oid.Address, data []byte) error {
7584
exists, err := dst.Exists(addr)
@@ -80,13 +89,13 @@ func CopyBatched(dst, src Storage, batchSize int) error {
8089
}
8190

8291
if batchSize <= 1 {
83-
err = dst.Put(addr, data)
92+
err = dst.Put(addr, data, nil)
8493
if err != nil {
8594
return fmt.Errorf("put object %s into destination sub-storage: %w", addr, err)
8695
}
8796
return nil
8897
}
89-
objBatch[addr] = data
98+
objBatch[addr] = [2][]byte{data}
9099
if len(objBatch) == batchSize {
91100
err = dst.PutBatch(objBatch)
92101
if err != nil {

pkg/local_object_storage/blobstor/common/storage_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func testCopy(t *testing.T, copier func(dst, src common.Storage) error) {
3939
_, _ = rand.Read(data)
4040
mObjs[addr] = data
4141

42-
err := src.Put(addr, data)
42+
err := src.Put(addr, data, nil)
4343
require.NoError(t, err)
4444
}
4545

pkg/local_object_storage/blobstor/fstree/bench_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func testReadOp(b *testing.B, fsTree *fstree.FSTree, read func(address oid.Addre
4343
obj := generateTestObject(payloadSize)
4444
addr := object.AddressOf(obj)
4545

46-
require.NoError(b, fsTree.Put(addr, obj.Marshal()))
46+
require.NoError(b, fsTree.Put(addr, obj.Payload(), obj.CutPayload().Marshal()))
4747
b.ReportAllocs()
4848
b.ResetTimer()
4949
for range b.N {
@@ -57,11 +57,11 @@ func testReadOp(b *testing.B, fsTree *fstree.FSTree, read func(address oid.Addre
5757
b.Run(name+"_combined", func(b *testing.B) {
5858
const numObjects = 10
5959

60-
objMap := make(map[oid.Address][]byte, numObjects)
60+
objMap := make(map[oid.Address][2][]byte, numObjects)
6161
addrs := make([]oid.Address, numObjects)
6262
for i := range numObjects {
6363
o := generateTestObject(payloadSize)
64-
objMap[object.AddressOf(o)] = o.Marshal()
64+
objMap[object.AddressOf(o)] = [2][]byte{o.Payload(), o.CutPayload().Marshal()}
6565
addrs[i] = object.AddressOf(o)
6666
}
6767
require.NoError(b, fsTree.PutBatch(objMap))
@@ -85,7 +85,7 @@ func testReadOp(b *testing.B, fsTree *fstree.FSTree, read func(address oid.Addre
8585
}
8686
require.NoError(b, compressConfig.Init())
8787
fsTree.SetCompressor(compressConfig)
88-
require.NoError(b, fsTree.Put(addr, obj.Marshal()))
88+
require.NoError(b, fsTree.Put(addr, obj.Payload(), obj.CutPayload().Marshal()))
8989

9090
b.ReportAllocs()
9191
b.ResetTimer()

pkg/local_object_storage/blobstor/fstree/fstree.go

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
objectSDK "github.com/nspcc-dev/neofs-sdk-go/object"
2323
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
2424
"go.uber.org/zap"
25+
"google.golang.org/protobuf/encoding/protowire"
2526
)
2627

2728
// FSTree represents an object storage as a filesystem tree.
@@ -90,6 +91,26 @@ const (
9091
// combinedDataOff is the offset from the start of the combined prefix to object data.
9192
// It's also the length of the prefix in total.
9293
combinedDataOff = combinedLengthOff + combinedLenSize
94+
95+
// streamPrefix is the prefix for streamed objects. It is used to distinguish
96+
// streamed objects from regular ones.
97+
streamPrefix = 0x7e
98+
99+
// streamLenHeaderOff is the offset from the start of the stream prefix to
100+
// the length of the header data.
101+
streamLenHeaderOff = 2
102+
103+
// streamLenSize is sizeof(uint32), length of a serialized 32-bit BE integer
104+
// that represents the length of the header or payload data.
105+
streamLenSize = 4
106+
107+
// streamLenDataOff is the offset from the start of the stream prefix to
108+
// the length of the data.
109+
streamLenDataOff = streamLenHeaderOff + streamLenSize
110+
111+
// streamDataOff is the offset from the start of the stream prefix to the
112+
// start of the data. It is used to read the data after the header.
113+
streamDataOff = streamLenDataOff + streamLenSize
93114
)
94115

95116
var _ common.Storage = (*FSTree)(nil)
@@ -329,7 +350,7 @@ func (t *FSTree) getPath(addr oid.Address) (string, error) {
329350
}
330351

331352
// Put puts an object in the storage.
332-
func (t *FSTree) Put(addr oid.Address, data []byte) error {
353+
func (t *FSTree) Put(addr oid.Address, data []byte, header []byte) error {
333354
if t.readOnly {
334355
return common.ErrReadOnly
335356
}
@@ -339,7 +360,7 @@ func (t *FSTree) Put(addr oid.Address, data []byte) error {
339360
if err := util.MkdirAllX(filepath.Dir(p), t.Permissions); err != nil {
340361
return fmt.Errorf("mkdirall for %q: %w", p, err)
341362
}
342-
data = t.Compress(data)
363+
data = t.joinHeaderAndPayload(header, data)
343364

344365
err := t.writer.writeData(addr.Object(), p, data)
345366
if err != nil {
@@ -349,7 +370,7 @@ func (t *FSTree) Put(addr oid.Address, data []byte) error {
349370
}
350371

351372
// PutBatch puts a batch of objects in the storage.
352-
func (t *FSTree) PutBatch(objs map[oid.Address][]byte) error {
373+
func (t *FSTree) PutBatch(objs map[oid.Address][2][]byte) error {
353374
if t.readOnly {
354375
return common.ErrReadOnly
355376
}
@@ -363,7 +384,7 @@ func (t *FSTree) PutBatch(objs map[oid.Address][]byte) error {
363384
writeDataUnits = append(writeDataUnits, writeDataUnit{
364385
id: addr.Object(),
365386
path: p,
366-
data: t.Compress(data),
387+
data: t.joinHeaderAndPayload(data[1], data[0]),
367388
})
368389
}
369390

@@ -375,6 +396,28 @@ func (t *FSTree) PutBatch(objs map[oid.Address][]byte) error {
375396
return nil
376397
}
377398

399+
// joinHeaderAndPayload combines header and payload into a single byte slice.
400+
func (t *FSTree) joinHeaderAndPayload(header, payload []byte) []byte {
401+
if header == nil {
402+
return payload
403+
}
404+
405+
hLen := len(header)
406+
payload = t.Compress(payload)
407+
pLen := len(payload)
408+
409+
data := make([]byte, hLen+pLen+streamDataOff)
410+
data[0] = streamPrefix
411+
data[1] = 0 // version 0
412+
binary.BigEndian.PutUint32(data[streamLenHeaderOff:], uint32(hLen))
413+
binary.BigEndian.PutUint32(data[streamLenDataOff:], uint32(pLen))
414+
415+
copy(data[streamDataOff:], header)
416+
copy(data[streamDataOff+hLen:], payload)
417+
418+
return data
419+
}
420+
378421
// Get returns an object from the storage by address.
379422
func (t *FSTree) Get(addr oid.Address) (*objectSDK.Object, error) {
380423
data, err := t.getObjBytes(addr)
@@ -433,6 +476,16 @@ func parseCombinedPrefix(p []byte) ([]byte, uint32) {
433476
binary.BigEndian.Uint32(p[combinedLengthOff:combinedDataOff])
434477
}
435478

479+
// parseStreamPrefix checks the given byte slice for stream prefix and returns
480+
// the length of the header and data if so (0, 0 otherwise).
481+
func parseStreamPrefix(p []byte) (uint32, uint32) {
482+
if p[0] != streamPrefix || p[1] != 0 { // Only version 0 is supported now.
483+
return 0, 0
484+
}
485+
return binary.BigEndian.Uint32(p[streamLenHeaderOff:streamLenDataOff]),
486+
binary.BigEndian.Uint32(p[streamLenDataOff:])
487+
}
488+
436489
func (t *FSTree) extractCombinedObject(id oid.ID, f *os.File) ([]byte, error) {
437490
var (
438491
comBuf [combinedDataOff]byte
@@ -485,6 +538,23 @@ func (t *FSTree) readFullObject(f io.Reader, initial []byte, size int64) ([]byte
485538
return nil, fmt.Errorf("read: %w", err)
486539
}
487540
data = data[:len(initial)+n]
541+
hLen, _ := parseStreamPrefix(data)
542+
if hLen > 0 {
543+
data = data[streamDataOff:]
544+
payload, err := t.Decompress(data[hLen:])
545+
if err != nil {
546+
return nil, fmt.Errorf("decompress payload: %w", err)
547+
}
548+
pLen := len(payload)
549+
payloadNum := protowire.Number(4)
550+
n := protowire.SizeTag(payloadNum) + protowire.SizeVarint(uint64(pLen))
551+
buf := make([]byte, int(hLen)+pLen+n)
552+
copy(buf[:hLen], data)
553+
off := binary.PutUvarint(buf[hLen:], protowire.EncodeTag(payloadNum, protowire.BytesType)) + int(hLen)
554+
off += binary.PutUvarint(buf[off:], uint64(pLen))
555+
copy(buf[off:], payload)
556+
data = buf
557+
}
488558

489559
return t.Decompress(data)
490560
}

pkg/local_object_storage/blobstor/fstree/getstream_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestGetStream(t *testing.T) {
4141
obj.SetID(addr.Object())
4242
obj.SetPayload(payload)
4343

44-
require.NoError(t, tree.Put(addr, obj.Marshal()))
44+
require.NoError(t, tree.Put(addr, obj.Marshal(), nil))
4545

4646
retrievedObj, reader, err := tree.GetStream(addr)
4747
require.NoError(t, err)
@@ -108,7 +108,7 @@ func TestGetStreamAfterErrors(t *testing.T) {
108108
payload := []byte("test payload")
109109
obj.SetPayload(payload)
110110

111-
require.NoError(t, tree.Put(addr, obj.Marshal()))
111+
require.NoError(t, tree.Put(addr, obj.Marshal(), nil))
112112

113113
objPath := tree.treePath(addr)
114114

pkg/local_object_storage/blobstor/fstree/head.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,47 @@ func (t *FSTree) extractHeaderAndStream(id oid.ID, f *os.File) (*objectSDK.Objec
124124

125125
// readHeaderAndPayload reads an object header from the file and returns reader for payload.
126126
// This function takes ownership of the io.ReadCloser and will close it if it does not return it.
127-
func (t *FSTree) readHeaderAndPayload(f io.ReadCloser, initial []byte) (*objectSDK.Object, io.ReadSeekCloser, error) {
127+
func (t *FSTree) readHeaderAndPayload(f io.ReadSeekCloser, initial []byte) (*objectSDK.Object, io.ReadSeekCloser, error) {
128128
var err error
129+
var hLen, dLen uint32
130+
if len(initial) >= streamDataOff {
131+
hLen, dLen = parseStreamPrefix(initial)
132+
} else {
133+
var p []byte
134+
copy(p[:], initial)
135+
_, err := io.ReadFull(f, p[len(initial):])
136+
if err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) {
137+
return nil, f, fmt.Errorf("read stream prefix: %w", err)
138+
}
139+
hLen, dLen = parseStreamPrefix(p)
140+
if hLen == 0 {
141+
initial = p[:]
142+
}
143+
}
144+
if hLen > 0 {
145+
initial = initial[streamDataOff:]
146+
var header []byte
147+
if len(initial) < int(hLen) {
148+
header = make([]byte, hLen)
149+
copy(header, initial)
150+
_, err = io.ReadFull(f, header[len(initial):])
151+
if err != nil {
152+
return nil, nil, fmt.Errorf("read stream header: %w", err)
153+
}
154+
initial = header
155+
}
156+
header = initial[:hLen]
157+
var obj objectSDK.Object
158+
err = obj.Unmarshal(header)
159+
if err != nil {
160+
return nil, nil, fmt.Errorf("unmarshal object: %w", err)
161+
}
162+
return &obj, &payloadReader{
163+
Reader: io.LimitReader(io.MultiReader(bytes.NewReader(initial[hLen:]), f), int64(dLen)),
164+
close: f.Close,
165+
}, nil
166+
}
167+
129168
if len(initial) < objectSDK.MaxHeaderLen {
130169
_ = f.Close()
131170
initial, err = t.Decompress(initial)

pkg/local_object_storage/blobstor/fstree/head_bench_test.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func runHeadVsGetBenchmark(b *testing.B, payloadSize int, compressed bool) {
7171
obj := generateTestObject(payloadSize)
7272
addr := object.AddressOf(obj)
7373

74-
require.NoError(b, fsTree.Put(addr, obj.Marshal()))
74+
require.NoError(b, fsTree.Put(addr, obj.Payload(), obj.CutPayload().Marshal()))
7575

7676
b.Run("Head"+suffix, func(b *testing.B) {
7777
b.ResetTimer()
@@ -96,6 +96,53 @@ func runHeadVsGetBenchmark(b *testing.B, payloadSize int, compressed bool) {
9696
})
9797
}
9898

99+
func BenchmarkFSTree_NewStructureHead(b *testing.B) {
100+
for _, size := range payloadSizes {
101+
b.Run(generateSizeLabel(size), func(b *testing.B) {
102+
for _, tc := range []struct {
103+
name string
104+
compressed bool
105+
nilHeader bool
106+
}{
107+
{"WithHeader", false, false},
108+
{"WithoutHeader", false, true},
109+
{"WithHeader_Compressed", true, false},
110+
{"WithoutHeader_Compressed", true, true},
111+
} {
112+
b.Run(tc.name, func(b *testing.B) {
113+
fsTree := fstree.New(fstree.WithPath(b.TempDir()))
114+
if tc.compressed {
115+
compressConfig := &compression.Config{Enabled: true}
116+
require.NoError(b, compressConfig.Init())
117+
fsTree.SetCompressor(compressConfig)
118+
}
119+
require.NoError(b, fsTree.Open(false))
120+
require.NoError(b, fsTree.Init())
121+
122+
obj := generateTestObject(size)
123+
addr := object.AddressOf(obj)
124+
125+
if tc.nilHeader {
126+
payload := obj.Marshal()
127+
require.NoError(b, fsTree.Put(addr, payload, nil))
128+
} else {
129+
header := obj.CutPayload().Marshal()
130+
payload := obj.Payload()
131+
require.NoError(b, fsTree.Put(addr, payload, header))
132+
}
133+
134+
b.ReportAllocs()
135+
b.ResetTimer()
136+
for range b.N {
137+
_, err := fsTree.Head(addr)
138+
require.NoError(b, err)
139+
}
140+
})
141+
}
142+
})
143+
}
144+
}
145+
99146
func generateTestObject(payloadSize int) *objectSDK.Object {
100147
obj := objecttest.Object()
101148
if payloadSize > 0 {

pkg/local_object_storage/blobstor/fstree/head_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestHeadStorage(t *testing.T) {
2323
addAttribute(obj, "test-key1", "test-value1")
2424
addAttribute(obj, "test-key2", "test-value2")
2525

26-
err := fsTree.Put(object.AddressOf(obj), obj.Marshal())
26+
err := fsTree.Put(object.AddressOf(obj), obj.Payload(), obj.CutPayload().Marshal())
2727
require.NoError(t, err)
2828

2929
res, err := fsTree.Head(object.AddressOf(obj))
@@ -42,15 +42,15 @@ func TestHeadStorage(t *testing.T) {
4242
testCombinedObjects := func(t *testing.T, fsTree *fstree.FSTree, size int) {
4343
const numObjects = 100
4444

45-
objMap := make(map[oid.Address][]byte, numObjects)
45+
objMap := make(map[oid.Address][2][]byte, numObjects)
4646
objects := make([]*objectSDK.Object, numObjects)
4747
for i := range numObjects {
4848
obj := generateTestObject(size)
4949
obj.SetAttributes()
5050
addAttribute(obj, fmt.Sprintf("key-%d", i), fmt.Sprintf("value-%d", i))
5151

5252
objects[i] = obj
53-
objMap[object.AddressOf(obj)] = obj.Marshal()
53+
objMap[object.AddressOf(obj)] = [2][]byte{obj.Payload(), obj.CutPayload().Marshal()}
5454
}
5555

5656
require.NoError(t, fsTree.PutBatch(objMap))
@@ -75,7 +75,7 @@ func TestHeadStorage(t *testing.T) {
7575
addAttribute(obj, fmt.Sprintf("key%d", i), fmt.Sprintf("value%d", i))
7676
}
7777

78-
err := fsTree.Put(object.AddressOf(obj), obj.Marshal())
78+
err := fsTree.Put(object.AddressOf(obj), obj.Payload(), obj.CutPayload().Marshal())
7979
require.NoError(t, err)
8080

8181
res, err := fsTree.Head(object.AddressOf(obj))

0 commit comments

Comments
 (0)