github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/kvdb/bunt.go (about) 1 // Package kvdb provides a local key/value database server for AIS. 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package kvdb 6 7 import ( 8 "strings" 9 10 "github.com/NVIDIA/aistore/cmn/cos" 11 jsoniter "github.com/json-iterator/go" 12 "github.com/tidwall/buntdb" 13 ) 14 15 // BuntDB: 16 // At this moment, BuntDB runs with the following settings: 17 // - calls filesystem sync every second (if we want to do it less frequently 18 // may add `Flush` to `Driver` interface and call in Get/Set/Delete/List 19 // when a certain number of seconds pass. There is not way to do it in easier 20 // way with built-in stuff: either Never or every second) 21 // - after database size exceeds `autoShrinkSize`(1MB), BuntDB starts compacting 22 // the database periodically(when it grows over a given limit) 23 // - compacting is executed when the database size is greater than the size 24 // after previous compacting by `AutoShrinkPercentage`(50%) 25 26 const autoShrinkSize = cos.MiB 27 28 type BuntDriver struct { 29 driver *buntdb.DB 30 } 31 32 // interface guard 33 var _ Driver = (*BuntDriver)(nil) 34 35 func NewBuntDB(path string) (*BuntDriver, error) { 36 driver, err := buntdb.Open(path) 37 if err != nil { 38 return nil, err 39 } 40 driver.SetConfig(buntdb.Config{ 41 SyncPolicy: buntdb.EverySecond, // periodical FS sync 42 AutoShrinkMinSize: autoShrinkSize, // start autoShrink only of file exceeds the size 43 AutoShrinkPercentage: 50, // run compacting when DB grows by half 44 }) 45 return &BuntDriver{driver: driver}, nil 46 } 47 48 func buntToCommonErr(err error, collection, key string) error { 49 if err == buntdb.ErrNotFound { 50 what := collection 51 if key != "" { 52 what += " \"" + key + "\"" 53 } 54 return cos.NewErrNotFound(nil, what) 55 } 56 return err 57 } 58 59 // Create "unique" key from collection and key, so there was no trouble when 60 // there is an overlap. E.g, if key and collection uses the same separator 61 // for subkeys, two pairs ("abc", "def/ghi") and ("abc/def", "ghi") generate 62 // the same full path. The function should make them different. 63 func makePath(collection, key string) string { 64 if strings.HasSuffix(collection, "##") { 65 return collection + key 66 } 67 return collection + CollectionSepa + key 68 } 69 70 func (bd *BuntDriver) Close() error { 71 return bd.driver.Close() 72 } 73 74 func (bd *BuntDriver) Set(collection, key string, object any) error { 75 b := cos.MustMarshal(object) 76 err := bd.SetString(collection, key, string(b)) 77 return buntToCommonErr(err, collection, key) 78 } 79 80 func (bd *BuntDriver) Get(collection, key string, object any) error { 81 s, err := bd.GetString(collection, key) 82 if err != nil { 83 return buntToCommonErr(err, collection, key) 84 } 85 return jsoniter.Unmarshal([]byte(s), object) 86 } 87 88 func (bd *BuntDriver) SetString(collection, key, data string) error { 89 name := makePath(collection, key) 90 err := bd.driver.Update(func(tx *buntdb.Tx) error { 91 _, _, err := tx.Set(name, data, nil) 92 return err 93 }) 94 return buntToCommonErr(err, collection, key) 95 } 96 97 func (bd *BuntDriver) GetString(collection, key string) (string, error) { 98 var value string 99 name := makePath(collection, key) 100 err := bd.driver.View(func(tx *buntdb.Tx) error { 101 var err error 102 value, err = tx.Get(name) 103 return err 104 }) 105 return value, buntToCommonErr(err, collection, key) 106 } 107 108 func (bd *BuntDriver) Delete(collection, key string) error { 109 name := makePath(collection, key) 110 err := bd.driver.Update(func(tx *buntdb.Tx) error { 111 _, err := tx.Delete(name) 112 return err 113 }) 114 return buntToCommonErr(err, collection, key) 115 } 116 117 func (bd *BuntDriver) List(collection, pattern string) ([]string, error) { 118 var ( 119 keys = make([]string, 0) 120 filter string 121 ) 122 if !strings.Contains(pattern, "*") && !strings.Contains(pattern, "?") { 123 pattern += "*" 124 } 125 filter = makePath(collection, pattern) 126 err := bd.driver.View(func(tx *buntdb.Tx) error { 127 tx.AscendKeys(filter, func(path, _ string) bool { 128 _, key := ParsePath(path) 129 if key != "" { 130 keys = append(keys, key) 131 } 132 return true 133 }) 134 return nil 135 }) 136 return keys, buntToCommonErr(err, collection, "") 137 } 138 139 func (bd *BuntDriver) DeleteCollection(collection string) error { 140 keys, err := bd.List(collection, "") 141 if err != nil || len(keys) == 0 { 142 return err 143 } 144 return bd.driver.Update(func(tx *buntdb.Tx) error { 145 for _, k := range keys { 146 _, err := tx.Delete(k) 147 if err != nil && err != buntdb.ErrNotFound { 148 return err 149 } 150 } 151 return nil 152 }) 153 } 154 155 func (bd *BuntDriver) GetAll(collection, pattern string) (map[string]string, error) { 156 var ( 157 values = make(map[string]string) 158 filter string 159 ) 160 if !strings.Contains(pattern, "*") && !strings.Contains(pattern, "?") { 161 pattern += "*" 162 } 163 filter = makePath(collection, pattern) 164 err := bd.driver.View(func(tx *buntdb.Tx) error { 165 tx.AscendKeys(filter, func(path, val string) bool { 166 _, key := ParsePath(path) 167 if key != "" { 168 values[key] = val 169 } 170 return true 171 }) 172 return nil 173 }) 174 return values, buntToCommonErr(err, collection, "") 175 }