blob: 21878959109af8577e26cd8f13e8dd36e79c60ff [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dscache
import (
"context"
"time"
)
const (
// MutationLockTimeout is expiration time of a "lock" memcache entry that
// protects mutations (Put/Delete/Commit). It should be larger than the
// maximum expected duration of datastore mutating operations. Must have
// seconds precision.
MutationLockTimeout = 120 * time.Second
// RefreshLockTimeout is expiration time of a "lock" memcache entry that
// protects the cache refresh process (during Get). It should be larger than
// expected Get duration, but it's not a big deal if the lock expires sooner.
// Must have seconds precision.
RefreshLockTimeout = 20 * time.Second
// CacheDuration is the default duration that a cached entity will be retained
// (memcache contention notwithstanding). Must have seconds precision.
CacheDuration = time.Hour * 24
// CompressionThreshold is the number of bytes of entity value after which
// compression kicks in.
CompressionThreshold = 860
// DefaultShards is the default number of key sharding to do.
DefaultShards = 1
// MaxShards is the maximum number of shards a single entity can have.
MaxShards = 256
// MemcacheVersion will be incremented in the event that the in-memcache
// representation of the cache data is modified.
MemcacheVersion = "1"
// KeyFormat is the format string used to generate memcache keys. It's
// gae:<version>:<shard#>:<base64_std_nopad(sha1(datastore.Key))>
KeyFormat = "gae:" + MemcacheVersion + ":%x:%s"
// ValueSizeLimit is the maximum encoded size a datastore key+entry may
// occupy. If a datastore entity is too large, it will have an indefinite
// lock which will cause all clients to fetch it from the datastore.
//
// See https://cloud.google.com/appengine/docs/go/memcache/#Go_Limits
// 80 is approximately the internal GAE padding. 36 is maximum length of our
// keys.
ValueSizeLimit = (1024 * 1024) - 80 - 36
// CacheEnableMeta is the gae metadata key name for whether or not dscache
// is enabled for an entity type at all.
CacheEnableMeta = "dscache.enable"
// CacheExpirationMeta is the gae metadata key name for the default
// expiration time (in seconds) for an entity type.
CacheExpirationMeta = "dscache.expiration"
// NonceBytes is the number of bytes to use in the 'lock' nonce.
NonceBytes = 8
)
// CacheItem represents either a cached datastore entity or a placeholder lock
// that "promises" that such entity is being fetched now (either by us or by
// someone else).
//
// CacheItem is created by TryLockAndFetch. An item that represents a lock
// can be "promoted" into either a data item or a permanent lock. Such promoted
// items are stored by CompareAndSwap.
type CacheItem interface {
// Key is the item's key as passed to TryLockAndFetch.
Key() string
// Nonce returns nil for data items or a lock nonce for lock items.
Nonce() []byte
// Data returns nil for lock items or an item's data for data items.
Data() []byte
// PromoteToData converts this lock item into a data item.
//
// Panics if self is not a lock item.
PromoteToData(data []byte, exp time.Duration)
// PromoteToIndefiniteLock converts this lock into an indefinite lock.
//
// An indefinite lock means that the datastore item is not cacheable for some
// reasons and 'Get' should not try to cache it. Such locks are removed by
// PutLocks/DropLocks.
//
// Panics if self is not a lock item.
PromoteToIndefiniteLock()
}
// Cache abstracts a particular memcache implementation.
//
// This interface is tightly coupled to the dscache algorithm (rather than
// trying to emulate a generic cache API) to allow the implementation to be as
// efficient as possible.
type Cache interface {
// PutLocks is called before mutating entities during Put/Delete/Commit.
//
// `keys` represent CacheItem keys of all shards of all to-be-mutated
// entities. The implementation should unconditionally write locks into all
// these keys keys.
//
// Errors are treated as fatal.
PutLocks(ctx context.Context, keys []string, timeout time.Duration) error
// DropLocks is called after finishing Put/Delete/Commit.
//
// The implementation should unconditionally remove these keys, thus unlocking
// them (if they were locked).
//
// Errors are logged, but ignored.
DropLocks(ctx context.Context, keys []string) error
// TryLockAndFetch is called before executing Get.
//
// Each key is either empty, or contains some random shard of a to-be-fetched
// entity (one such key per entity). For each non-empty key, if it doesn't
// exist yet, the implementation should try to write a lock with the nonce.
// It then should fetch all keys (whatever they might be).
//
// Should always return len(keys) items, even on errors. Items matching empty
// keys should be nil. Items that do not exist in the cache should also be
// represented by nils.
//
// Errors are logged, but ignored (i.e. treated as cache misses).
TryLockAndFetch(ctx context.Context, keys []string, nonce []byte, timeout time.Duration) ([]CacheItem, error)
// CompareAndSwap stores promoted items (see CacheItem) in place of locks
// they formerly represented iff the cache still has the same locks there.
//
// Errors are logged, but ignored.
CompareAndSwap(ctx context.Context, items []CacheItem) error
}