Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a3e2e7e
sn/object: Refactor broadcast on PUT
cthulhu-rider Jul 7, 2025
464adb5
sn/object: Separate code for ready object saving
cthulhu-rider Jul 8, 2025
7628001
sn/object: Replace stateless method with standalone function
cthulhu-rider Jul 15, 2025
b798c9a
sn/object: Initial support for erasure coding policies by PUT server
cthulhu-rider Aug 5, 2025
24bf2ea
sn/object: Do not acquire meta lock for reading without concurrency
cthulhu-rider Aug 6, 2025
9fb9eea
sn/object: Deduplicate copying struct fields
cthulhu-rider Aug 6, 2025
3efe8b0
sn/object: De-struct `SendChunk()` parameters containing single field
cthulhu-rider Aug 6, 2025
334c62f
sn/object: Split required and optional parameters of `Streamer.Init()`
cthulhu-rider Aug 6, 2025
88e3efe
sn/object: Accept optional `Streamer.Init()` parameters by value
cthulhu-rider Aug 6, 2025
ecca22f
sn/object: Typedef relay function signature for its deduplication
cthulhu-rider Aug 6, 2025
917116a
sn/object: Pass relay function parameter explicitly
cthulhu-rider Aug 6, 2025
b393035
sn/object: De-struct `Streamer.Close()` return containing single field
cthulhu-rider Aug 6, 2025
63cd90a
sn/object: Check PUT message stream flow in server handler
cthulhu-rider Aug 6, 2025
8e9b99b
sn/object: Deduplicate payload limit check in forwarded PUT case
cthulhu-rider Aug 6, 2025
12aa4e8
sn/object: Drop internal target entity from errors' context
cthulhu-rider Aug 6, 2025
ad16f49
sn/object: Bring `putsvc.Streamer` interface closer to proto PUT stream
cthulhu-rider Aug 6, 2025
95e9c29
sn/object: Do not keep `internal.Target` in `Streamer` struct
cthulhu-rider Aug 6, 2025
4808244
sn/object: Replace context struct field with a parameter
cthulhu-rider Aug 6, 2025
e252c57
sn/object: Drop `putsvc.Streamer` type
cthulhu-rider Aug 6, 2025
0e739bd
sn/object: Export struct fields instead of providing setters
cthulhu-rider Aug 6, 2025
84d3910
sn/object: Inline and drop once used struct
cthulhu-rider Aug 6, 2025
f1f3070
sn/object: Do not copy `Service` fields to `distributedTarget`
cthulhu-rider Aug 6, 2025
c6dbbf3
sn/object: Rename mutex field closer to the protected one
cthulhu-rider Aug 6, 2025
9160803
sn/object: Struct out state fields of `dsitributedTarget`
cthulhu-rider Aug 6, 2025
45dcca4
sn/object: Regroup `distributedTarget` struct fields
cthulhu-rider Aug 6, 2025
d6634d7
sn/object: Get rid of `newCommonTarget` function
cthulhu-rider Aug 6, 2025
00af94f
sn/object: Inline `prepareOptions` method
cthulhu-rider Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions cmd/neofs-node/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/google/uuid"
lru "github.com/hashicorp/golang-lru/v2"
iec "github.com/nspcc-dev/neofs-node/internal/ec"
coreclient "github.com/nspcc-dev/neofs-node/pkg/core/client"
containercore "github.com/nspcc-dev/neofs-node/pkg/core/container"
"github.com/nspcc-dev/neofs-node/pkg/core/netmap"
Expand Down Expand Up @@ -49,7 +50,7 @@ import (
)

