161 lines
4.1 KiB
Go
161 lines
4.1 KiB
Go
package fastcache
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
xxhash "github.com/cespare/xxhash/v2"
|
|
)
|
|
|
|
// maxSubvalueLen is the maximum size of subvalue chunk.
|
|
//
|
|
// - 16 bytes are for subkey encoding
|
|
// - 4 bytes are for len(key)+len(value) encoding inside fastcache
|
|
// - 1 byte is implementation detail of fastcache
|
|
const maxSubvalueLen = chunkSize - 16 - 4 - 1
|
|
|
|
// maxKeyLen is the maximum size of key.
|
|
//
|
|
// - 16 bytes are for (hash + valueLen)
|
|
// - 4 bytes are for len(key)+len(subkey)
|
|
// - 1 byte is implementation detail of fastcache
|
|
const maxKeyLen = chunkSize - 16 - 4 - 1
|
|
|
|
// SetBig sets (k, v) to c where len(v) may exceed 64KB.
|
|
//
|
|
// GetBig must be used for reading stored values.
|
|
//
|
|
// The stored entry may be evicted at any time either due to cache
|
|
// overflow or due to unlikely hash collision.
|
|
// Pass higher maxBytes value to New if the added items disappear
|
|
// frequently.
|
|
//
|
|
// It is safe to store entries smaller than 64KB with SetBig.
|
|
//
|
|
// k and v contents may be modified after returning from SetBig.
|
|
func (c *Cache) SetBig(k, v []byte) {
|
|
atomic.AddUint64(&c.bigStats.SetBigCalls, 1)
|
|
if len(k) > maxKeyLen {
|
|
atomic.AddUint64(&c.bigStats.TooBigKeyErrors, 1)
|
|
return
|
|
}
|
|
valueLen := len(v)
|
|
valueHash := xxhash.Sum64(v)
|
|
|
|
// Split v into chunks with up to 64Kb each.
|
|
subkey := getSubkeyBuf()
|
|
var i uint64
|
|
for len(v) > 0 {
|
|
subkey.B = marshalUint64(subkey.B[:0], valueHash)
|
|
subkey.B = marshalUint64(subkey.B, uint64(i))
|
|
i++
|
|
subvalueLen := maxSubvalueLen
|
|
if len(v) < subvalueLen {
|
|
subvalueLen = len(v)
|
|
}
|
|
subvalue := v[:subvalueLen]
|
|
v = v[subvalueLen:]
|
|
c.Set(subkey.B, subvalue)
|
|
}
|
|
|
|
// Write metavalue, which consists of valueHash and valueLen.
|
|
subkey.B = marshalUint64(subkey.B[:0], valueHash)
|
|
subkey.B = marshalUint64(subkey.B, uint64(valueLen))
|
|
c.Set(k, subkey.B)
|
|
putSubkeyBuf(subkey)
|
|
}
|
|
|
|
// GetBig searches for the value for the given k, appends it to dst
|
|
// and returns the result.
|
|
//
|
|
// GetBig returns only values stored via SetBig. It doesn't work
|
|
// with values stored via other methods.
|
|
//
|
|
// k contents may be modified after returning from GetBig.
|
|
func (c *Cache) GetBig(dst, k []byte) (r []byte) {
|
|
atomic.AddUint64(&c.bigStats.GetBigCalls, 1)
|
|
subkey := getSubkeyBuf()
|
|
dstWasNil := dst == nil
|
|
defer func() {
|
|
putSubkeyBuf(subkey)
|
|
if len(r) == 0 && dstWasNil {
|
|
// Guarantee that if the caller provided nil and this is a cache miss that
|
|
// the caller can accurately test for a cache miss with `if r == nil`.
|
|
r = nil
|
|
}
|
|
}()
|
|
|
|
// Read and parse metavalue
|
|
subkey.B = c.Get(subkey.B[:0], k)
|
|
if len(subkey.B) == 0 {
|
|
// Nothing found.
|
|
return dst
|
|
}
|
|
if len(subkey.B) != 16 {
|
|
atomic.AddUint64(&c.bigStats.InvalidMetavalueErrors, 1)
|
|
return dst
|
|
}
|
|
valueHash := unmarshalUint64(subkey.B)
|
|
valueLen := unmarshalUint64(subkey.B[8:])
|
|
|
|
// Collect result from chunks.
|
|
dstLen := len(dst)
|
|
if n := dstLen + int(valueLen) - cap(dst); n > 0 {
|
|
dst = append(dst[:cap(dst)], make([]byte, n)...)
|
|
}
|
|
dst = dst[:dstLen]
|
|
var i uint64
|
|
for uint64(len(dst)-dstLen) < valueLen {
|
|
subkey.B = marshalUint64(subkey.B[:0], valueHash)
|
|
subkey.B = marshalUint64(subkey.B, uint64(i))
|
|
i++
|
|
dstNew := c.Get(dst, subkey.B)
|
|
if len(dstNew) == len(dst) {
|
|
// Cannot find subvalue
|
|
return dst[:dstLen]
|
|
}
|
|
dst = dstNew
|
|
}
|
|
|
|
// Verify the obtained value.
|
|
v := dst[dstLen:]
|
|
if uint64(len(v)) != valueLen {
|
|
atomic.AddUint64(&c.bigStats.InvalidValueLenErrors, 1)
|
|
return dst[:dstLen]
|
|
}
|
|
h := xxhash.Sum64(v)
|
|
if h != valueHash {
|
|
atomic.AddUint64(&c.bigStats.InvalidValueHashErrors, 1)
|
|
return dst[:dstLen]
|
|
}
|
|
return dst
|
|
}
|
|
|
|
func getSubkeyBuf() *bytesBuf {
|
|
v := subkeyPool.Get()
|
|
if v == nil {
|
|
return &bytesBuf{}
|
|
}
|
|
return v.(*bytesBuf)
|
|
}
|
|
|
|
func putSubkeyBuf(bb *bytesBuf) {
|
|
bb.B = bb.B[:0]
|
|
subkeyPool.Put(bb)
|
|
}
|
|
|
|
var subkeyPool sync.Pool
|
|
|
|
type bytesBuf struct {
|
|
B []byte
|
|
}
|
|
|
|
func marshalUint64(dst []byte, u uint64) []byte {
|
|
return append(dst, byte(u>>56), byte(u>>48), byte(u>>40), byte(u>>32), byte(u>>24), byte(u>>16), byte(u>>8), byte(u))
|
|
}
|
|
|
|
func unmarshalUint64(src []byte) uint64 {
|
|
_ = src[7]
|
|
return uint64(src[0])<<56 | uint64(src[1])<<48 | uint64(src[2])<<40 | uint64(src[3])<<32 | uint64(src[4])<<24 | uint64(src[5])<<16 | uint64(src[6])<<8 | uint64(src[7])
|
|
}
|