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  }