github.com/TeaOSLab/EdgeNode@v1.3.8/internal/utils/kvstore/store.go (about) 1 // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 3 package kvstore 4 5 import ( 6 "errors" 7 "fmt" 8 "github.com/TeaOSLab/EdgeNode/internal/events" 9 "github.com/TeaOSLab/EdgeNode/internal/remotelogs" 10 fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs" 11 memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem" 12 "github.com/cockroachdb/pebble" 13 "github.com/iwind/TeaGo/Tea" 14 "io" 15 "os" 16 "path/filepath" 17 "strings" 18 "sync" 19 ) 20 21 const StoreSuffix = ".store" 22 23 type Store struct { 24 name string 25 26 path string 27 rawDB *pebble.DB 28 locker *fsutils.Locker 29 30 isClosed bool 31 32 dbs []*DB 33 34 mu sync.Mutex 35 } 36 37 // NewStore create store with name 38 func NewStore(storeName string) (*Store, error) { 39 if !IsValidName(storeName) { 40 return nil, errors.New("invalid store name '" + storeName + "'") 41 } 42 43 var path = Tea.Root + "/data/stores/" + storeName + StoreSuffix 44 _, err := os.Stat(path) 45 if err != nil && os.IsNotExist(err) { 46 _ = os.MkdirAll(path, 0777) 47 } 48 49 return &Store{ 50 name: storeName, 51 path: path, 52 locker: fsutils.NewLocker(path + "/.fs"), 53 }, nil 54 } 55 56 // NewStoreWithPath create store with path 57 func NewStoreWithPath(path string) (*Store, error) { 58 if !strings.HasSuffix(path, ".store") { 59 return nil, errors.New("store path must contains a '.store' suffix") 60 } 61 62 _, err := os.Stat(path) 63 if err != nil && os.IsNotExist(err) { 64 _ = os.MkdirAll(path, 0777) 65 } 66 67 var storeName = filepath.Base(path) 68 storeName = strings.TrimSuffix(storeName, ".store") 69 70 if !IsValidName(storeName) { 71 return nil, errors.New("invalid store name '" + storeName + "'") 72 } 73 74 return &Store{ 75 name: storeName, 76 path: path, 77 locker: fsutils.NewLocker(path + "/.fs"), 78 }, nil 79 } 80 81 func OpenStore(storeName string) (*Store, error) { 82 store, err := NewStore(storeName) 83 if err != nil { 84 return nil, err 85 } 86 err = store.Open() 87 if err != nil { 88 return nil, err 89 } 90 91 return store, nil 92 } 93 94 func OpenStoreDir(dir string, storeName string) (*Store, error) { 95 if !IsValidName(storeName) { 96 return nil, errors.New("invalid store name '" + storeName + "'") 97 } 98 99 var path = strings.TrimSuffix(dir, "/") + "/" + storeName + StoreSuffix 100 _, err := os.Stat(path) 101 if err != nil && os.IsNotExist(err) { 102 _ = os.MkdirAll(path, 0777) 103 } 104 105 var store = &Store{ 106 name: storeName, 107 path: path, 108 locker: fsutils.NewLocker(path + "/.fs"), 109 } 110 111 err = store.Open() 112 if err != nil { 113 return nil, err 114 } 115 return store, nil 116 } 117 118 var storeOnce = &sync.Once{} 119 var defaultSore *Store 120 121 func DefaultStore() (*Store, error) { 122 if defaultSore != nil { 123 return defaultSore, nil 124 } 125 126 var resultErr error 127 storeOnce.Do(func() { 128 store, err := NewStore("default") 129 if err != nil { 130 resultErr = fmt.Errorf("create default store failed: %w", err) 131 remotelogs.Error("KV", resultErr.Error()) 132 return 133 } 134 err = store.Open() 135 if err != nil { 136 resultErr = fmt.Errorf("open default store failed: %w", err) 137 remotelogs.Error("KV", resultErr.Error()) 138 return 139 } 140 defaultSore = store 141 }) 142 143 return defaultSore, resultErr 144 } 145 146 func (this *Store) Path() string { 147 return this.path 148 } 149 150 func (this *Store) Open() error { 151 err := this.locker.Lock() 152 if err != nil { 153 return err 154 } 155 156 var opt = &pebble.Options{ 157 Logger: NewLogger(), 158 } 159 160 if fsutils.DiskIsFast() { 161 opt.BytesPerSync = 1 << 20 162 } 163 164 var memoryMB = memutils.SystemMemoryGB() * 2 165 if memoryMB > 256 { 166 memoryMB = 256 167 } 168 if memoryMB > 4 { 169 opt.MemTableSize = uint64(memoryMB) << 20 170 } 171 172 rawDB, err := pebble.Open(this.path, opt) 173 if err != nil { 174 return err 175 } 176 this.rawDB = rawDB 177 178 // events 179 events.OnClose(func() { 180 _ = this.Close() 181 }) 182 183 return nil 184 } 185 186 func (this *Store) Set(keyBytes []byte, valueBytes []byte) error { 187 return this.rawDB.Set(keyBytes, valueBytes, DefaultWriteOptions) 188 } 189 190 func (this *Store) Get(keyBytes []byte) (valueBytes []byte, closer io.Closer, err error) { 191 return this.rawDB.Get(keyBytes) 192 } 193 194 func (this *Store) Delete(keyBytes []byte) error { 195 return this.rawDB.Delete(keyBytes, DefaultWriteOptions) 196 } 197 198 func (this *Store) NewDB(dbName string) (*DB, error) { 199 this.mu.Lock() 200 defer this.mu.Unlock() 201 202 // check existence 203 for _, db := range this.dbs { 204 if db.name == dbName { 205 return db, nil 206 } 207 } 208 209 // create new 210 db, err := NewDB(this, dbName) 211 if err != nil { 212 return nil, err 213 } 214 215 this.dbs = append(this.dbs, db) 216 return db, nil 217 } 218 219 func (this *Store) RawDB() *pebble.DB { 220 return this.rawDB 221 } 222 223 func (this *Store) Flush() error { 224 return this.rawDB.Flush() 225 } 226 227 func (this *Store) Close() error { 228 if this.isClosed { 229 return nil 230 } 231 232 _ = this.locker.Release() 233 234 this.mu.Lock() 235 var lastErr error 236 for _, db := range this.dbs { 237 err := db.Close() 238 if err != nil { 239 lastErr = err 240 } 241 } 242 243 this.mu.Unlock() 244 245 if this.rawDB != nil { 246 this.isClosed = true 247 err := this.rawDB.Close() 248 if err != nil { 249 return err 250 } 251 } 252 253 return lastErr 254 } 255 256 func (this *Store) IsClosed() bool { 257 return this.isClosed 258 }