Skip to content

Commit

Permalink
Combined writing for FSTree (#2814)
Browse files Browse the repository at this point in the history
We can think of replacing peapod with it.
  • Loading branch information
roman-khimov authored Aug 28, 2024
2 parents 7baa16c + 1b40ec4 commit 9975aa4
Show file tree
Hide file tree
Showing 21 changed files with 556 additions and 154 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog for NeoFS Node
## [Unreleased]

### Added
- More effective FSTree writer for HDDs, new configuration options for it (#2814)

### Fixed

Expand Down
14 changes: 10 additions & 4 deletions cmd/neofs-lens/internal/storage/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
engineconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine"
shardconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard"
fstreeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/blobstor/fstree"
peapodconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/blobstor/peapod"
"github.com/nspcc-dev/neofs-node/cmd/neofs-node/storage"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree"
Expand Down Expand Up @@ -119,15 +118,18 @@ func openEngine(cmd *cobra.Command) *engine.StorageEngine {
sCfg.Typ = storagesCfg[i].Type()
sCfg.Path = storagesCfg[i].Path()
sCfg.Perm = storagesCfg[i].Perm()
sCfg.FlushInterval = storagesCfg[i].FlushInterval()

switch storagesCfg[i].Type() {
case fstree.Type:
sub := fstreeconfig.From((*config.Config)(storagesCfg[i]))
sCfg.Depth = sub.Depth()
sCfg.NoSync = sub.NoSync()
sCfg.CombinedCountLimit = sub.CombinedCountLimit()
sCfg.CombinedSizeLimit = sub.CombinedSizeLimit()
sCfg.CombinedSizeThreshold = sub.CombinedSizeThreshold()
case peapod.Type:
peapodCfg := peapodconfig.From((*config.Config)(storagesCfg[i]))
sCfg.FlushInterval = peapodCfg.FlushInterval()
// Nothing peapod-specific, but it should work.
default:
return fmt.Errorf("can't initiate storage. invalid storage type: %s", storagesCfg[i].Type())
}
Expand Down Expand Up @@ -193,7 +195,11 @@ func openEngine(cmd *cobra.Command) *engine.StorageEngine {
fstree.WithPath(sRead.Path),
fstree.WithPerm(sRead.Perm),
fstree.WithDepth(sRead.Depth),
fstree.WithNoSync(sRead.NoSync)),
fstree.WithNoSync(sRead.NoSync),
fstree.WithCombinedCountLimit(sRead.CombinedCountLimit),
fstree.WithCombinedSizeLimit(sRead.CombinedSizeLimit),
fstree.WithCombinedSizeThreshold(sRead.CombinedSizeThreshold),
fstree.WithCombinedWriteInterval(sRead.FlushInterval)),
Policy: func(_ *objectSDK.Object, data []byte) bool {
return true
},
Expand Down
4 changes: 1 addition & 3 deletions cmd/neofs-lens/internal/storage/sanity.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
engineconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine"
shardconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard"
fstreeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/blobstor/fstree"
peapodconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/blobstor/peapod"
objectcore "github.com/nspcc-dev/neofs-node/pkg/core/object"
commonb "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/common"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/compression"
Expand Down Expand Up @@ -77,8 +76,7 @@ func sanityCheck(cmd *cobra.Command, _ []string) {
default:
return fmt.Errorf("unsupported sub-storage type '%s'", subCfg.Type())
case peapod.Type:
peapodCfg := peapodconfig.From((*config.Config)(subCfg))
sh.p = peapod.New(subCfg.Path(), subCfg.Perm(), peapodCfg.FlushInterval())
sh.p = peapod.New(subCfg.Path(), subCfg.Perm(), subCfg.FlushInterval())

var compressCfg compression.Config
err := compressCfg.Init()
Expand Down
8 changes: 5 additions & 3 deletions cmd/neofs-node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
engineconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine"
shardconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard"
fstreeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/blobstor/fstree"
peapodconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/blobstor/peapod"
loggerconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/logger"
metricsconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/metrics"
morphconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/morph"
Expand Down Expand Up @@ -224,15 +223,18 @@ func (a *applicationConfiguration) readConfig(c *config.Config) error {
sCfg.Typ = storagesCfg[i].Type()
sCfg.Path = storagesCfg[i].Path()
sCfg.Perm = storagesCfg[i].Perm()
sCfg.FlushInterval = storagesCfg[i].FlushInterval()

switch storagesCfg[i].Type() {
case fstree.Type:
sub := fstreeconfig.From((*config.Config)(storagesCfg[i]))
sCfg.Depth = sub.Depth()
sCfg.NoSync = sub.NoSync()
sCfg.CombinedCountLimit = sub.CombinedCountLimit()
sCfg.CombinedSizeLimit = sub.CombinedSizeLimit()
sCfg.CombinedSizeThreshold = sub.CombinedSizeThreshold()
case peapod.Type:
peapodCfg := peapodconfig.From((*config.Config)(storagesCfg[i]))
sCfg.FlushInterval = peapodCfg.FlushInterval()
// No specific configs, but it's a valid storage type.
default:
return fmt.Errorf("invalid storage type: %s", storagesCfg[i].Type())
}
Expand Down
7 changes: 2 additions & 5 deletions cmd/neofs-node/config/engine/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
engineconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine"
shardconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard"
fstreeconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/blobstor/fstree"
peapodconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/blobstor/peapod"
piloramaconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/engine/shard/pilorama"
configtest "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/test"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/peapod"
Expand Down Expand Up @@ -87,11 +86,10 @@ func TestEngineSection(t *testing.T) {
require.EqualValues(t, 102400, sc.SmallSizeLimit())

require.Equal(t, 2, len(ss))
ppd := peapodconfig.From((*config.Config)(ss[0]))
require.Equal(t, "tmp/0/blob/peapod.db", ss[0].Path())
require.EqualValues(t, 0644, ss[0].Perm())
require.EqualValues(t, peapod.Type, ss[0].Type())
require.EqualValues(t, 10*time.Millisecond, ppd.FlushInterval())
require.EqualValues(t, 10*time.Millisecond, ss[0].FlushInterval())

require.Equal(t, "tmp/0/blob", ss[1].Path())
require.EqualValues(t, 0644, ss[1].Perm())
Expand Down Expand Up @@ -131,11 +129,10 @@ func TestEngineSection(t *testing.T) {
require.EqualValues(t, 102400, sc.SmallSizeLimit())

require.Equal(t, 2, len(ss))
ppd := peapodconfig.From((*config.Config)(ss[0]))
require.Equal(t, "tmp/1/blob/peapod.db", ss[0].Path())
require.EqualValues(t, 0644, ss[0].Perm())
require.EqualValues(t, peapod.Type, ss[0].Type())
require.EqualValues(t, 30*time.Millisecond, ppd.FlushInterval())
require.EqualValues(t, 30*time.Millisecond, ss[0].FlushInterval())

require.Equal(t, "tmp/1/blob", ss[1].Path())
require.EqualValues(t, 0644, ss[1].Perm())
Expand Down
53 changes: 51 additions & 2 deletions cmd/neofs-node/config/engine/shard/blobstor/fstree/config.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
package fstree

import (
"math"

"github.com/nspcc-dev/neofs-node/cmd/neofs-node/config"
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/fstree"
"github.com/spf13/cast"
)

// Config is a wrapper over the config section
// which provides access to FSTree configurations.
type Config config.Config

// DepthDefault is a default shallow dir depth.
const DepthDefault = 4
const (
// DepthDefault is the default shallow dir depth.
DepthDefault = 4
// CombinedCountLimitDefault is the default for the maximum number of objects to write into a single file.
CombinedCountLimitDefault = 128
// CombinedSizeLimitDefault is the default for the maximum size of the combined object file.
CombinedSizeLimitDefault = 8 * 1024 * 1024
// CombinedSizeThresholdDefault is the default for the minimal size of the object that won't be combined with others for writes.
CombinedSizeThresholdDefault = 128 * 1024
)

// From wraps config section into Config.
func From(c *config.Config) *Config {
Expand Down Expand Up @@ -45,3 +56,41 @@ func (x *Config) Depth() uint64 {
func (x *Config) NoSync() bool {
return config.BoolSafe((*config.Config)(x), "no_sync")
}

// CombinedCountLimit returns the value of "combined_count_limit" config parameter.
//
// Returns [CombinedCountLimitDefault] if the value is missing or not a positive integer.
func (x *Config) CombinedCountLimit() int {
var v = (*config.Config)(x).Value("combined_count_limit")
if v == nil {
return CombinedCountLimitDefault
}

i, err := cast.ToIntE(v)
if err != nil {
return CombinedCountLimitDefault
}
return i
}

// CombinedSizeLimit returns the value of "combined_size_limit" config parameter.
//
// Returns [CombinedSizeLimitDefault] if the value is missing, equal to 0 or not a proper size specification.
func (x *Config) CombinedSizeLimit() int {
var s = config.SizeInBytesSafe((*config.Config)(x), "combined_size_limit")
if s == 0 || s > math.MaxInt {
return CombinedSizeLimitDefault
}
return int(s)
}

// CombinedSizeThreshold returns the value of "combined_size_threshold" config parameter.
//
// Returns [CombinedSizeThresholdDefault] if the value is missing, equal to 0 or not a proper size specification.
func (x *Config) CombinedSizeThreshold() int {
var s = config.SizeInBytesSafe((*config.Config)(x), "combined_size_threshold")
if s == 0 || s > math.MaxInt {
return CombinedSizeThresholdDefault
}
return int(s)
}
34 changes: 0 additions & 34 deletions cmd/neofs-node/config/engine/shard/blobstor/peapod/config.go

This file was deleted.

23 changes: 21 additions & 2 deletions cmd/neofs-node/config/engine/shard/blobstor/storage/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ package storage

import (
"io/fs"
"time"

"github.com/nspcc-dev/neofs-node/cmd/neofs-node/config"
)

type Config config.Config

// PermDefault are default permission bits for BlobStor data.
const PermDefault = 0o640
// Various config defaults.
const (
// PermDefault are default permission bits for BlobStor data.
PermDefault = 0o640

// DefaultFlushInterval is the default time interval between Peapod's batch writes
// to disk.
DefaultFlushInterval = 10 * time.Millisecond
)

func From(x *config.Config) *Config {
return (*Config)(x)
Expand Down Expand Up @@ -53,3 +61,14 @@ func (x *Config) Perm() fs.FileMode {

return fs.FileMode(p)
}

// FlushInterval returns the value of "flush_interval" config parameter.
//
// Returns DefaultFlushInterval if the value is not a positive duration.
func (x *Config) FlushInterval() time.Duration {
d := config.DurationSafe((*config.Config)(x), "flush_interval")
if d > 0 {
return d
}
return DefaultFlushInterval
}
6 changes: 5 additions & 1 deletion cmd/neofs-node/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ func (c *cfg) shardOpts() []shardOptsWithID {
fstree.WithPath(sRead.Path),
fstree.WithPerm(sRead.Perm),
fstree.WithDepth(sRead.Depth),
fstree.WithNoSync(sRead.NoSync)),
fstree.WithNoSync(sRead.NoSync),
fstree.WithCombinedCountLimit(sRead.CombinedCountLimit),
fstree.WithCombinedSizeLimit(sRead.CombinedSizeLimit),
fstree.WithCombinedSizeThreshold(sRead.CombinedSizeThreshold),
fstree.WithCombinedWriteInterval(sRead.FlushInterval)),
Policy: func(_ *objectSDK.Object, data []byte) bool {
return true
},
Expand Down
17 changes: 9 additions & 8 deletions cmd/neofs-node/storage/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,17 @@ type ShardCfg struct {
}
type SubStorageCfg struct {
// common for all storages
Typ string
Path string
Perm fs.FileMode
Typ string
Path string
Perm fs.FileMode
FlushInterval time.Duration

// tree-specific (FS)
Depth uint64
NoSync bool

// Peapod-specific
FlushInterval time.Duration
Depth uint64
NoSync bool
CombinedCountLimit int
CombinedSizeLimit int
CombinedSizeThreshold int
}

// ID returns persistent id of a shard. It is different from the ID used in runtime
Expand Down
6 changes: 5 additions & 1 deletion config/example/node.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@
"path": "tmp/1/blob",
"no_sync": true,
"perm": "0644",
"depth": 5
"depth": 5,
"flush_interval": "20ms",
"combined_count_limit": 64,
"combined_size_limit": "16M",
"combined_size_threshold": "512K"
}
],
"pilorama": {
Expand Down
4 changes: 4 additions & 0 deletions config/example/node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ storage:
- type: fstree
path: tmp/1/blob # blobstor path
no_sync: true
flush_interval: 20ms # time interval between combined file writes to disk (defaults to 10ms)
combined_count_limit: 64 # number of small objects to write into a single file (defaults to 128)
combined_size_limit: 16M # limit for the multi-object file size (defaults to 8M)
combined_size_threshold: 512K # threshold for combined object writing (defaults to 128K)

pilorama:
path: tmp/1/blob/pilorama.db
Expand Down
21 changes: 10 additions & 11 deletions docs/storage-node-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,36 +180,35 @@ Currently only 2 types are supported: `fstree` and `peapod`.
blobstor:
- type: peapod
path: /path/to/peapod.db
depth: 1
width: 4
- type: fstree
path: /path/to/blobstor
perm: 0644
size: 4194304
depth: 1
width: 4
opened_cache_capacity: 50
```

#### Common options for sub-storages
| Parameter | Type | Default value | Description |
|-------------------------------------|-----------------------------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `path` | `string` | | Path to the root of the blobstor. |
| `perm` | file mode | `0640` | Default permission for created files and directories. |
| `flush_interval` | `duration` | `10ms` | Time interval between batch writes to disk. |

#### `fstree` type options
| Parameter | Type | Default value | Description |
|---------------------|-----------|---------------|-------------------------------------------------------|
| `path` | `string` | | Path to the root of the blobstor. |
| `perm` | file mode | `0640` | Default permission for created files and directories. |
| `depth` | `int` | `4` | File-system tree depth. |
| Parameter | Type | Default value | Description |
|---------------------------|-----------|---------------|------------------------------------------------------------------------------------------------------------------------------|
| `path` | `string` | | Path to the root of the blobstor. |
| `perm` | file mode | `0640` | Default permission for created files and directories. |
| `depth` | `int` | `4` | File-system tree depth. |
| `no_sync` | `bool` | `false` | Disable write synchronization, makes writes faster, but can lead to data loss. |
| `combined_count_limit` | `int` | `128` | Maximum number of objects to write into a single file, 0 or 1 disables combined writing (disabling is recommended for SSDs). |
| `combined_size_limit` | `size` | `8M` | Maximum size of a multi-object file. |
| `combined_size_threshold` | `size` | `128K` | Minimum size of object that won't be combined with others when writing to disk. |

#### `peapod` type options
| Parameter | Type | Default value | Description |
|---------------------|-----------|---------------|-------------------------------------------------------|
| `path` | `string` | | Path to the Peapod database file. |
| `perm` | file mode | `0640` | Default permission for created files and directories. |
| `flush_interval` | `duration`| `10ms` | Time interval between batch writes to disk. |

### `gc` subsection

Expand Down
Loading

0 comments on commit 9975aa4

Please sign in to comment.