github.com/matrixorigin/matrixone@v1.2.0/pkg/fileservice/fifocache/fifo.go (about)

     1  // Copyright 2024 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fifocache
    16  
    17  import (
    18  	"sync"
    19  	"sync/atomic"
    20  )
    21  
    22  // Cache implements an in-memory cache with FIFO-based eviction
    23  // it's mostly like the S3-fifo, only without the ghost queue part
    24  type Cache[K comparable, V any] struct {
    25  	capacity     int
    26  	capacity1    int
    27  	onEvict      func(K, V)
    28  	keyShardFunc func(K) uint8
    29  
    30  	shards [256]struct {
    31  		sync.RWMutex
    32  		values map[K]*_CacheItem[K, V]
    33  	}
    34  
    35  	queueLock sync.Mutex
    36  	used1     int
    37  	queue1    Queue[*_CacheItem[K, V]]
    38  	used2     int
    39  	queue2    Queue[*_CacheItem[K, V]]
    40  }
    41  
    42  type _CacheItem[K comparable, V any] struct {
    43  	key   K
    44  	value V
    45  	size  int
    46  	count atomic.Int32
    47  }
    48  
    49  func (c *_CacheItem[K, V]) inc() {
    50  	for {
    51  		cur := c.count.Load()
    52  		if cur >= 3 {
    53  			return
    54  		}
    55  		if c.count.CompareAndSwap(cur, cur+1) {
    56  			return
    57  		}
    58  	}
    59  }
    60  
    61  func (c *_CacheItem[K, V]) dec() {
    62  	for {
    63  		cur := c.count.Load()
    64  		if cur <= 0 {
    65  			return
    66  		}
    67  		if c.count.CompareAndSwap(cur, cur-1) {
    68  			return
    69  		}
    70  	}
    71  }
    72  
    73  func New[K comparable, V any](
    74  	capacity int,
    75  	onEvict func(K, V),
    76  	keyShardFunc func(K) uint8,
    77  ) *Cache[K, V] {
    78  	ret := &Cache[K, V]{
    79  		capacity:     capacity,
    80  		capacity1:    capacity / 10,
    81  		queue1:       *NewQueue[*_CacheItem[K, V]](),
    82  		queue2:       *NewQueue[*_CacheItem[K, V]](),
    83  		onEvict:      onEvict,
    84  		keyShardFunc: keyShardFunc,
    85  	}
    86  	for i := range ret.shards {
    87  		ret.shards[i].values = make(map[K]*_CacheItem[K, V], 1024)
    88  	}
    89  	return ret
    90  }
    91  
    92  func (c *Cache[K, V]) Set(key K, value V, size int) {
    93  	shard := &c.shards[c.keyShardFunc(key)]
    94  	shard.Lock()
    95  	_, ok := shard.values[key]
    96  	if ok {
    97  		// existed
    98  		shard.Unlock()
    99  		return
   100  	}
   101  
   102  	item := &_CacheItem[K, V]{
   103  		key:   key,
   104  		value: value,
   105  		size:  size,
   106  	}
   107  	shard.values[key] = item
   108  	shard.Unlock()
   109  
   110  	c.queueLock.Lock()
   111  	defer c.queueLock.Unlock()
   112  	c.queue1.enqueue(item)
   113  	c.used1 += size
   114  	if c.used1+c.used2 > c.capacity {
   115  		c.evict()
   116  	}
   117  }
   118  
   119  func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
   120  	shard := &c.shards[c.keyShardFunc(key)]
   121  	shard.RLock()
   122  	var item *_CacheItem[K, V]
   123  	item, ok = shard.values[key]
   124  	if !ok {
   125  		shard.RUnlock()
   126  		return
   127  	}
   128  	shard.RUnlock()
   129  	item.inc()
   130  	return item.value, true
   131  }
   132  
   133  func (c *Cache[K, V]) Delete(key K) {
   134  	shard := &c.shards[c.keyShardFunc(key)]
   135  	shard.Lock()
   136  	defer shard.Unlock()
   137  	delete(shard.values, key)
   138  	// we don't update queues
   139  }
   140  
   141  func (c *Cache[K, V]) evict() {
   142  	for c.used1+c.used2 > c.capacity {
   143  		if c.used1 > c.capacity1 {
   144  			c.evict1()
   145  		} else {
   146  			c.evict2()
   147  		}
   148  	}
   149  }
   150  
   151  func (c *Cache[K, V]) evict1() {
   152  	// queue 1
   153  	for {
   154  		item, ok := c.queue1.dequeue()
   155  		if !ok {
   156  			// queue empty
   157  			return
   158  		}
   159  		if item.count.Load() > 1 {
   160  			// put queue2
   161  			c.queue2.enqueue(item)
   162  			c.used1 -= item.size
   163  			c.used2 += item.size
   164  		} else {
   165  			// evict
   166  			shard := &c.shards[c.keyShardFunc(item.key)]
   167  			shard.Lock()
   168  			delete(shard.values, item.key)
   169  			shard.Unlock()
   170  			if c.onEvict != nil {
   171  				c.onEvict(item.key, item.value)
   172  			}
   173  			c.used1 -= item.size
   174  			return
   175  		}
   176  	}
   177  }
   178  
   179  func (c *Cache[K, V]) evict2() {
   180  	// queue 2
   181  	for {
   182  		item, ok := c.queue2.dequeue()
   183  		if !ok {
   184  			// empty queue
   185  			break
   186  		}
   187  		if item.count.Load() > 0 {
   188  			// re-enqueue
   189  			c.queue2.enqueue(item)
   190  			item.dec()
   191  		} else {
   192  			// evict
   193  			shard := &c.shards[c.keyShardFunc(item.key)]
   194  			shard.Lock()
   195  			delete(shard.values, item.key)
   196  			shard.Unlock()
   197  			if c.onEvict != nil {
   198  				c.onEvict(item.key, item.value)
   199  			}
   200  			c.used2 -= item.size
   201  			return
   202  		}
   203  	}
   204  }