|
1 | 1 | package main
|
2 | 2 |
|
3 |
| -import "sync" |
| 3 | +import ( |
| 4 | + "container/list" |
| 5 | + "sync" |
| 6 | + "time" |
| 7 | +) |
4 | 8 |
|
5 | 9 | type KeyVal struct {
|
6 |
| - mu sync.RWMutex |
7 |
| - data map[string][]byte |
| 10 | + mu sync.RWMutex |
| 11 | + data map[string][]byte |
| 12 | + lruList *list.List |
| 13 | + keyToElement map[string]*list.Element |
| 14 | + memoryLimit int64 |
| 15 | + currentSize int64 |
8 | 16 | }
|
9 | 17 |
|
10 |
| -func NewKeyVal() *KeyVal { |
11 |
| - return &KeyVal{ |
12 |
| - data: map[string][]byte{}, |
| 18 | +type lruItem struct { |
| 19 | + key string |
| 20 | + value []byte |
| 21 | +} |
| 22 | + |
| 23 | +// memoryLimit should be in MB |
| 24 | +func NewKeyVal(memoryLimit int64) *KeyVal { |
| 25 | + memoryLimit = memoryLimit * 1024 * 1024 //Size in mb |
| 26 | + kv := &KeyVal{ |
| 27 | + data: make(map[string][]byte), |
| 28 | + lruList: list.New(), |
| 29 | + keyToElement: make(map[string]*list.Element), |
| 30 | + memoryLimit: memoryLimit, |
| 31 | + currentSize: 0, |
13 | 32 | }
|
| 33 | + go kv.startEviction() // Start the background eviction goroutine |
| 34 | + return kv |
14 | 35 | }
|
| 36 | +func (kv *KeyVal) Set(key, val []byte) error { |
| 37 | + kv.mu.Lock() |
| 38 | + defer kv.mu.Unlock() |
| 39 | + |
| 40 | + keyStr := string(key) |
15 | 41 |
|
16 |
| -func (KeyVal *KeyVal) Set(key, val []byte) error { |
17 |
| - KeyVal.mu.Lock() |
18 |
| - defer KeyVal.mu.Unlock() |
19 |
| - KeyVal.data[string(key)] = []byte(val) |
| 42 | + if existingElem, exists := kv.keyToElement[keyStr]; exists { |
| 43 | + existingItem := existingElem.Value.(*lruItem) |
| 44 | + kv.currentSize -= int64(len(existingItem.value)) |
| 45 | + existingItem.value = val |
| 46 | + kv.currentSize += int64(len(val)) |
| 47 | + kv.lruList.MoveToFront(existingElem) |
| 48 | + } else { |
| 49 | + item := &lruItem{key: keyStr, value: val} |
| 50 | + element := kv.lruList.PushFront(item) |
| 51 | + kv.keyToElement[keyStr] = element |
| 52 | + kv.data[keyStr] = val |
| 53 | + kv.currentSize += int64(len(val)) |
| 54 | + } |
| 55 | + kv.evictIfNeeded() |
20 | 56 | return nil
|
21 | 57 | }
|
22 | 58 |
|
23 |
| -func (KeyVal *KeyVal) Get(key []byte) ([]byte, bool) { |
24 |
| - KeyVal.mu.RLock() |
25 |
| - defer KeyVal.mu.RUnlock() |
26 |
| - val, ok := KeyVal.data[string(key)] |
27 |
| - return val, ok |
| 59 | +func (kv *KeyVal) Get(key []byte) ([]byte, bool) { |
| 60 | + kv.mu.Lock() |
| 61 | + defer kv.mu.Unlock() |
| 62 | + |
| 63 | + keyStr := string(key) |
| 64 | + if element, exists := kv.keyToElement[keyStr]; exists { |
| 65 | + kv.lruList.MoveToFront(element) |
| 66 | + return kv.data[keyStr], true |
| 67 | + } |
| 68 | + return nil, false |
| 69 | +} |
| 70 | + |
| 71 | +func (kv *KeyVal) evictIfNeeded() { |
| 72 | + for kv.currentSize > kv.memoryLimit { |
| 73 | + lastElem := kv.lruList.Back() |
| 74 | + if lastElem == nil { |
| 75 | + break |
| 76 | + } |
| 77 | + item := lastElem.Value.(*lruItem) |
| 78 | + delete(kv.data, item.key) |
| 79 | + kv.lruList.Remove(lastElem) |
| 80 | + delete(kv.keyToElement, item.key) |
| 81 | + kv.currentSize -= int64(len(item.value)) |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +func (kv *KeyVal) startEviction() { |
| 86 | + for { |
| 87 | + time.Sleep(10 * time.Second) |
| 88 | + kv.mu.Lock() |
| 89 | + kv.evictIfNeeded() |
| 90 | + kv.mu.Unlock() |
| 91 | + } |
28 | 92 | }
|
0 commit comments