type objectSvc struct {
put *putsvc.Service
*putsvc.Service

search *searchsvc.Service

Expand All @@ -69,10 +70,6 @@ func (c *cfg) MaxObjectSize() uint64 {
return sz
}

func (s *objectSvc) Put(ctx context.Context) (*putsvc.Streamer, error) {
return s.put.Put(ctx)
}

func (s *objectSvc) Head(ctx context.Context, prm getsvc.HeadPrm) error {
return s.get.Head(ctx, prm)
}
Expand Down Expand Up @@ -276,10 +273,10 @@ func initObjectService(c *cfg) {
)

objSvc := &objectSvc{
put: sPut,
search: sSearch,
get: sGet,
delete: sDelete,
Service: sPut,
search: sSearch,
get: sGet,
delete: sDelete,
}

// cachedFirstObjectsNumber is a total cached objects number; the V2 split scheme
Expand Down Expand Up @@ -804,6 +801,7 @@ type containerNodesSorter struct {

func (x *containerNodesSorter) Unsorted() [][]netmapsdk.NodeInfo { return x.policy.nodeSets }
func (x *containerNodesSorter) PrimaryCounts() []uint { return x.policy.repCounts }
func (x *containerNodesSorter) ECRules() []iec.Rule { return nil }
func (x *containerNodesSorter) SortForObject(obj oid.ID) ([][]netmapsdk.NodeInfo, error) {
cacheKey := objectNodesCacheKey{epoch: x.curEpoch}
cacheKey.addr.SetContainer(x.cnrID)
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ require (
github.com/google/uuid v1.6.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/klauspost/compress v1.17.11
github.com/klauspost/reedsolomon v1.12.4
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.5.0
github.com/mr-tron/base58 v1.2.0
github.com/multiformats/go-multiaddr v0.12.2
github.com/mxschmitt/golang-combinations v1.2.0
github.com/nspcc-dev/hrw/v2 v2.0.3
github.com/nspcc-dev/locode-db v0.6.0
github.com/nspcc-dev/neo-go v0.111.0
Expand Down Expand Up @@ -62,7 +64,7 @@ require (
github.com/holiman/uint256 v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect
Expand Down
9 changes: 6 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/reedsolomon v1.12.4 h1:5aDr3ZGoJbgu/8+j45KtUJxzYm8k08JGtB9Wx1VQ4OA=
github.com/klauspost/reedsolomon v1.12.4/go.mod h1:d3CzOMOt0JXGIFZm1StgkyF14EYr3xneR2rNWo7NcMU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand Down Expand Up @@ -185,6 +187,8 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxschmitt/golang-combinations v1.2.0 h1:V5E7MncIK8Yr1SL/SpdqMuSquFsfoIs5auI7Y3n8z14=
github.com/mxschmitt/golang-combinations v1.2.0/go.mod h1:RCm5eR03B+JrBOMRDLsKZWShluXdrHu+qwhPEJ0miBM=
github.com/nspcc-dev/bbolt v0.0.0-20250612101626-5df2544a4a22 h1:M5Nmg1iCnbZngzIBDIlMr9vW+okFfcSMBvBlXG8r+14=
github.com/nspcc-dev/bbolt v0.0.0-20250612101626-5df2544a4a22/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk=
github.com/nspcc-dev/dbft v0.4.0 h1:4/atD4GrrMEtrYBDiZPrPzdKZ6ws7PR/cg0M4DEdVeI=
Expand Down Expand Up @@ -358,7 +362,6 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down
81 changes: 81 additions & 0 deletions internal/ec/ec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ec

import (
"fmt"
"slices"
"strconv"

"github.com/klauspost/reedsolomon"
"golang.org/x/exp/constraints"
)

// Erasure coding attributes.
const (
AttributePrefix = "__NEOFS__EC_"
AttributeRuleIdx = AttributePrefix + "RULE_IDX"
AttributePartIdx = AttributePrefix + "PART_IDX"
)

// Rule represents erasure coding rule for object payload's encoding and placement.
type Rule struct {
DataPartNum uint8
ParityPartNum uint8
}

// String implements [fmt.Stringer].
func (x Rule) String() string {
return strconv.FormatUint(uint64(x.DataPartNum), 10) + "/" + strconv.FormatUint(uint64(x.ParityPartNum), 10)
}

// Encode encodes given data according to specified EC rule and returns coded
// parts. First [Rule.DataPartNum] elements are data parts, other
// [Rule.ParityPartNum] ones are parity blocks.
//
// All parts are the same length. If data len is not divisible by
// [Rule.DataPartNum], last data part is aligned with zeros.
//
// If data is empty, all parts are nil.
func Encode(rule Rule, data []byte) ([][]byte, error) {
if len(data) == 0 {
return make([][]byte, rule.DataPartNum+rule.ParityPartNum), nil
}

// TODO: Explore reedsolomon.Option for performance improvement. https://github.com/nspcc-dev/neofs-node/issues/3501
enc, err := reedsolomon.New(int(rule.DataPartNum), int(rule.ParityPartNum))
if err != nil { // should never happen with correct rule
return nil, fmt.Errorf("init Reed-Solomon encoder: %w", err)
}

parts, err := enc.Split(data)
if err != nil {
return nil, fmt.Errorf("split data: %w", err)
}

if err := enc.Encode(parts); err != nil {
return nil, fmt.Errorf("calculate Reed-Solomon parity: %w", err)
}

return parts, nil
}

// Decode decodes source data of known len from EC parts obtained by applying
// specified rule.
func Decode[LT constraints.Unsigned](rule Rule, dataLen LT, parts [][]byte) ([]byte, error) {
// TODO: Explore reedsolomon.Option for performance improvement. https://github.com/nspcc-dev/neofs-node/issues/3501
dec, err := reedsolomon.New(int(rule.DataPartNum), int(rule.ParityPartNum))
if err != nil { // should never happen with correct rule
return nil, fmt.Errorf("init Reed-Solomon decoder: %w", err)
}

required := make([]bool, rule.DataPartNum+rule.ParityPartNum)
for i := range rule.DataPartNum {
required[i] = true
}

if err := dec.ReconstructSome(parts, required); err != nil {
return nil, fmt.Errorf("restore Reed-Solomon: %w", err)
}

// TODO: last part may be shorter, do not overallocate buffer.
return slices.Concat(parts[:rule.DataPartNum]...)[:dataLen], nil
}
76 changes: 76 additions & 0 deletions internal/ec/ec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package ec_test

import (
"testing"

"github.com/klauspost/reedsolomon"
iec "github.com/nspcc-dev/neofs-node/internal/ec"
islices "github.com/nspcc-dev/neofs-node/internal/slices"
"github.com/nspcc-dev/neofs-node/internal/testutil"
"github.com/stretchr/testify/require"
)

func TestRule_String(t *testing.T) {
r := iec.Rule{
DataPartNum: 12,
ParityPartNum: 23,
}
require.Equal(t, "12/23", r.String())
}

func testEncode(t *testing.T, rule iec.Rule, data []byte) {
ln := uint(len(data))

parts, err := iec.Encode(rule, data)
require.NoError(t, err)

res, err := iec.Decode(rule, ln, parts)
require.NoError(t, err)
require.Equal(t, data, res)

for lostCount := 1; lostCount <= int(rule.ParityPartNum); lostCount++ {
for _, lostIdxs := range islices.IndexCombos(len(parts), lostCount) {
res, err := iec.Decode(rule, ln, islices.NilTwoDimSliceElements(parts, lostIdxs))
require.NoError(t, err)
require.Equal(t, data, res)
}
}

for _, lostIdxs := range islices.IndexCombos(len(parts), int(rule.ParityPartNum)+1) {
_, err := iec.Decode(rule, ln, islices.NilTwoDimSliceElements(parts, lostIdxs))
require.ErrorContains(t, err, "restore Reed-Solomon")
require.ErrorIs(t, err, reedsolomon.ErrTooFewShards)
}
}

func TestEncode(t *testing.T) {
rules := []iec.Rule{
{DataPartNum: 3, ParityPartNum: 1},
{DataPartNum: 12, ParityPartNum: 4},
}

data := testutil.RandByteSlice(4 << 10)

t.Run("empty", func(t *testing.T) {
for _, rule := range rules {
t.Run(rule.String(), func(t *testing.T) {
test := func(t *testing.T, data []byte) {
res, err := iec.Encode(rule, []byte{})
require.NoError(t, err)

total := int(rule.DataPartNum + rule.ParityPartNum)
require.Len(t, res, total)
require.EqualValues(t, total, islices.CountNilsInTwoDimSlice(res))
}
test(t, nil)
test(t, []byte{})
})
}
})

for _, rule := range rules {
t.Run(rule.String(), func(t *testing.T) {
testEncode(t, rule, data)
})
}
}
Loading
Loading