fcache

package module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 20, 2022 License: Apache-2.0 Imports: 12 Imported by: 0

README

fcache

coverage go-report-card reference

fcache is a file based persistent blob cache. It can be used to bring remote files closer to applications.

Features

  • Least recently used (LRU) eviction strategy
  • Optional time to live (TTL) per entry
  • Configurable size limit
  • Request coalescing, to atomically query and insert entries and avoid cache stampedes
  • Persistent: reloads cache state from disk after restart
  • Usage statistics
  • Bring your own key hashing algorithm (for example xxHash)

Installation

go get github.com/ydylla/fcache

Usage

To build a new cache use the Builder:

cache, err := fcache.Builder("/tmp/fcache", 10*fcache.GiB).Build()

The main functions are documented at the Cache interface.

How it Works

Each cache entry is saved in its own file. On a successful get query the cache responds with a io.ReadSeekCloser backed by an io.File. The caller is responsible for closing the file handle after he is done reading. On each insert the old file is removed and a new one is created. Eviction happens in background after each insert and only if the amount of time specified by the eviction interval has passed.

Limitations

  • This cache has only limited Windows support, since it assumes it is possible to delete files that are still open.
    On Windows that is not the case. When you hold the io.ReadSeekCloser from a get query open over a long period of time and do a Put or Delete on the same key you will receive an error.

  • If you save millions of small entries the actual storage usage will be higher than the configured limit, due to how most file systems work.

Examples

Simple example

This is a simple insert & query example.

package main

import (
	"errors"
	"fmt"
	"github.com/cespare/xxhash/v2"
	"github.com/ydylla/fcache"
	"io"
	"time"
)

func main() {
	// build a new cache
	cache, err := fcache.Builder("/tmp/fcache", 10*fcache.GiB).Build()
	if err != nil {
		fmt.Println("builder failed to initialize the cache:", err)
		return
	}

	// prepare test key and data
	key := xxhash.Sum64String("test")
	data := []byte("Hello World")

	// insert entry without expiration (ttl)
	info, err := cache.Put(key, data, 0)
	if err != nil {
		fmt.Println("insert failed:", err)
		return
	}

	fmt.Printf("Cache entry was modified at %s\n", info.Mtime.Format(time.RFC3339))

	// query the cache
	reader, info, err := cache.GetReader(key)
	if err != nil && !errors.Is(err, fcache.ErrNotFound) {
		fmt.Println("get failed for some reason:", err)
		return
	}
	defer reader.Close() // remember to close the reader

	buf, _ := io.ReadAll(reader)
	fmt.Printf("received '%s' form cache\n", buf)
}

Coalescing example

This example demonstrates the usage of GetReaderOrPut which is used to ensure that the cache fill operation is only executed once.

package main

import (
	"errors"
	"fmt"
	"github.com/cespare/xxhash/v2"
	"github.com/ydylla/fcache"
	"io"
	"net/http"
)

// download downloads an url at most once, even if called concurrently
func download(cache fcache.Cache, url string) (reader io.ReadSeekCloser, info *fcache.EntryInfo, hit bool, err error) {
	// calculate the key
	key := xxhash.Sum64String(url)
	// atomically insert & query
	return cache.GetReaderOrPut(key, 0, fcache.FillerFunc(func(key uint64, sink io.Writer) (written int64, err error) {
		fmt.Println("downloading", url)
		response, err := http.Get(url)
		if err != nil {
			return 0, err // abort insert
		}
		defer response.Body.Close()
		if response.StatusCode != 200 {
			return 0, errors.New(response.Status)
		}
		// copy response body into cache & report how many bytes where written
		return io.Copy(sink, response.Body)
	}))
}

func main() {
	// build a new cache
	cache, err := fcache.Builder("/tmp/fcache", 10*fcache.GiB).Build()
	if err != nil {
		fmt.Println("builder failed to initialize the cache:", err)
		return
	}

	url := "https://example.org"

	reader, _, hit, err := download(cache, url)
	if err != nil {
		fmt.Println("download http request or cache query failed:", err)
		return
	}
	defer reader.Close() // remember to close the reader

	buf, _ := io.ReadAll(reader)
	fmt.Println("loaded", url, "from cache:", hit)
	fmt.Println(string(buf[:63]))
}

Documentation

Overview

Package fcache implements a file based persistent blob cache.

Index

Constants

View Source
const (
	Byte Size = 1
	KB        = 1000 * Byte
	KiB       = 1024 * Byte
	MB        = 1000 * KB
	MiB       = 1024 * KiB
	GB        = 1000 * MB
	GiB       = 1024 * MiB
	TB        = 1000 * GB
	TiB       = 1024 * GiB
)

Variables

View Source
var ErrNotFound = errors.New("entry not found")

Functions

func Builder

func Builder(cacheDir string, targetSize Size) *builder

Builder configures & builds a new cache. cacheDir is the base directory of the cache. targetSize is target size of the cache. Depending on the eviction interval and insert load it may grow larger.

Types

