Skip to content

Commit

Permalink
add docs and stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
anonyindian committed Feb 28, 2023
1 parent b7626b8 commit 234c4c0
Show file tree
Hide file tree
Showing 17 changed files with 585 additions and 35 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@

# Dependency directories (remove the comment below to include it)
# vendor/

benchfiles/
98 changes: 93 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,96 @@
# Cacher
Cacher is a fast, decentralised caching system, generic in nature and uses Go's in built mapping to store data. It has a plenty of features like TTL, Revaluation etc.
# Cacher

Some of the main features are descried below:
Cacher is a fast, decentralised caching library, generic in nature and uses Go's built-in maps to store data. It has a plenty of features like TTL, Revaluation etc.

TTL (Time-To-Live): It allows us to expire a key after a specific time period.
There are plenty examples available in the [examples](./examples) directory and cover almost every feature of the library with explanation and real life examples.

Revaluation: This is another useful feature that allows us to keep keys cached as per their usage frequency.
Support Go Versions: Go v1.18 and newer

[![Go Reference](https://pkg.go.dev/badge/github.com/animekaizoku/cacher.svg)](https://pkg.go.dev/github.com/animekaizoku/cacher) [![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](http://perso.crans.org/besson/LICENSE.html)

## Key Highlights:
- **Decentralised**: Allows users to implement decentralised caching system which helps to perform multiple set-get operations parallely.
- **TTL (Time-To-Live)**: It allows us to expire a key after a specific time period.
- **Revaluation**: This is another useful feature that allows us to keep keys cached as per their usage frequency.
- **Zero Bloat**: Doesn't rely on any 3rd party library and only uses standard ones.
- **Structs Friendly**: You don't need to serialize your structs to bytes to save them as values, which makes the set-get process faster and allows us to write more readable code.
- **Well Documentated**: Cacher is very well documentated and contains examples in the docs wherever required. There are plenty of examples available in the [examples](./examples) directory to get a quick overview.

## Getting Started
We have divided this section in several sub-sections, you can follow them sequentially to start using this library right now!

### Downloading the library
You can download it using the standard `go get` command:
```bash
go get github.com/AnimeKaizoku/cacher
```

### Basic Example
We will assume that you've already downloaded the library in your project as described in the previous section.
Now we will learn how to use it in your Go projects with the help of a basic example.

You can check-out examples directory for more detailed and well explained examples, here we will create just a basic program with a simple Cacher instance:
```golang
package main

import "github.com/AnimeKaizoku/cacher"

var cache = cacher.NewCacher[int, string](nil)

func main() {
cache.Set(1, "Value for 1 of type string")
cache.Set(2, "Value for 2 of type string")

valueOf1, ok := cache.Get(1)
if !ok {
println("value of 1 not found")
return
}
println("value of 1 is:")
println(valueOf1)
}
```

### Documentation and Examples
Cacher is a well-documentated library, and contains well explained examples with the help of real life cases which can be found out in [examples](./examples/) directory.

[Click here](https://pkg.go.dev/github.com/animekaizoku/cacher) to check-out documentations.

### Decentralised Usage
As we have already learnt how to create a simple Cacher instance, we can head towards our next step.

Decentralising means, distributing the work of a single central system to multiple systems. Since Cacher is a lightweight library, we can easily create new cacher instances for every level of our program.

An example structure is shown below:
```
cacher1----module1.go---\
cacher2----module2.go----\
cacher3----module2.go-----} main.go
cacher4----module3.go----/
cacher5----module4.go---/
```
As we can see above, we have 5 modules in our program (module 1-5) where each of them have their own Cacher instance instead of a single caching instance we would've defined for our whole program. This approach allows us to distribute our load to multiple cachers which will help us to achieve better efficiency than we would have got from a single central cacher. This approach also allows us to use different Types of values for different modules.

## Benchmarks
We made comparision between 3 libraries, i.e. Cacher, BigCache, FreeCache and took benchmark tests in two different situations:

**Situation 1**: Serialisation needed
![bench1](./assets/bench1.jpg)
Above screenshot contains benchmark of the situation when you'll need to serialise your struct to bytes to store in BigCache/FreeCache, while you won't need to do that in case of Cacher since it uses Generics.
We used "encoding/gob" for serialising the value struct to bytes


**Situation 2**: Serialisation not needed
![bench2](./assets/bench2.jpg)
Above screenshot contains benchmark of the situation when you won't need to serialise your value to bytes or we can say, serialisation time removed.

**Conclusion**: We can clearly see the difference. Although the libraries like BigCache and FreeCache act quickly without the serialisation time included, in practical life, you will **need to** serialise your date when attempting to cache structs while since Cacher uses Generic ability of Go, you won't need to serialise your data while using it and can just create a new cacher instance with your Value Type set as your struct's type.

## Contributing
Contributions are always welcome! Just create a PR (Pull Request) with what you want to contribute with a brief description.

Please make sure to update examples as appropriate.

## License
[![GPLv3](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
<br>Cacher is licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.en.html">GNU General Public License v3</a>
Binary file added assets/bench1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/bench2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 62 additions & 27 deletions cacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,56 +30,82 @@ type Cacher[C comparable, T any] struct {
cleanInterval time.Duration
}

// NewCacher is a generic function which creates a new Cacher instance.
//
// Generic parameters (for current Cacher instance):
// This struct contains the optional arguments which can be filled
// while creating a new Cacher instance.
//
// KeyT: It is the "static" type of keys of our cache.
// It accepts types which implement built-in comparable interface.
// Eg: If it is set to string, then keys will only be allowed
// as a string built-in data-type.
// Parameters:
//
// ValueT: It is the type of values of our cache.
// It can be set to any type.
// Eg: If it is set to string, then value will only be allowed
// as a string built-in data-type.
//
// Input parameters:
//
// timeToLive (type time.Duration):
// TimeToLive (type time.Duration):
// It allows us to expire a key after a specific time period.
// Eg: If it is set to 30 seconds, then each key of current
// Cacher will be expired after 30 seconds of their addition.
//
// cleanInterval (type time.Duration):
// CleanInterval (type time.Duration):
// It is the time of interval between two cleaner windows.
// A cleaner window is that time frame when all the expired
// keys will be deleted from our cache mapping.
// Eg: If cleanInterval is set to 1 hour, then cleaner
// Note: It TTL is set to a finite value and no value is passed
// to CleanInterval, it'll use a default time interval of 1 day
// for clean window.
// Eg: If CleanInterval is set to 1 hour, then cleaner
// window will be run after every 1 hour, and the expired keys
// which are present in our cache map will be deleted.
//
// revaluate (type bool):
// Revaluate (type bool):
// It allows us to keep keys cached as per their usage frequency.
// Working: Whenever the keys will be retrieved via (Cacher.Get)
// method, its expiry will be renewed and this will allow us to
// keep frequently used keys in the map without expiration.
type NewCacherOpts struct {
TimeToLive time.Duration
CleanInterval time.Duration
Revaluate bool
}

// NewCacher is a generic function which creates a new Cacher instance.
//
// Generic parameters (for current Cacher instance):
//
// KeyT: It is the "static" type of keys of our cache.
// It accepts types which implement built-in comparable interface.
// Eg: If it is set to string, then keys will only be allowed
// as a string built-in data-type.
//
// ValueT: It is the type of values of our cache.
// It can be set to any type.
// Eg: If it is set to string, then value will only be allowed
// as a string built-in data-type.
//
// Input parameters:
//
// opts (type *NewCacherOpts):
// It contains optional parameters which you can use while creating
// a new Cacher instance.
//
// General Example:
// c := cacher.NewCacher[int, string](10*time.Minute, time.Hour, true)
// c := cacher.NewCacher[int, string](&cacher.NewCacherOpts{10*time.Minute, time.Hour, true})
// will create a new Cacher instance which will expire keys after 10
// minutes of their addition to the system, all the expired keys will
// be deleted from cache once in an hour. Keys will have their expiry
// revalueted on every c.Get call.
func NewCacher[KeyT comparable, ValueT any](timeToLive time.Duration, cleanInterval time.Duration, revaluate bool) *Cacher[KeyT, ValueT] {
func NewCacher[KeyT comparable, ValueT any](opts *NewCacherOpts) *Cacher[KeyT, ValueT] {
if opts == nil {
opts = new(NewCacherOpts)
}
ttl := int64(opts.TimeToLive.Seconds())
c := Cacher[KeyT, ValueT]{
cacheMap: make(map[KeyT]*value[ValueT]),
mutex: new(sync.RWMutex),
ttl: int64(timeToLive.Seconds()),
cleanInterval: cleanInterval,
revaluate: revaluate,
ttl: ttl,
cleanInterval: opts.CleanInterval,
revaluate: opts.Revaluate,
}
if ttl != 0 {
if c.cleanInterval == 0 {
c.cleanInterval = time.Hour * 24
}
go c.cleaner()
}
go c.cleaner()
return &c
}

Expand Down Expand Up @@ -185,10 +211,13 @@ func (c *Cacher[C, T]) getRawValue(key C) (val *value[T], ok bool) {

// It packs the value to a a struct with expiry date.
func (c *Cacher[C, T]) packValue(val T) *value[T] {
return &value[T]{
expiry: time.Now().Unix() + c.ttl,
val: val,
v := value[T]{
val: val,
}
if c.ttl != 0 {
v.expiry = time.Now().Unix() + c.ttl
}
return &v
}

// Delete is used to delete the input key from current
Expand Down Expand Up @@ -232,3 +261,9 @@ func (c *Cacher[C, T]) Reset() {
defer c.mutex.Unlock()
c.cacheMap = make(map[C]*value[T])
}

// NumKeys counts the number of keys present in the
// current Cacher instance and returns that count.
func (c *Cacher[C, T]) NumKeys() int {
return len(c.cacheMap)
}
4 changes: 2 additions & 2 deletions cleaner.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ import (
func (c *Cacher[C, T]) cleaner() {
for {
currTime := time.Now().Unix()
c.mutex.Lock()
for key, val := range c.cacheMap {
// Skip the current clean window if cacher is reset or deleted.
if c.status == cacherReset || c.status == cacherDeleted {
c.status = noop
break
}
if val.expiry <= currTime {
c.mutex.Lock()
delete(c.cacheMap, key)
c.mutex.Unlock()
}
}
c.mutex.Unlock()
// cleanup expired keys every c.cleanInterval duration
time.Sleep(c.cleanInterval)
}
Expand Down
7 changes: 7 additions & 0 deletions examples/multiKey/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module github.com/AnimeKaizoku/cacher/examples/multiKey

go 1.18

require github.com/AnimeKaizoku/cacher v0.0.0

replace github.com/AnimeKaizoku/cacher => ../../
123 changes: 123 additions & 0 deletions examples/multiKey/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package main

import (
"fmt"
"strings"

"github.com/AnimeKaizoku/cacher"
)

// We'll be using PolyKeyer hence we need to choose string as the data type for Key.
// In this example, we won't be expiring keys and hence we don't pass them to opts.
var cache = cacher.NewCacher[string, string](nil)

// Following code initiates a PolyKeyer instance.
// We've chosen "chat" as the prefix key (also called primary key) and 2 as our secondary
// parameters.
// Note that it'll panic in case you pass different number of secondary keys to the keyer.New
// method than the chosen one while initiation.
var keyer = cacher.NewPolyKeyer("chat", 2)

// This function will call Get method on input key and print it if found.
func get(key string) {
title, ok := cache.Get(key)
if !ok {
fmt.Printf("Key '%s' not found in the valid cache!\n", key)
return
}
fmt.Println("Title of that chat is:", title)
}

// This function will fetch all the keys in our current cacher and print them.
func getAll() {
allKeys := cache.GetAll()
fmt.Println("All keys:")
for _, x := range allKeys {
fmt.Println("-", x)
}
}

func main() {
// We create a new key with exactly 2 arguments (we chose while initialising polykeyer)
// Let's assume that our 1st argument will be chat id and 2nd argument can either be
// "public" or "private".
var key = keyer.New("101", "private")
// Here we set the value for our key we created above and let our value be the title
// of chat.
cache.Set(key, "King's Chat")

// Uncomment the following line of code to see how does a poly key look like actually:
// fmt.Println("This is how a polykey look like:", key)

// Let's retrieve our key
// We'll use the get function we wrote earlier in this example for it!
get(key)

// We can know the number of keys which are present in our current
// Cacher instance using the cacher.NumKeys() method.
fmt.Println("Number of keys in current cacher:", cache.NumKeys())

// Resetting the cache will delete all key-value mapping present in
// it currently.
cache.Reset()

// Let's try to get our key now, it should not be found as we recently
// reset our cache.
get(key)

// Let's add the key again and a few more for further tutorial.
cache.Set(key, "King's Chat")
cache.Set(keyer.New("102", "public"), "Bob's chitchat group")
cache.Set(keyer.New("103", "private"), "Cacher Test Chat")

key1 := keyer.New("104", "public")
cache.Set(key1, "Github Public Chat")

cache.Set(keyer.New("105", "private"), "King Hero")

// Let's print the number of keys now:
fmt.Println("Number of keys:", cache.NumKeys())

// Now we will learn how to make a segrigator function which we can
// use for doing ___Some calls which are conditional in nature.
// Let out condition be: return true for all values which start
// with "King"
exampleSegrigator := func(v string) bool {
// We'll use HasPrefix function from strings package determining
// prefix.
return strings.HasPrefix(v, "King")
}

// We can also get all or some particular keys in an array at once.
// Following is the code on how to get some keys.
values := cache.GetSome(exampleSegrigator)

// Let's print our conditional values
fmt.Println(`Here are chats which start with the phrase "King"`)
for _, x := range values {
fmt.Println("-", x)
}

// Now we'll learn how to delete a specific key from our current cacher
// Let's suppose we want to delete key with chatid 104 and "public"
// secondary keys, we'll use the key1 variable we defined earlier for
// the purpose.
cache.Delete(key1)

// Let's print all the keys currently present in the cacher
// We'll use getAll function we created earlier in this example for it.
getAll()

// We've learnt a lot till yet, it's the time we introduce ourselves to
// the DeleteSome method, similar to GetSome, it deletes keys on the basis
// of segrigations, we'll just use the previous segrigator function and delete
// all keys which start with the phrase "King"
cache.DeleteSome(exampleSegrigator)

// Now we will again use getAll to see the difference finally
getAll()
}

// Congratulations! You just learnt most of the usable methods of this library!
// We request you to check out Single Key System examples to learn how to make
// a cacher which will expire keys and that too with the revaluation mode.
Loading

0 comments on commit 234c4c0

Please sign in to comment.