github.com/bhojpur/cache@v0.0.4/pkg/memory/compact.go (about)

     1  package memory
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  // Compact will create a copy of the source DB and in the destination DB. This may
    24  // reclaim space that the source database no longer has use for. txMaxSize can be
    25  // used to limit the transactions size of this process and may trigger intermittent
    26  // commits. A value of zero will ignore transaction sizes.
    27  func Compact(dst, src *DB, txMaxSize int64) error {
    28  	// commit regularly, or we'll run out of memory for large datasets if using
    29  	// one transaction.
    30  	var size int64
    31  	tx, err := dst.Begin(true)
    32  	if err != nil {
    33  		return err
    34  	}
    35  	defer tx.Rollback()
    36  
    37  	if err := walk(src, func(keys [][]byte, k, v []byte, seq uint64) error {
    38  		// On each key/value, check if we have exceeded tx size.
    39  		sz := int64(len(k) + len(v))
    40  		if size+sz > txMaxSize && txMaxSize != 0 {
    41  			// Commit previous transaction.
    42  			if err := tx.Commit(); err != nil {
    43  				return err
    44  			}
    45  
    46  			// Start new transaction.
    47  			tx, err = dst.Begin(true)
    48  			if err != nil {
    49  				return err
    50  			}
    51  			size = 0
    52  		}
    53  		size += sz
    54  
    55  		// Create bucket on the root transaction if this is the first level.
    56  		nk := len(keys)
    57  		if nk == 0 {
    58  			bkt, err := tx.CreateBucket(k)
    59  			if err != nil {
    60  				return err
    61  			}
    62  			if err := bkt.SetSequence(seq); err != nil {
    63  				return err
    64  			}
    65  			return nil
    66  		}
    67  
    68  		// Create buckets on subsequent levels, if necessary.
    69  		b := tx.Bucket(keys[0])
    70  		if nk > 1 {
    71  			for _, k := range keys[1:] {
    72  				b = b.Bucket(k)
    73  			}
    74  		}
    75  
    76  		// Fill the entire page for best compaction.
    77  		b.FillPercent = 1.0
    78  
    79  		// If there is no value then this is a bucket call.
    80  		if v == nil {
    81  			bkt, err := b.CreateBucket(k)
    82  			if err != nil {
    83  				return err
    84  			}
    85  			if err := bkt.SetSequence(seq); err != nil {
    86  				return err
    87  			}
    88  			return nil
    89  		}
    90  
    91  		// Otherwise treat it as a key/value pair.
    92  		return b.Put(k, v)
    93  	}); err != nil {
    94  		return err
    95  	}
    96  
    97  	return tx.Commit()
    98  }
    99  
   100  // walkFunc is the type of the function called for keys (buckets and "normal"
   101  // values) discovered by Walk. keys is the list of keys to descend to the bucket
   102  // owning the discovered key/value pair k/v.
   103  type walkFunc func(keys [][]byte, k, v []byte, seq uint64) error
   104  
   105  // walk walks recursively the Bhojpur Cache in-memory database db, calling walkFn
   106  // for each key it finds.
   107  func walk(db *DB, walkFn walkFunc) error {
   108  	return db.View(func(tx *Tx) error {
   109  		return tx.ForEach(func(name []byte, b *Bucket) error {
   110  			return walkBucket(b, nil, name, nil, b.Sequence(), walkFn)
   111  		})
   112  	})
   113  }
   114  
   115  func walkBucket(b *Bucket, keypath [][]byte, k, v []byte, seq uint64, fn walkFunc) error {
   116  	// Execute callback.
   117  	if err := fn(keypath, k, v, seq); err != nil {
   118  		return err
   119  	}
   120  
   121  	// If this is not a Bucket then stop.
   122  	if v != nil {
   123  		return nil
   124  	}
   125  
   126  	// Iterate over each child key/value.
   127  	keypath = append(keypath, k)
   128  	return b.ForEach(func(k, v []byte) error {
   129  		if v == nil {
   130  			bkt := b.Bucket(k)
   131  			return walkBucket(bkt, keypath, k, nil, bkt.Sequence(), fn)
   132  		}
   133  		return walkBucket(b, keypath, k, v, b.Sequence(), fn)
   134  	})
   135  }