github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/ethdb/database.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package ethdb 13 14 import ( 15 "strconv" 16 "strings" 17 "sync" 18 "time" 19 20 "github.com/Sberex/go-sberex/log" 21 "github.com/Sberex/go-sberex/metrics" 22 "github.com/syndtr/goleveldb/leveldb" 23 "github.com/syndtr/goleveldb/leveldb/errors" 24 "github.com/syndtr/goleveldb/leveldb/filter" 25 "github.com/syndtr/goleveldb/leveldb/iterator" 26 "github.com/syndtr/goleveldb/leveldb/opt" 27 ) 28 29 var OpenFileLimit = 64 30 31 type LDBDatabase struct { 32 fn string // filename for reporting 33 db *leveldb.DB // LevelDB instance 34 35 getTimer metrics.Timer // Timer for measuring the database get request counts and latencies 36 putTimer metrics.Timer // Timer for measuring the database put request counts and latencies 37 delTimer metrics.Timer // Timer for measuring the database delete request counts and latencies 38 missMeter metrics.Meter // Meter for measuring the missed database get requests 39 readMeter metrics.Meter // Meter for measuring the database get request data usage 40 writeMeter metrics.Meter // Meter for measuring the database put request data usage 41 compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction 42 compReadMeter metrics.Meter // Meter for measuring the data read during compaction 43 compWriteMeter metrics.Meter // Meter for measuring the data written during compaction 44 45 quitLock sync.Mutex // Mutex protecting the quit channel access 46 quitChan chan chan error // Quit channel to stop the metrics collection before closing the database 47 48 log log.Logger // Contextual logger tracking the database path 49 } 50 51 // NewLDBDatabase returns a LevelDB wrapped object. 52 func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) { 53 logger := log.New("database", file) 54 55 // Ensure we have some minimal caching and file guarantees 56 if cache < 16 { 57 cache = 16 58 } 59 if handles < 16 { 60 handles = 16 61 } 62 logger.Info("Allocated cache and file handles", "cache", cache, "handles", handles) 63 64 // Open the db and recover any potential corruptions 65 db, err := leveldb.OpenFile(file, &opt.Options{ 66 OpenFilesCacheCapacity: handles, 67 BlockCacheCapacity: cache / 2 * opt.MiB, 68 WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally 69 Filter: filter.NewBloomFilter(10), 70 }) 71 if _, corrupted := err.(*errors.ErrCorrupted); corrupted { 72 db, err = leveldb.RecoverFile(file, nil) 73 } 74 // (Re)check for errors and abort if opening of the db failed 75 if err != nil { 76 return nil, err 77 } 78 return &LDBDatabase{ 79 fn: file, 80 db: db, 81 log: logger, 82 }, nil 83 } 84 85 // Path returns the path to the database directory. 86 func (db *LDBDatabase) Path() string { 87 return db.fn 88 } 89 90 // Put puts the given key / value to the queue 91 func (db *LDBDatabase) Put(key []byte, value []byte) error { 92 // Measure the database put latency, if requested 93 if db.putTimer != nil { 94 defer db.putTimer.UpdateSince(time.Now()) 95 } 96 // Generate the data to write to disk, update the meter and write 97 //value = rle.Compress(value) 98 99 if db.writeMeter != nil { 100 db.writeMeter.Mark(int64(len(value))) 101 } 102 return db.db.Put(key, value, nil) 103 } 104 105 func (db *LDBDatabase) Has(key []byte) (bool, error) { 106 return db.db.Has(key, nil) 107 } 108 109 // Get returns the given key if it's present. 110 func (db *LDBDatabase) Get(key []byte) ([]byte, error) { 111 // Measure the database get latency, if requested 112 if db.getTimer != nil { 113 defer db.getTimer.UpdateSince(time.Now()) 114 } 115 // Retrieve the key and increment the miss counter if not found 116 dat, err := db.db.Get(key, nil) 117 if err != nil { 118 if db.missMeter != nil { 119 db.missMeter.Mark(1) 120 } 121 return nil, err 122 } 123 // Otherwise update the actually retrieved amount of data 124 if db.readMeter != nil { 125 db.readMeter.Mark(int64(len(dat))) 126 } 127 return dat, nil 128 //return rle.Decompress(dat) 129 } 130 131 // Delete deletes the key from the queue and database 132 func (db *LDBDatabase) Delete(key []byte) error { 133 // Measure the database delete latency, if requested 134 if db.delTimer != nil { 135 defer db.delTimer.UpdateSince(time.Now()) 136 } 137 // Execute the actual operation 138 return db.db.Delete(key, nil) 139 } 140 141 func (db *LDBDatabase) NewIterator() iterator.Iterator { 142 return db.db.NewIterator(nil, nil) 143 } 144 145 func (db *LDBDatabase) Close() { 146 // Stop the metrics collection to avoid internal database races 147 db.quitLock.Lock() 148 defer db.quitLock.Unlock() 149 150 if db.quitChan != nil { 151 errc := make(chan error) 152 db.quitChan <- errc 153 if err := <-errc; err != nil { 154 db.log.Error("Metrics collection failed", "err", err) 155 } 156 } 157 err := db.db.Close() 158 if err == nil { 159 db.log.Info("Database closed") 160 } else { 161 db.log.Error("Failed to close database", "err", err) 162 } 163 } 164 165 func (db *LDBDatabase) LDB() *leveldb.DB { 166 return db.db 167 } 168 169 // Meter configures the database metrics collectors and 170 func (db *LDBDatabase) Meter(prefix string) { 171 // Short circuit metering if the metrics system is disabled 172 if !metrics.Enabled { 173 return 174 } 175 // Initialize all the metrics collector at the requested prefix 176 db.getTimer = metrics.NewRegisteredTimer(prefix+"user/gets", nil) 177 db.putTimer = metrics.NewRegisteredTimer(prefix+"user/puts", nil) 178 db.delTimer = metrics.NewRegisteredTimer(prefix+"user/dels", nil) 179 db.missMeter = metrics.NewRegisteredMeter(prefix+"user/misses", nil) 180 db.readMeter = metrics.NewRegisteredMeter(prefix+"user/reads", nil) 181 db.writeMeter = metrics.NewRegisteredMeter(prefix+"user/writes", nil) 182 db.compTimeMeter = metrics.NewRegisteredMeter(prefix+"compact/time", nil) 183 db.compReadMeter = metrics.NewRegisteredMeter(prefix+"compact/input", nil) 184 db.compWriteMeter = metrics.NewRegisteredMeter(prefix+"compact/output", nil) 185 186 // Create a quit channel for the periodic collector and run it 187 db.quitLock.Lock() 188 db.quitChan = make(chan chan error) 189 db.quitLock.Unlock() 190 191 go db.meter(3 * time.Second) 192 } 193 194 // meter periodically retrieves internal leveldb counters and reports them to 195 // the metrics subsystem. 196 // 197 // This is how a stats table look like (currently): 198 // Compactions 199 // Level | Tables | Size(MB) | Time(sec) | Read(MB) | Write(MB) 200 // -------+------------+---------------+---------------+---------------+--------------- 201 // 0 | 0 | 0.00000 | 1.27969 | 0.00000 | 12.31098 202 // 1 | 85 | 109.27913 | 28.09293 | 213.92493 | 214.26294 203 // 2 | 523 | 1000.37159 | 7.26059 | 66.86342 | 66.77884 204 // 3 | 570 | 1113.18458 | 0.00000 | 0.00000 | 0.00000 205 func (db *LDBDatabase) meter(refresh time.Duration) { 206 // Create the counters to store current and previous values 207 counters := make([][]float64, 2) 208 for i := 0; i < 2; i++ { 209 counters[i] = make([]float64, 3) 210 } 211 // Iterate ad infinitum and collect the stats 212 for i := 1; ; i++ { 213 // Retrieve the database stats 214 stats, err := db.db.GetProperty("leveldb.stats") 215 if err != nil { 216 db.log.Error("Failed to read database stats", "err", err) 217 return 218 } 219 // Find the compaction table, skip the header 220 lines := strings.Split(stats, "\n") 221 for len(lines) > 0 && strings.TrimSpace(lines[0]) != "Compactions" { 222 lines = lines[1:] 223 } 224 if len(lines) <= 3 { 225 db.log.Error("Compaction table not found") 226 return 227 } 228 lines = lines[3:] 229 230 // Iterate over all the table rows, and accumulate the entries 231 for j := 0; j < len(counters[i%2]); j++ { 232 counters[i%2][j] = 0 233 } 234 for _, line := range lines { 235 parts := strings.Split(line, "|") 236 if len(parts) != 6 { 237 break 238 } 239 for idx, counter := range parts[3:] { 240 value, err := strconv.ParseFloat(strings.TrimSpace(counter), 64) 241 if err != nil { 242 db.log.Error("Compaction entry parsing failed", "err", err) 243 return 244 } 245 counters[i%2][idx] += value 246 } 247 } 248 // Update all the requested meters 249 if db.compTimeMeter != nil { 250 db.compTimeMeter.Mark(int64((counters[i%2][0] - counters[(i-1)%2][0]) * 1000 * 1000 * 1000)) 251 } 252 if db.compReadMeter != nil { 253 db.compReadMeter.Mark(int64((counters[i%2][1] - counters[(i-1)%2][1]) * 1024 * 1024)) 254 } 255 if db.compWriteMeter != nil { 256 db.compWriteMeter.Mark(int64((counters[i%2][2] - counters[(i-1)%2][2]) * 1024 * 1024)) 257 } 258 // Sleep a bit, then repeat the stats collection 259 select { 260 case errc := <-db.quitChan: 261 // Quit requesting, stop hammering the database 262 errc <- nil 263 return 264 265 case <-time.After(refresh): 266 // Timeout, gather a new set of stats 267 } 268 } 269 } 270 271 func (db *LDBDatabase) NewBatch() Batch { 272 return &ldbBatch{db: db.db, b: new(leveldb.Batch)} 273 } 274 275 type ldbBatch struct { 276 db *leveldb.DB 277 b *leveldb.Batch 278 size int 279 } 280 281 func (b *ldbBatch) Put(key, value []byte) error { 282 b.b.Put(key, value) 283 b.size += len(value) 284 return nil 285 } 286 287 func (b *ldbBatch) Write() error { 288 return b.db.Write(b.b, nil) 289 } 290 291 func (b *ldbBatch) ValueSize() int { 292 return b.size 293 } 294 295 func (b *ldbBatch) Reset() { 296 b.b.Reset() 297 b.size = 0 298 } 299 300 type table struct { 301 db Database 302 prefix string 303 } 304 305 // NewTable returns a Database object that prefixes all keys with a given 306 // string. 307 func NewTable(db Database, prefix string) Database { 308 return &table{ 309 db: db, 310 prefix: prefix, 311 } 312 } 313 314 func (dt *table) Put(key []byte, value []byte) error { 315 return dt.db.Put(append([]byte(dt.prefix), key...), value) 316 } 317 318 func (dt *table) Has(key []byte) (bool, error) { 319 return dt.db.Has(append([]byte(dt.prefix), key...)) 320 } 321 322 func (dt *table) Get(key []byte) ([]byte, error) { 323 return dt.db.Get(append([]byte(dt.prefix), key...)) 324 } 325 326 func (dt *table) Delete(key []byte) error { 327 return dt.db.Delete(append([]byte(dt.prefix), key...)) 328 } 329 330 func (dt *table) Close() { 331 // Do nothing; don't close the underlying DB. 332 } 333 334 type tableBatch struct { 335 batch Batch 336 prefix string 337 } 338 339 // NewTableBatch returns a Batch object which prefixes all keys with a given string. 340 func NewTableBatch(db Database, prefix string) Batch { 341 return &tableBatch{db.NewBatch(), prefix} 342 } 343 344 func (dt *table) NewBatch() Batch { 345 return &tableBatch{dt.db.NewBatch(), dt.prefix} 346 } 347 348 func (tb *tableBatch) Put(key, value []byte) error { 349 return tb.batch.Put(append([]byte(tb.prefix), key...), value) 350 } 351 352 func (tb *tableBatch) Write() error { 353 return tb.batch.Write() 354 } 355 356 func (tb *tableBatch) ValueSize() int { 357 return tb.batch.ValueSize() 358 } 359 360 func (tb *tableBatch) Reset() { 361 tb.batch.Reset() 362 }