Skip to content

Commit

Permalink
Updating the documentation for TTL
Browse files Browse the repository at this point in the history
  • Loading branch information
benjivesterby committed Mar 29, 2022
1 parent b2b815c commit 8d146a1
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 156 deletions.
75 changes: 58 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,66 +3,107 @@
[![Build & Test Action Status](https://github.com/devnw/ttl/actions/workflows/build.yml/badge.svg)](https://github.com/devnw/ttl/actions)
[![Go Report Card](https://goreportcard.com/badge/go.devnw.com/ttl)](https://goreportcard.com/report/go.devnw.com/ttl)
[![codecov](https://codecov.io/gh/devnw/ttl/branch/main/graph/badge.svg)](https://codecov.io/gh/devnw/ttl)
[![Go Reference](https://pkg.go.dev/badge/go.devnw.com/ttl.svg)](https://pkg.go.dev/go.devnw.com/ttl)
[![Go Reference](https://pkg.go.dev/badge/go.devnw.com/ttl.svg)](https://go.devnw.com/ttl)
[![License: Apache 2.0](https://img.shields.io/badge/license-Apache-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)

## Overview

A TTL (Time To Live) cache is a data store where data is removed after a defined period of time to optimize memory performance.
A TTL (Time To Live) cache is a data store where data is removed after a defined
period of time to optimize memory performance.

### Implementation

#### TTL Details

This TTL Cache implementation is designed for ease of use. There are two methods for setting TTL for a data element.
This TTL Cache implementation is designed for ease of use. There are two methods
for setting TTL for a data element.

1. TTL is configured when the Cache is instantiated using `ttl.NewCache` which accepts a `time.Duration` parameter for the default TTL of the cache instance. Unless otherwise specified for a specific data element, this is the TTL for all data stored in the cache.
1. TTL is configured when the Cache is instantiated using `ttl.NewCache` which
accepts a `time.Duration` parameter for the default TTL of the cache
instance. Unless otherwise specified for a specific data element, this is the
TTL for all data stored in the cache.

2. TTL for each data element can be configured using the `cache.SetTTL` meethod which accepts a `time.Duration` which will set a specific TTL for that piece of data when it's loaded into the cache.
2. TTL for each data element can be configured using the `cache.SetTTL` meethod
which accepts a `time.Duration` which will set a specific TTL for that piece
of data when it's loaded into the cache.

#### TTL Extension

In order to optimize data access and retention an `extension` option was included as part of the implementation of the TTL cache. This can be enabled as part of the `NewCache` method. TTL Extension configures the cache so that when data is `READ` from the cache the timeout for that **key/value** pair is reset extending the life of that data in memory. This allows for regularly accessed data to be retained while less regularly accessed data is cleared from memory.
In order to optimize data access and retention an `extension` option was
included as part of the implementation of the TTL cache. This can be enabled as
part of the `NewCache` method. TTL Extension configures the cache so that when
data is `READ` from the cache the timeout for that **key/value** pair is reset
extending the life of that data in memory. This allows for regularly accessed
data to be retained while less regularly accessed data is cleared from memory.

## Usage

`go get -u go.devnw.com/ttl@latest`

### Cache Creation

The `NewCache` method returns an instance of the ttl.Cache[K,V] type to hold
the data.

```go
cache := ttl.NewCache(
ctx, // Context used in the application
timeout, // `time.Duration` that configures the default timeout for elements of the cache
extend, // boolean which configures whether or not the timeout should be reset on READ
// NOTE: The KeyType must adhere to the `comparable` constraint
cache := ttl.NewCache[KeyType, ValueType](
// Context used in the application
ctx,

// `time.Duration` that configures the default
// timeout for elements of the cache
timeout,

// boolean which configures whether or not the
// timeout should be reset on READ
extend,
)
```

The `NewCache` method returns the `ttl.Cache` interface which defines the expected API for storing and accessing data in the cache.

### Add Data to Cache

Adding to the cache uses a simple key/value pattern for setting data. There are two functions for adding data to the cache. The standard method `Set` uses the cache's configured default timeout.
Adding to the cache uses a simple key/value pattern for setting data. There are
two functions for adding data to the cache. The standard method `Set` uses the
cache's configured default timeout.

`err := cache.Set(ctx, key, value)`

The `SetTTL` method uses the timeout (`time.Duration`) that is passed into the method for configuring how long the cache should hold the data before it's cleared from memory.
The `SetTTL` method uses the timeout (`time.Duration`) that is passed into the
method for configuring how long the cache should hold the data before it's
cleared from memory.

`err := cache.SetTTL(ctx, key, value, timeout)`

### Get Data from Cache

Getting data from the cache follows a fairly standard pattern which is similar ot the `sync.Map` get method.
Getting data from the cache follows a fairly standard pattern which is similar
ot the `sync.Map` get method.

`value, exists := cache.Get(ctx, key)`

The Get method returns the value (if it exists) and a boolean which indicates if a value was retrieved from the cache.
The Get method returns the value (if it exists) and a boolean which indicates if
a value was retrieved from the cache.

### Delete Data from Cache

As with Get, Delete uses a similar pattern to `sync.Map`.

`cache.Delete(ctx, key)`

This deletes the key from the map as well as shuts down the backend routines running that key's processing.
This deletes the key from the map as well as shuts down the backend routines
running that key's processing.

## Benchmarks

To execute the benchmarks, run the following command:

```bash
go test -bench=. ./...
```

To view benchmarks over time for the `main` branch of the repository they can
be seen on our [Benchmark Report Card].

[Benchmark Report Card]: https://devnw.github.io/ttl/dev/bench
17 changes: 4 additions & 13 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ttl
import (
"context"
"fmt"
"sync"
"time"
)

Expand All @@ -19,15 +18,7 @@ type rw[V any] struct {
write chan<- newvalue[V]
}

type cache[K comparable, V any] struct {
ctx context.Context
timeout time.Duration
extend bool
values map[K]*rw[V]
valuesMu sync.RWMutex
}

func (c *cache[K, V]) write(key K, value *rw[V]) error {
func (c *Cache[K, V]) write(key K, value *rw[V]) error {
if c.values == nil || value == nil {
return fmt.Errorf("invalid cache instance")
}
Expand All @@ -40,7 +31,7 @@ func (c *cache[K, V]) write(key K, value *rw[V]) error {
return nil
}

func (c *cache[K, V]) cleanup() {
func (c *Cache[K, V]) cleanup() {
c.valuesMu.Lock()
defer c.valuesMu.Unlock()

Expand All @@ -62,7 +53,7 @@ func (c *cache[K, V]) cleanup() {
c.values = nil
}

func (c *cache[K, V]) set(
func (c *Cache[K, V]) set(
key K,
value V,
timeout time.Duration,
Expand Down Expand Up @@ -92,7 +83,7 @@ func (c *cache[K, V]) set(
return out
}

func (c *cache[K, V]) rwloop(
func (c *Cache[K, V]) rwloop(
ctx context.Context,
key K,
value V,
Expand Down
47 changes: 31 additions & 16 deletions cache_exported.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,46 @@
// Package ttl implements a TTL cache which can be used to store
// values a specified timeout period. The cache implementation
// supports extending the timeout of regularly read values as well
// as storing custom TTL timeouts for specific key/value pairs
// with the `SetTTL` method.
package ttl

import (
"context"
"fmt"
"sync"
"time"
)

type Cache[K comparable, V any] interface {
Get(ctx context.Context, key K) (V, bool)

Set(ctx context.Context, key K, value V) error

// SetTTL allows for overriding the default timeout
// for the cache for this value
SetTTL(ctx context.Context, key K, value V, timeout time.Duration) error

Delete(ctx context.Context, key K)
// Cache implements a TTL Cache which can be used to store
// values values a specified timeout period. If the value
// is read before the timeout period has passed, and the
// extend flag is set to true, the timeout period will be
// extended by the timeout period. This ensures that the
// value is not removed if it is regularly accessed within
// the timeout period.
type Cache[K comparable, V any] struct {
ctx context.Context
timeout time.Duration
extend bool
values map[K]*rw[V]
valuesMu sync.RWMutex
}

// NewCache creates a new TTL Cache using the a timeout
// for the default timeout of stored values and the extend
// value to determine if the cache lifetime of the set values
// should be extended upon read
func NewCache[K comparable, V any](ctx context.Context, timeout time.Duration, extend bool) Cache[K, V] {
func NewCache[K comparable, V any](
ctx context.Context,
timeout time.Duration,
extend bool,
) *Cache[K, V] {
if ctx == nil {
ctx = context.Background()
}

c := &cache[K, V]{
c := &Cache[K, V]{
ctx: ctx,
timeout: timeout,
extend: extend,
Expand All @@ -44,7 +57,7 @@ func NewCache[K comparable, V any](ctx context.Context, timeout time.Duration, e

// Delete removes the values associated with the
// passed key from the cache
func (c *cache[K, V]) Delete(ctx context.Context, key K) {
func (c *Cache[K, V]) Delete(ctx context.Context, key K) {
c.valuesMu.Lock()
defer c.valuesMu.Unlock()

Expand All @@ -59,7 +72,8 @@ func (c *cache[K, V]) Delete(ctx context.Context, key K) {
delete(c.values, key)
}

func (c *cache[K, V]) Get(ctx context.Context, key K) (V, bool) {
// Get accesses the value for the Key provide
func (c *Cache[K, V]) Get(ctx context.Context, key K) (V, bool) {
if c.values == nil {
return *new(V), false
}
Expand All @@ -85,15 +99,16 @@ func (c *cache[K, V]) Get(ctx context.Context, key K) (V, bool) {
}
}

func (c *cache[K, V]) Set(ctx context.Context, key K, value V) error {
// Set sets the value for the key provided
func (c *Cache[K, V]) Set(ctx context.Context, key K, value V) error {
return c.SetTTL(ctx, key, value, c.timeout)
}

// SetTTL allows for direct control over the TTL of a specific
// Key in the cache which is passed as timeout in parameter three.
// This timeout can be `nil` which will keep the value permanently
// in the cache without expiration until it's deleted
func (c *cache[K, V]) SetTTL(
func (c *Cache[K, V]) SetTTL(
ctx context.Context,
key K, value V,
timeout time.Duration,
Expand Down
Loading

0 comments on commit 8d146a1

Please sign in to comment.