ipfs-cluster/datastore/leveldb/config.go
Hector Sanjuan 0eef0ede89 Support a levelDB backend for cluster
Badger can take 1000x the amount of needed space if not GC'ed or compacted
(#1320), even for non heavy usage. Cluster has no provisions to run datastore
GC operations and while they could be added, they are not ensured to
help. Improvements on Badger v3 might help but would still need to GC
explicitally.

Cluster was however designed to support any go-datastore as backend.

This commit adds LevelDB support. LevelDB go-datastore wrapper is mature, does
not need GC and should work well for most cluster usecases, which are not
overly demanding.

A new `--datastore` flag has been added on init. The store backend is selected
based on the value in the configuration, similar to how raft/crdt is. The
default is set to leveldb. From now on it should be easier to add additional
backends, i.e. badgerv3.
2021-06-09 19:40:36 +02:00

244 lines
9.0 KiB
Go

package leveldb
import (
"encoding/json"
"errors"
"path/filepath"
"github.com/imdario/mergo"
"github.com/ipfs/ipfs-cluster/config"
"github.com/kelseyhightower/envconfig"
goleveldb "github.com/syndtr/goleveldb/leveldb/opt"
)
const configKey = "leveldb"
const envConfigKey = "cluster_leveldb"
// Default values for LevelDB Config
const (
DefaultSubFolder = "leveldb"
)
var (
// DefaultLevelDBOptions carries default options. Values are customized during Init().
DefaultLevelDBOptions goleveldb.Options
)
func init() {
// go-ipfs uses defaults and only allows to configure compression, but
// otherwise stores a small amount of values in LevelDB.
// We leave defaults.
// Example:
DefaultLevelDBOptions.NoSync = false
}
// Config is used to initialize a LevelDB datastore. It implements the
// ComponentConfig interface.
type Config struct {
config.Saver
// The folder for this datastore. Non-absolute paths are relative to
// the base configuration folder.
Folder string
LevelDBOptions goleveldb.Options
}
// levelDBOptions allows json serialization in our configuration of the
// goleveldb Options.
type levelDBOptions struct {
BlockCacheCapacity int `json:"block_cache_capacity"`
BlockCacheEvictRemoved bool `json:"block_cache_evict_removed"`
BlockRestartInterval int `json:"block_restart_interval"`
BlockSize int `json:"block_size"`
CompactionExpandLimitFactor int `json:"compaction_expand_limit_factor"`
CompactionGPOverlapsFactor int `json:"compaction_gp_overlaps_factor"`
CompactionL0Trigger int `json:"compaction_l0_trigger"`
CompactionSourceLimitFactor int `json:"compaction_source_limit_factor"`
CompactionTableSize int `json:"compaction_table_size"`
CompactionTableSizeMultiplier float64 `json:"compaction_table_size_multiplier"`
CompactionTableSizeMultiplierPerLevel []float64 `json:"compaction_table_size_multiplier_per_level"`
CompactionTotalSize int `json:"compaction_total_size"`
CompactionTotalSizeMultiplier float64 `json:"compaction_total_size_multiplier"`
CompactionTotalSizeMultiplierPerLevel []float64 `json:"compaction_total_size_multiplier_per_level"`
Compression uint `json:"compression"`
DisableBufferPool bool `json:"disable_buffer_pool"`
DisableBlockCache bool `json:"disable_block_cache"`
DisableCompactionBackoff bool `json:"disable_compaction_backoff"`
DisableLargeBatchTransaction bool `json:"disable_large_batch_transaction"`
IteratorSamplingRate int `json:"iterator_sampling_rate"`
NoSync bool `json:"no_sync"`
NoWriteMerge bool `json:"no_write_merge"`
OpenFilesCacheCapacity int `json:"open_files_cache_capacity"`
ReadOnly bool `json:"read_only"`
Strict uint `json:"strict"`
WriteBuffer int `json:"write_buffer"`
WriteL0PauseTrigger int `json:"write_l0_pause_trigger"`
WriteL0SlowdownTrigger int `json:"write_l0_slowdown_trigger"`
}
func (ldbo *levelDBOptions) Unmarshal() *goleveldb.Options {
goldbo := &goleveldb.Options{}
goldbo.BlockCacheCapacity = ldbo.BlockCacheCapacity
goldbo.BlockCacheEvictRemoved = ldbo.BlockCacheEvictRemoved
goldbo.BlockRestartInterval = ldbo.BlockRestartInterval
goldbo.BlockSize = ldbo.BlockSize
goldbo.CompactionExpandLimitFactor = ldbo.CompactionExpandLimitFactor
goldbo.CompactionGPOverlapsFactor = ldbo.CompactionGPOverlapsFactor
goldbo.CompactionL0Trigger = ldbo.CompactionL0Trigger
goldbo.CompactionSourceLimitFactor = ldbo.CompactionSourceLimitFactor
goldbo.CompactionTableSize = ldbo.CompactionTableSize
goldbo.CompactionTableSizeMultiplier = ldbo.CompactionTableSizeMultiplier
goldbo.CompactionTableSizeMultiplierPerLevel = ldbo.CompactionTableSizeMultiplierPerLevel
goldbo.CompactionTotalSize = ldbo.CompactionTotalSize
goldbo.CompactionTotalSizeMultiplier = ldbo.CompactionTotalSizeMultiplier
goldbo.CompactionTotalSizeMultiplierPerLevel = ldbo.CompactionTotalSizeMultiplierPerLevel
goldbo.Compression = goleveldb.Compression(ldbo.Compression)
goldbo.DisableBufferPool = ldbo.DisableBufferPool
goldbo.DisableBlockCache = ldbo.DisableBlockCache
goldbo.DisableCompactionBackoff = ldbo.DisableCompactionBackoff
goldbo.DisableLargeBatchTransaction = ldbo.DisableLargeBatchTransaction
goldbo.IteratorSamplingRate = ldbo.IteratorSamplingRate
goldbo.NoSync = ldbo.NoSync
goldbo.NoWriteMerge = ldbo.NoWriteMerge
goldbo.OpenFilesCacheCapacity = ldbo.OpenFilesCacheCapacity
goldbo.ReadOnly = ldbo.ReadOnly
goldbo.Strict = goleveldb.Strict(ldbo.Strict)
goldbo.WriteBuffer = ldbo.WriteBuffer
goldbo.WriteL0PauseTrigger = ldbo.WriteL0PauseTrigger
goldbo.WriteL0SlowdownTrigger = ldbo.WriteL0SlowdownTrigger
return goldbo
}
func (ldbo *levelDBOptions) Marshal(goldbo *goleveldb.Options) {
ldbo.BlockCacheCapacity = goldbo.BlockCacheCapacity
ldbo.BlockCacheEvictRemoved = goldbo.BlockCacheEvictRemoved
ldbo.BlockRestartInterval = goldbo.BlockRestartInterval
ldbo.BlockSize = goldbo.BlockSize
ldbo.CompactionExpandLimitFactor = goldbo.CompactionExpandLimitFactor
ldbo.CompactionGPOverlapsFactor = goldbo.CompactionGPOverlapsFactor
ldbo.CompactionL0Trigger = goldbo.CompactionL0Trigger
ldbo.CompactionSourceLimitFactor = goldbo.CompactionSourceLimitFactor
ldbo.CompactionTableSize = goldbo.CompactionTableSize
ldbo.CompactionTableSizeMultiplier = goldbo.CompactionTableSizeMultiplier
ldbo.CompactionTableSizeMultiplierPerLevel = goldbo.CompactionTableSizeMultiplierPerLevel
ldbo.CompactionTotalSize = goldbo.CompactionTotalSize
ldbo.CompactionTotalSizeMultiplier = goldbo.CompactionTotalSizeMultiplier
ldbo.CompactionTotalSizeMultiplierPerLevel = goldbo.CompactionTotalSizeMultiplierPerLevel
ldbo.Compression = uint(goldbo.Compression)
ldbo.DisableBufferPool = goldbo.DisableBufferPool
ldbo.DisableBlockCache = goldbo.DisableBlockCache
ldbo.DisableCompactionBackoff = goldbo.DisableCompactionBackoff
ldbo.DisableLargeBatchTransaction = goldbo.DisableLargeBatchTransaction
ldbo.IteratorSamplingRate = goldbo.IteratorSamplingRate
ldbo.NoSync = goldbo.NoSync
ldbo.NoWriteMerge = goldbo.NoWriteMerge
ldbo.OpenFilesCacheCapacity = goldbo.OpenFilesCacheCapacity
ldbo.ReadOnly = goldbo.ReadOnly
ldbo.Strict = uint(goldbo.Strict)
ldbo.WriteBuffer = goldbo.WriteBuffer
ldbo.WriteL0PauseTrigger = goldbo.WriteL0PauseTrigger
ldbo.WriteL0SlowdownTrigger = goldbo.WriteL0SlowdownTrigger
}
type jsonConfig struct {
Folder string `json:"folder,omitempty"`
LevelDBOptions levelDBOptions `json:"leveldb_options,omitempty"`
}
// ConfigKey returns a human-friendly identifier for this type of Datastore.
func (cfg *Config) ConfigKey() string {
return configKey
}
// Default initializes this Config with sensible values.
func (cfg *Config) Default() error {
cfg.Folder = DefaultSubFolder
cfg.LevelDBOptions = DefaultLevelDBOptions
return nil
}
// ApplyEnvVars fills in any Config fields found as environment variables.
func (cfg *Config) ApplyEnvVars() error {
jcfg := cfg.toJSONConfig()
err := envconfig.Process(envConfigKey, jcfg)
if err != nil {
return err
}
return cfg.applyJSONConfig(jcfg)
}
// Validate checks that the fields of this Config have working values,
// at least in appearance.
func (cfg *Config) Validate() error {
if cfg.Folder == "" {
return errors.New("folder is unset")
}
return nil
}
// LoadJSON reads the fields of this Config from a JSON byteslice as
// generated by ToJSON.
func (cfg *Config) LoadJSON(raw []byte) error {
jcfg := &jsonConfig{}
err := json.Unmarshal(raw, jcfg)
if err != nil {
return err
}
cfg.Default()
return cfg.applyJSONConfig(jcfg)
}
func (cfg *Config) applyJSONConfig(jcfg *jsonConfig) error {
config.SetIfNotDefault(jcfg.Folder, &cfg.Folder)
ldbOpts := jcfg.LevelDBOptions.Unmarshal()
if err := mergo.Merge(&cfg.LevelDBOptions, ldbOpts, mergo.WithOverride); err != nil {
return err
}
return cfg.Validate()
}
// ToJSON generates a JSON-formatted human-friendly representation of this
// Config.
func (cfg *Config) ToJSON() (raw []byte, err error) {
jcfg := cfg.toJSONConfig()
raw, err = config.DefaultJSONMarshal(jcfg)
return
}
func (cfg *Config) toJSONConfig() *jsonConfig {
jCfg := &jsonConfig{}
if cfg.Folder != DefaultSubFolder {
jCfg.Folder = cfg.Folder
}
bo := &levelDBOptions{}
bo.Marshal(&cfg.LevelDBOptions)
jCfg.LevelDBOptions = *bo
return jCfg
}
// GetFolder returns the LevelDB folder.
func (cfg *Config) GetFolder() string {
if filepath.IsAbs(cfg.Folder) {
return cfg.Folder
}
return filepath.Join(cfg.BaseDir, cfg.Folder)
}
// ToDisplayJSON returns JSON config as a string.
func (cfg *Config) ToDisplayJSON() ([]byte, error) {
return config.DisplayJSON(cfg.toJSONConfig())
}