type Cache

type Cache interface {
	// Stats returns statistics about the cache.
	// Like number of items, used bytes, number of hits and more.
	// It also returns a list of recent eviction errors if there are any.
	Stats() Stats

	// Has returns the EntryInfo of a cache entry or ErrNotFound if no entry for that key exists.
	Has(key uint64) (info *EntryInfo, err error)

	// Put inserts a byte slice under the provided key into the cache.
	// If an entry with the provided key already exists it gets overwritten.
	// Set the time to live (ttl) to 0 if the entry should not expire.
	Put(key uint64, data []byte, ttl time.Duration) (info *EntryInfo, err error)

	// PutReader inserts the content of reader under the provided key into the cache.
	// If an entry with the provided key already exists it gets overwritten.
	// Set the time to live (ttl) to 0 if the entry should not expire.
	PutReader(key uint64, reader io.Reader, ttl time.Duration) (info *EntryInfo, err error)

	// Get returns the content of the cache entry at key as byte slice.
	// If no cache entry exists for key ErrNotFound is returned.
	Get(key uint64) (data []byte, info *EntryInfo, err error)

	// GetReader returns the content of the cache entry at key as io.ReadSeekCloser.
	// If no cache entry exists for key ErrNotFound is returned.
	// Makes sure to call Close() on the reader when you are done reading.
	GetReader(key uint64) (reader io.ReadSeekCloser, info *EntryInfo, err error)

	// GetOrPut atomically gets or inserts a cache entry as byte slice.
	// If no entry exists at key the Filler is used to insert the entry before returning its content.
	// This functions locks the key for the duration of the insert, so all other calls to this key will block.
	// Using only GetOrPut or GetReaderOrPut guarantees that the insert only happens exactly once.
	GetOrPut(key uint64, ttl time.Duration, filler Filler) (data []byte, info *EntryInfo, hit bool, err error)

	// GetReaderOrPut atomically gets or inserts a cache entry as reader.
	// If no entry exists at key the Filler is used to insert the entry before returning its content.
	// This functions locks the key for the duration of the insert, so all other calls to this key will block.
	// Using only GetReaderOrPut or GetOrPut guarantees that the insert only happens exactly once.
	GetReaderOrPut(key uint64, ttl time.Duration, filler Filler) (reader io.ReadSeekCloser, info *EntryInfo, hit bool, err error)

	// Delete removes the cache entry at key.
	// If no cache entry exists at key nothing happens.
	Delete(key uint64) (info *EntryInfo, err error)

	// Clear removes all cache entries and optionally resets statistics.
	Clear(resetStats bool) error
}

Cache is safe for concurrent use by multiple goroutines.

type EntryInfo

type EntryInfo struct {
	Size    int64
	Mtime   time.Time
	Expires time.Time
}

type EvictionError

type EvictionError struct {
	Time  time.Time
	Error error
}

type Filler

type Filler interface {
	// WriteCacheData is used by GetOrPut and GetReaderOrPut to atomically insert cache entries.
	// Write the content for key into sink and return the number of written bytes.
	// Errors are passed through to GetOrPut or GetReaderOrPut.
	WriteCacheData(key uint64, sink io.Writer) (written int64, err error)
}

type FillerFunc

type FillerFunc func(key uint64, sink io.Writer) (written int64, err error)

FillerFunc is used by GetOrPut and GetReaderOrPut to atomically insert cache entries. Write the content for key into sink and return the number of written bytes. Errors are passed through to GetOrPut or GetReaderOrPut.

func (FillerFunc) WriteCacheData

func (f FillerFunc) WriteCacheData(key uint64, sink io.Writer) (written int64, err error)

type InitCallback added in v1.1.0

type InitCallback func(cache Cache, err error)

InitCallback is called after the cache finished restoring all entries from disk. cache is the Cache that was doing the init. err indicates if the init was successful or not.

type Locker added in v1.3.0

type Locker struct {
	// contains filtered or unexported fields
}

Locker provides a key based locking mechanism

func NewLocker added in v1.3.0

func NewLocker() *Locker

NewLocker creates a new Locker ready to be used

func (*Locker) Lock added in v1.3.0

func (l *Locker) Lock(key uint64)

Lock locks a mutex with the given key for writing. If no mutex for that key exists it is created.

func (*Locker) RLock added in v1.3.0

func (l *Locker) RLock(key uint64)

RLock locks a mutex with the given key for reading. If no mutex for that key exists it is created.

func (*Locker) RUnlock added in v1.3.0

func (l *Locker) RUnlock(key uint64)

RUnlock unlocks the mutex with the given key

func (*Locker) Unlock added in v1.3.0

func (l *Locker) Unlock(key uint64)

Unlock unlocks the mutex with the given key

type Size

type Size int64

type Stats

type Stats struct {
	Items          int
	Bytes          int64
	Has            int64
	Gets           int64
	Hits           int64
	Puts           int64
	Deletes        int64
	Evictions      int64
	EvictionErrors []EvictionError
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL