DEV Community

Leo Antony
Leo Antony

Posted on

Go High-Performance Cache with TTL and Disk Persistence

1.Introduction

Accelerate your Golang projects without the hassle of setting up a database everytime you begin a new project. Tired of configuring databases from scratch? Only to face new problems? Look no further In this blog we will be looking into Golang caching library with Support for TTL, disk Persistence and Hash Data type.

GoSwift.

2.Prerequisite

  • Basic Knowledge of Golang
  • Knowledge of how a typical cache works

3.Features

  1. Set & Get command
  2. Update command
  3. Del command
  4. Exists command
  5. Support for TTL
  6. Support for Disk Save(Snapshots)
  7. Support Hash Data type (Hset, Hget, HgetAll, HMset)
  8. Safe Locking

Set & Get command


import (
    "fmt"
    "github.com/leoantony72/goswift"
)

func main(){
    cache := goswift.NewCache()

    // Value 0 indicates no expiry
    cache.Set("key", "value", 0)

    val, err := cache.Get("key")
    if err !=nil{
        fmt.Println(err)
        return
    }
    fmt.Println("key", val)
}
Enter fullscreen mode Exit fullscreen mode

Update command

// Update value
// @Update(key string, val interface{}) error
err = cache.Update("key","value2")
if err != nil{
    fmt.Println(err)
    return
}
Enter fullscreen mode Exit fullscreen mode

Del command && Exists command

// Delete command
// @Del(key string)
cache.Del("key")

// Exist command
// @Exists(key string) bool
value = cache.Exists("key")
fmt.Println(value) // returns false
Enter fullscreen mode Exit fullscreen mode

Support for TTL

// Set Value with Expiry
// @Set(key string, val interface{}, exp int)
// Here expiry is set to 1sec
cache.Set("key","value",1000)

// Hset command
// @Hset(key, field string, value interface{}, exp int)
// in this case the "key" expires in 1sec
cache.Hset("key","name","value",1000)
cache.Hset("key","age",18,1000)
Enter fullscreen mode Exit fullscreen mode

Support Hash Data type (Hset, Hget, HgetAll, HMset)


// Hset command
// @Hset(key, field string, value interface{}, exp int)
// in this case the "key" expires in 1sec
cache.Hset("key","name","value",1000)
cache.Hset("key","age",18,1000)


// HMset command
// @HMset(key string, d interface{}, exp int) error
// Set a Hash by passing a Struct/Map
// ---by passing a struct---
type Person struct{
    Name  string
    Age   int
    Place string
}

person1 := &Person{Name:"bob",Age:18,Place:"NYC"}
err = cache.HMset("key",person1)
if err != nil{
    fmt.Println(err)
    return
}

// ---by passing a map---
person2 := map[string]interface{Name:"john",Age:18,Place:"NYC"}
err = cache.HMset("key",person2)
if err != nil{
    fmt.Println(err)
    return
}


// Hget command
// @HGet(key, field string) (interface{}, error)
// get individual fields in Hash
data,err := cache.HGet("key","field")
if err != nil{
    fmt.Println(err)
    return
}
fmt.Println(data)

// HgetAll command
// @HGetAll(key string) (map[string]interface{}, error)
// gets all the fields with value in a hash key
// retuns a map[string]interface{}
data,err = cache.HGetAll("key")
if err != nil{
    fmt.Println(err)
    return
}

Enter fullscreen mode Exit fullscreen mode

Snapshots

opt := goswift.CacheOptions{
        EnableSnapshots:  true,
        SnapshotInterval: time.Second*5,
    }
c := goswift.NewCache(opt)
Enter fullscreen mode Exit fullscreen mode

This will take a snapshot of the Data Every 5sec and saves it into a Snapshot.data file. By default Snapshots are disabled and if the SnapshotInterval is not provided default value is 5seconds.

NOTE: If the EnableSnapshot is false, Data saved in the file will not imported

Error handling

const (
    ErrKeyNotFound   = "key does not Exists"
    ErrFieldNotFound = "field does not Exists"
    ErrNotHashvalue  = "not a Hash value/table"
    ErrHmsetDataType = "invalid data type, Expected Struct/Map"
)
Enter fullscreen mode Exit fullscreen mode

These are the common Errors that may occur while writing the code. These Varible provide you a clear and easy Error comparison method to determine errors.

data,err := cache.Get("key")
if err != nil {
    if err.Error() == goswift.ErrKeyNotFound {
        //do something
}
}    
Enter fullscreen mode Exit fullscreen mode

Inner workings of the cache expiry

Every 3sec the **sweaper **function gets called to clear out the expired values from the hash table. We maintain a min heap which points to the hash map. The top element will be the key with the smallest TTL. we traverse through the tree until TTL is greater then the current time.

Summary

I wouldn't advice you to use this in production!!, but feel free to use it in your small side project. Do try it out and if you encounter a bug do make a issue on GitHub repo.

Email: leoantony102@gmail.com
Github: https://github.com/leoantony72
Repo: https://github.com/leoantony72/goswift

Top comments (2)

Collapse
 
tempmaildetector profile image
TMD

Nicely done, especially with persistence. For a small service this could help with the "thundering herd" problem.

Collapse
 
leoantony72 profile image
Leo Antony

Thank you😁