|
| 1 | +//go:build rocksdb |
| 2 | +// +build rocksdb |
| 3 | + |
| 4 | +package config |
| 5 | + |
| 6 | +import ( |
| 7 | + "path/filepath" |
| 8 | + "runtime" |
| 9 | + "strings" |
| 10 | + |
| 11 | + dbm "github.com/cosmos/cosmos-db" |
| 12 | + "github.com/cosmos/cosmos-sdk/server/types" |
| 13 | + "github.com/linxGnu/grocksdb" |
| 14 | +) |
| 15 | + |
| 16 | +// 3G block cache |
| 17 | +const BlockCacheSize = 3 << 30 |
| 18 | + |
| 19 | +func OpenDB(_ types.AppOptions, home string, backendType dbm.BackendType) (dbm.DB, error) { |
| 20 | + dataDir := filepath.Join(home, "data") |
| 21 | + if backendType == dbm.RocksDBBackend { |
| 22 | + return openRocksdb(filepath.Join(dataDir, "application.db"), false) |
| 23 | + } |
| 24 | + |
| 25 | + return dbm.NewDB("application", backendType, dataDir) |
| 26 | +} |
| 27 | + |
| 28 | +// OpenReadOnlyDB opens rocksdb backend in read-only mode. |
| 29 | +func OpenReadOnlyDB(home string, backendType dbm.BackendType) (dbm.DB, error) { |
| 30 | + dataDir := filepath.Join(home, "data") |
| 31 | + if backendType == dbm.RocksDBBackend { |
| 32 | + return openRocksdb(filepath.Join(dataDir, "application.db"), true) |
| 33 | + } |
| 34 | + |
| 35 | + return dbm.NewDB("application", backendType, dataDir) |
| 36 | +} |
| 37 | + |
| 38 | +func openRocksdb(dir string, readonly bool) (dbm.DB, error) { |
| 39 | + opts, err := loadLatestOptions(dir) |
| 40 | + if err != nil { |
| 41 | + return nil, err |
| 42 | + } |
| 43 | + // customize rocksdb options |
| 44 | + opts = NewRocksdbOptions(opts, false) |
| 45 | + |
| 46 | + var db *grocksdb.DB |
| 47 | + if readonly { |
| 48 | + db, err = grocksdb.OpenDbForReadOnly(opts, dir, false) |
| 49 | + } else { |
| 50 | + db, err = grocksdb.OpenDb(opts, dir) |
| 51 | + } |
| 52 | + if err != nil { |
| 53 | + return nil, err |
| 54 | + } |
| 55 | + |
| 56 | + ro := grocksdb.NewDefaultReadOptions() |
| 57 | + wo := grocksdb.NewDefaultWriteOptions() |
| 58 | + woSync := grocksdb.NewDefaultWriteOptions() |
| 59 | + woSync.SetSync(true) |
| 60 | + return dbm.NewRocksDBWithRawDB(db, ro, wo, woSync), nil |
| 61 | +} |
| 62 | + |
| 63 | +// loadLatestOptions try to load options from existing db, returns nil if not exists. |
| 64 | +func loadLatestOptions(dir string) (*grocksdb.Options, error) { |
| 65 | + opts, err := grocksdb.LoadLatestOptions(dir, grocksdb.NewDefaultEnv(), true, grocksdb.NewLRUCache(BlockCacheSize)) |
| 66 | + if err != nil { |
| 67 | + // not found is not an error |
| 68 | + if strings.HasPrefix(err.Error(), "NotFound: ") { |
| 69 | + return nil, nil |
| 70 | + } |
| 71 | + return nil, err |
| 72 | + } |
| 73 | + |
| 74 | + cfNames := opts.ColumnFamilyNames() |
| 75 | + cfOpts := opts.ColumnFamilyOpts() |
| 76 | + |
| 77 | + for i := 0; i < len(cfNames); i++ { |
| 78 | + if cfNames[i] == "default" { |
| 79 | + return &cfOpts[i], nil |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + return opts.Options(), nil |
| 84 | +} |
| 85 | + |
| 86 | +// NewRocksdbOptions build options for `application.db`, |
| 87 | +// it overrides existing options if provided, otherwise create new one assuming it's a new database. |
| 88 | +func NewRocksdbOptions(opts *grocksdb.Options, sstFileWriter bool) *grocksdb.Options { |
| 89 | + if opts == nil { |
| 90 | + opts = grocksdb.NewDefaultOptions() |
| 91 | + // only enable dynamic-level-bytes on new db, don't override for existing db |
| 92 | + opts.SetLevelCompactionDynamicLevelBytes(true) |
| 93 | + } |
| 94 | + opts.SetCreateIfMissing(true) |
| 95 | + opts.IncreaseParallelism(runtime.NumCPU()) |
| 96 | + opts.OptimizeLevelStyleCompaction(512 * 1024 * 1024) |
| 97 | + opts.SetTargetFileSizeMultiplier(2) |
| 98 | + |
| 99 | + // block based table options |
| 100 | + bbto := grocksdb.NewDefaultBlockBasedTableOptions() |
| 101 | + |
| 102 | + bbto.SetBlockCache(grocksdb.NewLRUCache(BlockCacheSize)) |
| 103 | + |
| 104 | + // http://rocksdb.org/blog/2021/12/29/ribbon-filter.html |
| 105 | + bbto.SetFilterPolicy(grocksdb.NewRibbonHybridFilterPolicy(9.9, 1)) |
| 106 | + |
| 107 | + // partition index |
| 108 | + // http://rocksdb.org/blog/2017/05/12/partitioned-index-filter.html |
| 109 | + bbto.SetIndexType(grocksdb.KTwoLevelIndexSearchIndexType) |
| 110 | + bbto.SetPartitionFilters(true) |
| 111 | + bbto.SetOptimizeFiltersForMemory(true) |
| 112 | + |
| 113 | + // reduce memory usage |
| 114 | + bbto.SetCacheIndexAndFilterBlocks(true) |
| 115 | + bbto.SetPinTopLevelIndexAndFilter(true) |
| 116 | + bbto.SetPinL0FilterAndIndexBlocksInCache(true) |
| 117 | + |
| 118 | + // hash index is better for iavl tree which mostly do point lookup. |
| 119 | + bbto.SetDataBlockIndexType(grocksdb.KDataBlockIndexTypeBinarySearchAndHash) |
| 120 | + |
| 121 | + opts.SetBlockBasedTableFactory(bbto) |
| 122 | + |
| 123 | + // in iavl tree, we almost always query existing keys |
| 124 | + opts.SetOptimizeFiltersForHits(true) |
| 125 | + |
| 126 | + // heavier compression option at bottommost level, |
| 127 | + // 110k dict bytes is default in zstd library, |
| 128 | + // train bytes is recommended to be set at 100x dict bytes. |
| 129 | + opts.SetBottommostCompression(grocksdb.ZSTDCompression) |
| 130 | + compressOpts := grocksdb.NewDefaultCompressionOptions() |
| 131 | + compressOpts.Level = 12 |
| 132 | + if !sstFileWriter { |
| 133 | + compressOpts.MaxDictBytes = 110 * 1024 |
| 134 | + opts.SetBottommostCompressionOptionsZstdMaxTrainBytes(compressOpts.MaxDictBytes*100, true) |
| 135 | + } |
| 136 | + opts.SetBottommostCompressionOptions(compressOpts, true) |
| 137 | + return opts |
| 138 | +} |
0 commit comments