| 1 | package pebbleds |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "path/filepath" |
| 6 | "time" |
| 7 | |
| 8 | "github.com/cockroachdb/pebble/v2" |
| 9 | pebbleds "github.com/ipfs/go-ds-pebble" |
| 10 | "github.com/ipfs/kubo/misc/fsutil" |
| 11 | "github.com/ipfs/kubo/plugin" |
| 12 | "github.com/ipfs/kubo/repo" |
| 13 | "github.com/ipfs/kubo/repo/fsrepo" |
| 14 | ) |
| 15 | |
| 16 | // Plugins is exported list of plugins that will be loaded. |
| 17 | var Plugins = []plugin.Plugin{ |
| 18 | &pebbledsPlugin{}, |
| 19 | } |
| 20 | |
| 21 | type pebbledsPlugin struct{} |
| 22 | |
| 23 | var _ plugin.PluginDatastore = (*pebbledsPlugin)(nil) |
| 24 | |
| 25 | func (*pebbledsPlugin) Name() string { |
| 26 | return "ds-pebble" |
| 27 | } |
| 28 | |
| 29 | func (*pebbledsPlugin) Version() string { |
| 30 | return "0.1.0" |
| 31 | } |
| 32 | |
| 33 | func (*pebbledsPlugin) Init(_ *plugin.Environment) error { |
| 34 | return nil |
| 35 | } |
| 36 | |
| 37 | func (*pebbledsPlugin) DatastoreTypeName() string { |
| 38 | return "pebbleds" |
| 39 | } |
| 40 | |
| 41 | type datastoreConfig struct { |
| 42 | path string |
| 43 | cacheSize int64 |
| 44 | |
| 45 | // Documentation of these values: https://pkg.go.dev/github.com/cockroachdb/pebble@v1.1.2#Options |
| 46 | pebbleOpts *pebble.Options |
| 47 | } |
| 48 | |
| 49 | // PebbleDatastoreConfig returns a configuration stub for a pebble datastore |
| 50 | // from the given parameters. |
| 51 | func (*pebbledsPlugin) DatastoreConfigParser() fsrepo.ConfigFromMap { |
| 52 | return func(params map[string]any) (fsrepo.DatastoreConfig, error) { |
| 53 | var c datastoreConfig |
| 54 | var ok bool |
| 55 | |
| 56 | c.path, ok = params["path"].(string) |
| 57 | if !ok { |
| 58 | return nil, fmt.Errorf("'path' field is missing or not string") |
| 59 | } |
| 60 | |
| 61 | cacheSize, err := getConfigInt("cacheSize", params) |
| 62 | if err != nil { |
| 63 | return nil, err |
| 64 | } |
| 65 | c.cacheSize = int64(cacheSize) |
| 66 | |
| 67 | bytesPerSync, err := getConfigInt("bytesPerSync", params) |
| 68 | if err != nil { |
| 69 | return nil, err |
| 70 | } |
| 71 | disableWAL, err := getConfigBool("disableWAL", params) |
| 72 | if err != nil { |
| 73 | return nil, err |
| 74 | } |
| 75 | fmv, err := getConfigInt("formatMajorVersion", params) |
| 76 | if err != nil { |
| 77 | return nil, err |
| 78 | } |
| 79 | formatMajorVersion := pebble.FormatMajorVersion(fmv) |
| 80 | l0CompactionThreshold, err := getConfigInt("l0CompactionThreshold", params) |
| 81 | if err != nil { |
| 82 | return nil, err |
| 83 | } |
| 84 | l0StopWritesThreshold, err := getConfigInt("l0StopWritesThreshold", params) |
| 85 | if err != nil { |
| 86 | return nil, err |
| 87 | } |
| 88 | lBaseMaxBytes, err := getConfigInt("lBaseMaxBytes", params) |
| 89 | if err != nil { |
| 90 | return nil, err |
| 91 | } |
| 92 | maxConcurrentCompactions, err := getConfigInt("maxConcurrentCompactions", params) |
| 93 | if err != nil { |
| 94 | return nil, err |
| 95 | } |
| 96 | memTableSize, err := getConfigInt("memTableSize", params) |
| 97 | if err != nil { |
| 98 | return nil, err |
| 99 | } |
| 100 | memTableStopWritesThreshold, err := getConfigInt("memTableStopWritesThreshold", params) |
| 101 | if err != nil { |
| 102 | return nil, err |
| 103 | } |
| 104 | walBytesPerSync, err := getConfigInt("walBytesPerSync", params) |
| 105 | if err != nil { |
| 106 | return nil, err |
| 107 | } |
| 108 | walMinSyncSec, err := getConfigInt("walMinSyncIntervalSeconds", params) |
| 109 | if err != nil { |
| 110 | return nil, err |
| 111 | } |
| 112 | |
| 113 | if formatMajorVersion == 0 { |
| 114 | // Pebble DB format not configured. Automatically ratchet the |
| 115 | // database to the latest format. This may prevent downgrade. |
| 116 | formatMajorVersion = pebble.FormatNewest |
| 117 | } else if formatMajorVersion < pebble.FormatNewest { |
| 118 | // Pebble DB format is configured, but is not the latest. |
| 119 | fmt.Println("⚠️ A newer pebble db format is available.") |
| 120 | fmt.Println(" To upgrade, set the following in the pebble datastore config:") |
| 121 | fmt.Println(" \"formatMajorVersion\":", int(pebble.FormatNewest)) |
| 122 | } |
| 123 | |
| 124 | if bytesPerSync != 0 || disableWAL || formatMajorVersion != 0 || l0CompactionThreshold != 0 || l0StopWritesThreshold != 0 || lBaseMaxBytes != 0 || maxConcurrentCompactions != 0 || memTableSize != 0 || memTableStopWritesThreshold != 0 || walBytesPerSync != 0 || walMinSyncSec != 0 { |
| 125 | c.pebbleOpts = &pebble.Options{ |
| 126 | BytesPerSync: bytesPerSync, |
| 127 | DisableWAL: disableWAL, |
| 128 | FormatMajorVersion: formatMajorVersion, |
| 129 | L0CompactionThreshold: l0CompactionThreshold, |
| 130 | L0StopWritesThreshold: l0StopWritesThreshold, |
| 131 | LBaseMaxBytes: int64(lBaseMaxBytes), |
| 132 | MemTableSize: uint64(memTableSize), |
| 133 | MemTableStopWritesThreshold: memTableStopWritesThreshold, |
| 134 | WALBytesPerSync: walBytesPerSync, |
| 135 | } |
| 136 | if maxConcurrentCompactions != 0 { |
| 137 | c.pebbleOpts.CompactionConcurrencyRange = func() (int, int) { return 1, maxConcurrentCompactions } |
| 138 | } |
| 139 | if walMinSyncSec != 0 { |
| 140 | c.pebbleOpts.WALMinSyncInterval = func() time.Duration { return time.Duration(walMinSyncSec) * time.Second } |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | return &c, nil |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | func getConfigBool(name string, params map[string]any) (bool, error) { |
| 149 | val, ok := params[name] |
| 150 | if ok { |
| 151 | bval, ok := val.(bool) |
| 152 | if !ok { |
| 153 | return false, fmt.Errorf("%q field was not a bool", name) |
| 154 | } |
| 155 | return bval, nil |
| 156 | } |
| 157 | return false, nil |
| 158 | } |
| 159 | |
| 160 | func getConfigInt(name string, params map[string]any) (int, error) { |
| 161 | val, ok := params[name] |
| 162 | if ok { |
| 163 | // TODO: see why val may be an int or a float64. |
| 164 | ival, ok := val.(int) |
| 165 | if !ok { |
| 166 | fval, ok := val.(float64) |
| 167 | if !ok { |
| 168 | return 0, fmt.Errorf("%q field was not an integer or a float64", name) |
| 169 | } |
| 170 | return int(fval), nil |
| 171 | } |
| 172 | return ival, nil |
| 173 | } |
| 174 | return 0, nil |
| 175 | } |
| 176 | |
| 177 | func (c *datastoreConfig) DiskSpec() fsrepo.DiskSpec { |
| 178 | return map[string]any{ |
| 179 | "type": "pebbleds", |
| 180 | "path": c.path, |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | func (c *datastoreConfig) Create(path string) (repo.Datastore, error) { |
| 185 | p := c.path |
| 186 | if !filepath.IsAbs(p) { |
| 187 | p = filepath.Join(path, p) |
| 188 | } |
| 189 | |
| 190 | if err := fsutil.DirWritable(p); err != nil { |
| 191 | return nil, err |
| 192 | } |
| 193 | |
| 194 | return pebbleds.NewDatastore(p, pebbleds.WithCacheSize(c.cacheSize), pebbleds.WithPebbleOpts(c.pebbleOpts)) |
| 195 | } |