0eef0ede89
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.
244 lines
9.0 KiB
Go
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())
|
|
}
|