Skip to content

Commit

Permalink
Merge pull request #1 from ashuthe1/featurepersistentStorage
Browse files Browse the repository at this point in the history
Feature: Support for Saving Cache to File and vice-versa
  • Loading branch information
ashuthe1 authored Jul 8, 2024
2 parents b07543a + f5c8be8 commit 73f9645
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 51 deletions.
101 changes: 62 additions & 39 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,45 +57,45 @@ func (c *Cache) Set(key string, value interface{}) {
}

func (c *Cache) Get(key string) (interface{}, bool) {
// Initial read lock to safely access the cache
c.mu.RLock()
// Ensure the read lock is released when the function exits
defer c.mu.RUnlock()

// Check if the item exists in the cache
item, found := c.items[key]
if !found {
// Record a cache miss if the item is not found
c.benchmark.RecordMiss()
return nil, false
}

// Check if the item has expired
if time.Now().After(item.ExpiresAt) {
// Upgrade to a write lock to modify the cache
c.mu.RUnlock()
c.mu.Lock()
// Double-check if the item is still expired after acquiring the write lock
item, found = c.items[key]
if found && time.Now().After(item.ExpiresAt) {
// Remove the expired item from the cache
delete(c.items, key)
c.policy.Remove(key)
c.benchmark.RecordExpiration()
if c.onEvicted != nil {
c.onEvicted(key, item.Value)
}
}
// Release the write lock and downgrade to a read lock
c.mu.Unlock()
c.mu.RLock()

return nil, false
}

// Record a cache hit if the item is found and not expired
c.benchmark.RecordHit()
return item.Value, true
// Initial read lock to safely access the cache
c.mu.RLock()
// Ensure the read lock is released when the function exits
defer c.mu.RUnlock()

// Check if the item exists in the cache
item, found := c.items[key]
if !found {
// Record a cache miss if the item is not found
c.benchmark.RecordMiss()
return nil, false
}

// Check if the item has expired
if time.Now().After(item.ExpiresAt) {
// Upgrade to a write lock to modify the cache
c.mu.RUnlock()
c.mu.Lock()
// Double-check if the item is still expired after acquiring the write lock
item, found = c.items[key]
if found && time.Now().After(item.ExpiresAt) {
// Remove the expired item from the cache
delete(c.items, key)
c.policy.Remove(key)
c.benchmark.RecordExpiration()
if c.onEvicted != nil {
c.onEvicted(key, item.Value)
}
}
// Release the write lock and downgrade to a read lock
c.mu.Unlock()
c.mu.RLock()

return nil, false
}

// Record a cache hit if the item is found and not expired
c.benchmark.RecordHit()
return item.Value, true
}

func (c *Cache) Delete(key string) {
Expand Down Expand Up @@ -171,3 +171,26 @@ func (c *Cache) BatchGet(keys []string) map[string]interface{} {
}
return results
}

// Items returns all the items in the cache.
func (c *Cache) Items() map[string]CacheItem {
c.mu.RLock()
defer c.mu.RUnlock()

itemsCopy := make(map[string]CacheItem)
for key, item := range c.items {
itemsCopy[key] = item
}
return itemsCopy
}

// SetItems sets multiple items in the cache.
func (c *Cache) SetItems(items map[string]CacheItem) {
c.mu.Lock()
defer c.mu.Unlock()

for key, item := range items {
c.items[key] = item
c.policy.Add(key)
}
}
72 changes: 72 additions & 0 deletions persistence/file_persistence.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package persistence

import (
"encoding/json"
"io"
"os"
"sync"

"github.com/ashuthe1/kuki-memcache/cache"
)

// FilePersistence handles saving and loading data to/from a file.
type FilePersistence struct {
filePath string
mu sync.Mutex
}

// NewFilePersistence creates a new FilePersistence instance with the given filePath.
func NewFilePersistence(filePath string) *FilePersistence {
return &FilePersistence{
filePath: filePath,
}
}

// SaveToFile saves data to a file.
func (fp *FilePersistence) SaveToFile(data interface{}) error {
fp.mu.Lock()
defer fp.mu.Unlock()

file, err := os.Create(fp.filePath)
if err != nil {
return err
}
defer file.Close()

jsonData, err := json.Marshal(data)
if err != nil {
return err
}

_, err = file.Write(jsonData)
if err != nil {
return err
}

return nil
}

// LoadFromFile loads data from a file.
func (fp *FilePersistence) LoadFromFile() (map[string]cache.CacheItem, error) {
fp.mu.Lock()
defer fp.mu.Unlock()

file, err := os.Open(fp.filePath)
if err != nil {
return nil, err
}
defer file.Close()

jsonData, err := io.ReadAll(file)
if err != nil {
return nil, err
}

var data map[string]cache.CacheItem
err = json.Unmarshal(jsonData, &data)
if err != nil {
return nil, err
}

return data, nil
}
12 changes: 12 additions & 0 deletions persistence/persistable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package persistence

import (
"github.com/ashuthe1/kuki-memcache/cache"
)

// CachePersistable defines methods that must be implemented by a cache for persistence.
type CachePersistable interface {
Lock()
Unlock()
Items() map[string]cache.CacheItem
}
1 change: 1 addition & 0 deletions sample/cache_data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"key1":{"Value":"value1","ExpiresAt":"2024-07-08T19:36:19.444308625+05:30"},"key2":{"Value":"value2","ExpiresAt":"2024-07-08T19:36:19.444397391+05:30"}}
44 changes: 32 additions & 12 deletions sample/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,43 @@ import (

"github.com/ashuthe1/kuki-memcache/cache"
"github.com/ashuthe1/kuki-memcache/eviction"
"github.com/ashuthe1/kuki-memcache/persistence"
)

func main() {
c := cache.NewCache(20*time.Minute, 10, eviction.NewLRU(), nil)
// Initialize cache
evictionPolicy := eviction.NewLRU()
myCache := cache.NewCache(5*time.Minute, 100, evictionPolicy, nil)

// Batch set key-value pairs
items := map[string]interface{}{
"key1": "value1",
"key2": 123,
"key3": []int{1, 2, 3},
// Add some items to the cache
myCache.Set("key1", "value1")
myCache.Set("key2", "value2")

// Save cache to file
persistenceManager := persistence.NewFilePersistence("cache_data.json")
if err := persistenceManager.SaveToFile(myCache.Items()); err != nil {
fmt.Println("Error saving cache to file:", err)
return
}
c.BatchSet(items)

// Batch get values
keys := []string{"key1", "key2", "key3"}
values := c.BatchGet(keys)
for k, v := range values {
fmt.Printf("%s: %v\n", k, v)
fmt.Println("Cache saved to file")

// Simulate loading cache from file
loadedItems, err := persistenceManager.LoadFromFile()
if err != nil {
fmt.Println("Error loading cache from file:", err)
return
}

// Initialize a new cache with loaded items
loadedCache := cache.NewCache(5*time.Minute, 100, evictionPolicy, nil)
loadedCache.SetItems(loadedItems)

// Retrieve items from loaded cache
val1, found1 := loadedCache.Get("key1")
val2, found2 := loadedCache.Get("key2")

fmt.Println("Loaded cache from file")
fmt.Printf("key1: %v, found: %v\n", val1, found1)
fmt.Printf("key2: %v, found: %v\n", val2, found2)
}

0 comments on commit 73f9645

Please sign in to comment.