github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/lib/others/qdb/db.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 /* 6 Qdb is a fast persistent storage database. 7 8 The records are binary blobs that can have a variable length, up to 4GB. 9 10 The key must be a unique 64-bit value, most likely a hash of the actual key. 11 12 They data is stored on a disk, in a folder specified during the call to NewDB(). 13 There are can be three possible files in that folder 14 * qdb.0, qdb.1 - these files store a compact version of the entire database 15 * qdb.log - this one stores the changes since the most recent qdb.0 or qdb.1 16 17 */ 18 package qdb 19 20 import ( 21 "bufio" 22 "bytes" 23 "fmt" 24 "os" 25 "sync" 26 ) 27 28 type KeyType uint64 29 30 var ( 31 ExtraMemoryConsumed int64 // if we are using the glibc memory manager 32 ExtraMemoryAllocCnt int64 // if we are using the glibc memory manager 33 ) 34 35 const ( 36 KeySize = 8 37 38 NO_BROWSE = 0x00000001 39 NO_CACHE = 0x00000002 40 BR_ABORT = 0x00000004 41 YES_CACHE = 0x00000008 42 YES_BROWSE = 0x00000010 43 44 DefaultDefragPercentVal = 50 45 DefaultForcedDefragPerc = 300 46 DefaultMaxPending = 2500 47 DefaultMaxPendingNoSync = 10000 48 ) 49 50 type DB struct { 51 // folder with the db files 52 Dir string 53 54 LogFile *os.File 55 LastValidLogPos int64 56 DataSeq uint32 57 58 // access mutex: 59 Mutex sync.Mutex 60 61 //index: 62 Idx *QdbIndex 63 64 NoSyncMode bool 65 PendingRecords map[KeyType]bool 66 67 DatFiles map[uint32]*os.File 68 69 O ExtraOpts 70 71 VolatileMode bool // this will only store database on disk when you close it 72 73 counter map[string]uint64 74 counter_mutex sync.Mutex 75 } 76 77 type oneIdx struct { 78 data data_ptr_t 79 80 DataSeq uint32 // data file index 81 datpos uint32 // position of the record in the data file 82 datlen uint32 // length of the record in the data file 83 84 flags uint32 85 } 86 87 type NewDBOpts struct { 88 Dir string 89 Records uint 90 WalkFunction QdbWalkFunction 91 LoadData bool 92 Volatile bool 93 *ExtraOpts 94 } 95 96 type ExtraOpts struct { 97 DefragPercentVal uint32 // Defrag() will not be done if we waste less disk space 98 ForcedDefragPerc uint32 // forced defrag when extra disk usage goes above this 99 MaxPending uint32 100 MaxPendingNoSync uint32 101 } 102 103 type QdbWalkFunction func(key KeyType, val []byte) uint32 104 105 func (i oneIdx) String() string { 106 if i.data == nil { 107 return fmt.Sprintf("Nodata:%d:%d:%d", i.DataSeq, i.datpos, i.datlen) 108 } else { 109 return fmt.Sprintf("YesData:%d:%d:%d", i.DataSeq, i.datpos, i.datlen) 110 } 111 } 112 113 // Creates or opens a new database in the specified folder. 114 func NewDBExt(_db **DB, opts *NewDBOpts) (e error) { 115 db := new(DB) 116 *_db = db 117 db.counter = make(map[string]uint64) 118 dir := opts.Dir 119 if len(dir) > 0 && dir[len(dir)-1] != '\\' && dir[len(dir)-1] != '/' { 120 dir += string(os.PathSeparator) 121 } 122 123 db.VolatileMode = opts.Volatile 124 125 if opts.ExtraOpts == nil { 126 db.O.DefragPercentVal = DefaultDefragPercentVal 127 db.O.ForcedDefragPerc = DefaultForcedDefragPerc 128 db.O.MaxPending = DefaultMaxPending 129 db.O.MaxPendingNoSync = DefaultMaxPendingNoSync 130 } else { 131 db.O = *opts.ExtraOpts 132 } 133 134 os.MkdirAll(dir, 0770) 135 db.Dir = dir 136 db.DatFiles = make(map[uint32]*os.File) 137 db.PendingRecords = make(map[KeyType]bool, db.O.MaxPending) 138 139 db.Idx = NewDBidx(db, opts.Records) 140 if opts.LoadData { 141 db.Idx.load(opts.WalkFunction) 142 } 143 db.DataSeq = db.Idx.MaxDatfileSequence + 1 144 return 145 } 146 147 func NewDB(dir string, load bool) (*DB, error) { 148 var db *DB 149 e := NewDBExt(&db, &NewDBOpts{Dir: dir, LoadData: load}) 150 return db, e 151 } 152 153 // Returns number of records in the DB 154 func (db *DB) Count() (l int) { 155 db.Mutex.Lock() 156 l = db.Idx.size() 157 db.Mutex.Unlock() 158 return 159 } 160 161 // Browses through all the DB records calling the walk function for each record. 162 // If the walk function returns false, it aborts the browsing and returns. 163 func (db *DB) Browse(walk QdbWalkFunction) { 164 db.Mutex.Lock() 165 db.Idx.browse(func(k KeyType, v *oneIdx) bool { 166 if (v.flags & NO_BROWSE) != 0 { 167 return true 168 } 169 db.loadrec(v) 170 res := walk(k, v.Slice()) 171 v.aply_browsing_flags(res) 172 v.freerec() 173 return (res & BR_ABORT) == 0 174 }) 175 //println("br", db.Dir, "done") 176 db.Mutex.Unlock() 177 } 178 179 // works almost like normal browse except that it also returns non-browsable records 180 func (db *DB) BrowseAll(walk QdbWalkFunction) { 181 db.Mutex.Lock() 182 db.Idx.browse(func(k KeyType, v *oneIdx) bool { 183 db.loadrec(v) 184 res := walk(k, v.Slice()) 185 v.aply_browsing_flags(res) 186 v.freerec() 187 return (res & BR_ABORT) == 0 188 }) 189 //println("br", db.Dir, "done") 190 db.Mutex.Unlock() 191 } 192 193 func (db *DB) Get(key KeyType) (value []byte) { 194 db.Mutex.Lock() 195 idx := db.Idx.get(key) 196 if idx != nil { 197 db.loadrec(idx) 198 idx.aply_browsing_flags(YES_CACHE) // we are giving out the pointer, so keep it in cache 199 value = idx.Slice() 200 } 201 //fmt.Printf("get %016x -> %s\n", key, hex.EncodeToString(value)) 202 db.Mutex.Unlock() 203 return 204 } 205 206 // Use this one inside Browse 207 func (db *DB) GetNoMutex(key KeyType) (value []byte) { 208 idx := db.Idx.get(key) 209 if idx != nil { 210 db.loadrec(idx) 211 value = idx.Slice() 212 } 213 //fmt.Printf("get %016x -> %s\n", key, hex.EncodeToString(value)) 214 return 215 } 216 217 // Adds or updates record with a given key. 218 func (db *DB) Put(key KeyType, value []byte) { 219 db.Mutex.Lock() 220 db.Idx.memput(key, newIdx(value, 0)) 221 if db.VolatileMode { 222 db.NoSyncMode = true 223 db.Mutex.Unlock() 224 return 225 } 226 db.PendingRecords[key] = true 227 if db.syncneeded() { 228 go func() { 229 db.sync() 230 db.Mutex.Unlock() 231 }() 232 } else { 233 db.Mutex.Unlock() 234 } 235 } 236 237 // Adds or updates record with a given key. 238 func (db *DB) PutExt(key KeyType, value []byte, flags uint32) { 239 db.Mutex.Lock() 240 //fmt.Printf("put %016x %s\n", key, hex.EncodeToString(value)) 241 db.Idx.memput(key, newIdx(value, flags)) 242 if db.VolatileMode { 243 db.NoSyncMode = true 244 db.Mutex.Unlock() 245 return 246 } 247 db.PendingRecords[key] = true 248 if db.syncneeded() { 249 go func() { 250 db.sync() 251 db.Mutex.Unlock() 252 }() 253 } else { 254 db.Mutex.Unlock() 255 } 256 } 257 258 // Removes record with a given key. 259 func (db *DB) Del(key KeyType) { 260 //println("del", hex.EncodeToString(key[:])) 261 db.Mutex.Lock() 262 db.Idx.memdel(key) 263 if db.VolatileMode { 264 db.NoSyncMode = true 265 db.Mutex.Unlock() 266 return 267 } 268 db.PendingRecords[key] = true 269 if db.syncneeded() { 270 go func() { 271 db.sync() 272 db.Mutex.Unlock() 273 }() 274 } else { 275 db.Mutex.Unlock() 276 } 277 } 278 279 func (db *DB) ApplyFlags(key KeyType, fl uint32) { 280 db.Mutex.Lock() 281 if idx := db.Idx.get(key); idx != nil { 282 idx.aply_browsing_flags(fl) 283 } 284 db.Mutex.Unlock() 285 } 286 287 // Defragments the DB on the disk. 288 // Return true if defrag hes been performed, and false if was not needed. 289 func (db *DB) Defrag(force bool) (doing bool) { 290 if db.VolatileMode { 291 return 292 } 293 db.Mutex.Lock() 294 doing = force || db.Idx.ExtraSpaceUsed > (uint64(db.O.DefragPercentVal)*db.Idx.DiskSpaceNeeded/100) 295 if doing { 296 db.cnt("DefragYes") 297 go func() { 298 db.defrag() 299 db.Mutex.Unlock() 300 }() 301 } else { 302 db.cnt("DefragNo") 303 db.Mutex.Unlock() 304 } 305 return 306 } 307 308 // Disable writing changes to disk. 309 func (db *DB) NoSync() { 310 if db.VolatileMode { 311 return 312 } 313 db.Mutex.Lock() 314 db.NoSyncMode = true 315 db.Mutex.Unlock() 316 } 317 318 // Write all the pending changes to disk now. 319 // Re enable syncing if it has been disabled. 320 func (db *DB) Sync() { 321 if db.VolatileMode { 322 return 323 } 324 db.Mutex.Lock() 325 db.NoSyncMode = false 326 go func() { 327 db.sync() 328 db.Mutex.Unlock() 329 }() 330 } 331 332 // Close the database. 333 // Writes all the pending changes to disk. 334 func (db *DB) Close() { 335 db.Mutex.Lock() 336 if db.VolatileMode { 337 // flush all the data to disk when closing 338 if db.NoSyncMode { 339 db.defrag() 340 } 341 } else { 342 db.sync() 343 } 344 if db.LogFile != nil { 345 db.LogFile.Close() 346 db.LogFile = nil 347 } 348 db.Idx.close() 349 db.Idx = nil 350 for _, f := range db.DatFiles { 351 f.Close() 352 } 353 db.Mutex.Unlock() 354 } 355 356 func (db *DB) Flush() { 357 if db.VolatileMode { 358 return 359 } 360 db.cnt("Flush") 361 if db.LogFile != nil { 362 db.LogFile.Sync() 363 } 364 if db.Idx.file != nil { 365 db.Idx.file.Sync() 366 } 367 } 368 369 func (db *DB) defrag() { 370 db.DataSeq++ 371 if db.LogFile != nil { 372 db.LogFile.Close() 373 db.LogFile = nil 374 } 375 db.checklogfile() 376 bufile := bufio.NewWriterSize(db.LogFile, 0x100000) 377 used := make(map[uint32]bool, 10) 378 db.Idx.browse(func(key KeyType, rec *oneIdx) bool { 379 db.loadrec(rec) 380 rec.datpos = uint32(db.addtolog(bufile, key, rec.Slice())) 381 rec.DataSeq = db.DataSeq 382 used[rec.DataSeq] = true 383 rec.freerec() 384 return true 385 }) 386 387 // first write & flush the data file: 388 bufile.Flush() 389 db.LogFile.Sync() 390 391 // now the index: 392 db.Idx.writedatfile() // this will close the file 393 394 db.cleanupold(used) 395 db.Idx.ExtraSpaceUsed = 0 396 } 397 398 func (db *DB) sync() { 399 if db.VolatileMode { 400 return 401 } 402 if len(db.PendingRecords) > 0 { 403 db.cnt("SyncOK") 404 bidx := new(bytes.Buffer) 405 db.checklogfile() 406 for k, _ := range db.PendingRecords { 407 rec := db.Idx.get(k) 408 if rec != nil { 409 fpos := db.addtolog(nil, k, rec.Slice()) 410 //rec.datlen = uint32(len(rec.data)) 411 rec.datpos = uint32(fpos) 412 rec.DataSeq = db.DataSeq 413 db.Idx.addtolog(bidx, k, rec) 414 if (rec.flags & NO_CACHE) != 0 { 415 rec.FreeData() 416 } 417 } else { 418 db.Idx.deltolog(bidx, k) 419 } 420 } 421 db.Idx.writebuf(bidx.Bytes()) 422 db.PendingRecords = make(map[KeyType]bool, db.O.MaxPending) 423 424 if db.Idx.ExtraSpaceUsed > (uint64(db.O.ForcedDefragPerc) * db.Idx.DiskSpaceNeeded / 100) { 425 db.cnt("DefragNow") 426 db.defrag() 427 } 428 } else { 429 db.cnt("SyncNO") 430 } 431 } 432 433 func (db *DB) syncneeded() bool { 434 if db.VolatileMode { 435 return false 436 } 437 if len(db.PendingRecords) > int(db.O.MaxPendingNoSync) { 438 db.cnt("SyncNeedBig") 439 return true 440 } 441 if !db.NoSyncMode && len(db.PendingRecords) > int(db.O.MaxPending) { 442 db.cnt("SyncNeedSmall") 443 return true 444 } 445 return false 446 } 447 448 func (idx *oneIdx) freerec() { 449 if (idx.flags & NO_CACHE) != 0 { 450 idx.FreeData() 451 } 452 } 453 454 func (v *oneIdx) aply_browsing_flags(res uint32) { 455 if (res & NO_BROWSE) != 0 { 456 v.flags |= NO_BROWSE 457 } else if (res & YES_BROWSE) != 0 { 458 v.flags &= ^uint32(NO_BROWSE) 459 } 460 461 if (res & NO_CACHE) != 0 { 462 v.flags |= NO_CACHE 463 } else if (res & YES_CACHE) != 0 { 464 v.flags &= ^uint32(NO_CACHE) 465 } 466 }