github.com/xgzlucario/GigaCache@v0.0.0-20240508025442-54204e9c8a6b/bucket.go (about)

     1  package cache
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"sync"
     7  )
     8  
     9  // bucket is the data container for GigaCache.
    10  type bucket struct {
    11  	sync.RWMutex
    12  	options *Options
    13  
    14  	// index is the index map for cache, mapped hash(kstr) to the position that data real stored.
    15  	index map[Key]Idx
    16  
    17  	// conflict map store keys that hash conflict with index.
    18  	cmap map[string]Idx
    19  
    20  	// data store all key-value bytes data.
    21  	data []byte
    22  
    23  	// runtime stats.
    24  	interval int
    25  	unused   uint64
    26  	migrates uint64
    27  	evict    uint64
    28  	probe    uint64
    29  }
    30  
    31  func newBucket(options Options) *bucket {
    32  	return &bucket{
    33  		options: &options,
    34  		index:   make(map[Key]Idx, options.IndexSize),
    35  		cmap:    map[string]Idx{},
    36  		data:    bpool.Get(options.BufferSize)[:0],
    37  	}
    38  }
    39  
    40  func (b *bucket) get(kstr string, key Key) ([]byte, int64, bool) {
    41  	// find conflict map.
    42  	idx, ok := b.cmap[kstr]
    43  	if ok && !idx.expired() {
    44  		_, _, val := b.find(idx)
    45  		return val, idx.TTL(), ok
    46  	}
    47  
    48  	// find index map.
    49  	idx, ok = b.index[key]
    50  	if ok && !idx.expired() {
    51  		_, _, val := b.find(idx)
    52  		return val, idx.TTL(), ok
    53  	}
    54  
    55  	return nil, 0, false
    56  }
    57  
    58  //	 map[Key]Idx ----+
    59  //	                 |
    60  //	                 v
    61  //	               start
    62  //			   +-----+------------+------------+------------+------------+-----+
    63  //			   | ... |    klen    |    vlen    |    key     |    value   | ... |
    64  //			   +-----+------------+------------+------------+------------+-----+
    65  //		             |<- varint ->|<- varint ->|<-- klen -->|<-- vlen -->|
    66  //				     |<--------------------- entry --------------------->|
    67  //
    68  // set stores key-value pair into bucket.
    69  func (b *bucket) set(key Key, kstr, val []byte, ts int64) {
    70  	// check conflict map.
    71  	idx, ok := b.cmap[b2s(kstr)]
    72  	if ok {
    73  		entry, kstrOld, valOld := b.find(idx)
    74  
    75  		// update inplaced.
    76  		if len(kstr) == len(kstrOld) && len(val) == len(valOld) {
    77  			copy(kstrOld, kstr)
    78  			copy(valOld, val)
    79  			b.cmap[string(kstr)] = idx.setTTL(ts)
    80  			return
    81  		}
    82  
    83  		// alloc new space.
    84  		b.unused += uint64(len(entry))
    85  		b.cmap[string(kstr)] = b.appendEntry(kstr, val, ts)
    86  		return
    87  	}
    88  
    89  	// check index map.
    90  	idx, ok = b.index[key]
    91  	if ok {
    92  		entry, kstrOld, valOld := b.find(idx)
    93  
    94  		// if hash conflict, insert to cmap.
    95  		if !idx.expired() && !bytes.Equal(kstr, kstrOld) {
    96  			b.cmap[string(kstr)] = b.appendEntry(kstr, val, ts)
    97  			return
    98  		}
    99  
   100  		// update inplaced.
   101  		if len(kstr) == len(kstrOld) && len(val) == len(valOld) {
   102  			copy(kstrOld, kstr)
   103  			copy(valOld, val)
   104  			b.index[key] = idx.setTTL(ts)
   105  			return
   106  		}
   107  
   108  		// alloc new space.
   109  		b.unused += uint64(len(entry))
   110  	}
   111  
   112  	// insert.
   113  	b.index[key] = b.appendEntry(kstr, val, ts)
   114  }
   115  
   116  func (b *bucket) appendEntry(kstr, val []byte, ts int64) Idx {
   117  	idx := newIdx(len(b.data), ts)
   118  	// append klen, vlen, key, val.
   119  	b.data = binary.AppendUvarint(b.data, uint64(len(kstr)))
   120  	b.data = binary.AppendUvarint(b.data, uint64(len(val)))
   121  	b.data = append(b.data, kstr...)
   122  	b.data = append(b.data, val...)
   123  	return idx
   124  }
   125  
   126  func (b *bucket) remove(key Key, kstr string) bool {
   127  	idx, ok := b.cmap[kstr]
   128  	if ok {
   129  		b.removeConflict(kstr, idx)
   130  		return !idx.expired()
   131  	}
   132  
   133  	idx, ok = b.index[key]
   134  	if ok {
   135  		b.removeIndex(key, idx)
   136  		return !idx.expired()
   137  	}
   138  
   139  	return false
   140  }
   141  
   142  func (b *bucket) setTTL(key Key, kstr string, ts int64) bool {
   143  	idx, ok := b.cmap[kstr]
   144  	if ok && !idx.expired() {
   145  		b.cmap[kstr] = newIdx(idx.start(), ts)
   146  		return true
   147  	}
   148  
   149  	idx, ok = b.index[key]
   150  	if ok && !idx.expired() {
   151  		b.index[key] = newIdx(idx.start(), ts)
   152  		return true
   153  	}
   154  
   155  	return false
   156  }
   157  
   158  func (b *bucket) scan(f Walker) (next bool) {
   159  	next = true
   160  
   161  	for _, idx := range b.cmap {
   162  		if idx.expired() {
   163  			continue
   164  		}
   165  		_, kstr, val := b.find(idx)
   166  		next = f(kstr, val, idx.TTL())
   167  		if !next {
   168  			return
   169  		}
   170  	}
   171  
   172  	for _, idx := range b.index {
   173  		if idx.expired() {
   174  			continue
   175  		}
   176  		_, kstr, val := b.find(idx)
   177  		next = f(kstr, val, idx.TTL())
   178  		if !next {
   179  			return
   180  		}
   181  	}
   182  	return
   183  }
   184  
   185  // eliminate the expired key-value pairs.
   186  func (b *bucket) eliminate() {
   187  	var failed int
   188  	if b.options.DisableEvict {
   189  		goto MIG
   190  	}
   191  
   192  	b.interval++
   193  	if b.interval < b.options.EvictInterval {
   194  		return
   195  	}
   196  	b.interval = 0
   197  
   198  	// probing
   199  	for key, idx := range b.cmap {
   200  		b.probe++
   201  		if idx.expired() {
   202  			b.removeConflict(key, idx)
   203  			b.evict++
   204  		}
   205  	}
   206  
   207  	for key, idx := range b.index {
   208  		b.probe++
   209  		if idx.expired() {
   210  			b.removeIndex(key, idx)
   211  			b.evict++
   212  			failed = 0
   213  
   214  		} else {
   215  			failed++
   216  			if failed > maxFailCount {
   217  				break
   218  			}
   219  		}
   220  	}
   221  
   222  MIG:
   223  	// check need to migrate.
   224  	rate := float64(b.unused) / float64(len(b.data))
   225  	if rate >= b.options.MigrateRatio {
   226  		b.migrate()
   227  	}
   228  }
   229  
   230  // migrate move valid key-value pairs to the new container to save memory.
   231  func (b *bucket) migrate() {
   232  	newData := bpool.Get(len(b.data))[:0]
   233  
   234  	// migrate data to new bucket.
   235  	for key, idx := range b.index {
   236  		if idx.expired() {
   237  			delete(b.index, key)
   238  			continue
   239  		}
   240  		// update with new position.
   241  		b.index[key] = newIdxx(len(newData), idx)
   242  		entry, _, _ := b.find(idx)
   243  		newData = append(newData, entry...)
   244  	}
   245  
   246  	for kstr, idx := range b.cmap {
   247  		if idx.expired() {
   248  			delete(b.cmap, kstr)
   249  			continue
   250  		}
   251  		key := Key(b.options.HashFn(kstr))
   252  		// check if conflict.
   253  		_, ok := b.index[key]
   254  		if ok {
   255  			b.cmap[kstr] = newIdxx(len(newData), idx)
   256  		} else {
   257  			b.index[key] = newIdxx(len(newData), idx)
   258  			delete(b.cmap, kstr)
   259  		}
   260  		entry, _, _ := b.find(idx)
   261  		newData = append(newData, entry...)
   262  	}
   263  
   264  	bpool.Put(b.data)
   265  	b.data = newData
   266  	b.unused = 0
   267  	b.migrates++
   268  }
   269  
   270  func (b *bucket) find(idx Idx) (entry, kstr, val []byte) {
   271  	var index = idx.start()
   272  	// klen
   273  	klen, n := binary.Uvarint(b.data[index:])
   274  	index += n
   275  	// vlen
   276  	vlen, n := binary.Uvarint(b.data[index:])
   277  	index += n
   278  	// kstr
   279  	kstr = b.data[index : index+int(klen)]
   280  	index += int(klen)
   281  	// val
   282  	val = b.data[index : index+int(vlen)]
   283  	index += int(vlen)
   284  
   285  	return b.data[idx.start():index], kstr, val
   286  }
   287  
   288  func (b *bucket) removeConflict(key string, idx Idx) {
   289  	entry, _, _ := b.find(idx)
   290  	b.unused += uint64(len(entry))
   291  	delete(b.cmap, key)
   292  }
   293  
   294  func (b *bucket) removeIndex(key Key, idx Idx) {
   295  	entry, _, _ := b.find(idx)
   296  	b.unused += uint64(len(entry))
   297  	delete(b.index, key)
   298  }