Documentation
¶
Overview ¶
Package kv provides the single low-level key-value storage primitive shared by fh's middleware packages (rate limiters, replay/nonce guards, response caches, reputation scores, allow-lists, admin registries, ...).
Middleware packages should depend on Store directly and layer their domain-specific operations (rate-limit Allow, replay Seen, cache Get/Set, and so on) as package functions over a Store parameter. The concrete mechanics of "keep this durably, keyed by an arbitrary string, with an optional TTL" — sharded locking, atomic file writes, key-to-filename hashing, directory permissions, background expiry sweeps — live here once, in MemoryStore and FileStore.
Index ¶
- Variables
- type FileOption
- type FileProvider
- type FileStore
- func (s *FileStore) Close() error
- func (s *FileStore) Delete(key string) error
- func (s *FileStore) GC()
- func (s *FileStore) Get(key string) ([]byte, bool, error)
- func (s *FileStore) Len() (int, error)
- func (s *FileStore) Mutate(key string, ...) error
- func (s *FileStore) Set(key string, value []byte, ttl time.Duration) error
- type MemoryOption
- type MemoryProvider
- type MemoryStore
- func (s *MemoryStore) Close() error
- func (s *MemoryStore) Delete(key string) error
- func (s *MemoryStore) Get(key string) ([]byte, bool, error)
- func (s *MemoryStore) Len() (int, error)
- func (s *MemoryStore) Mutate(key string, ...) error
- func (s *MemoryStore) Set(key string, value []byte, ttl time.Duration) error
- type Provider
- type Store
Constants ¶
This section is empty.
Variables ¶
var ( // ErrProviderClosed is returned when a namespace is requested after the // provider has been closed. ErrProviderClosed = errors.New("kv: provider is closed") // ErrInvalidNamespace is returned for an empty, malformed, or excessively // long state namespace. ErrInvalidNamespace = errors.New("kv: invalid namespace") )
var ErrCapacityExceeded = errors.New("kv: capacity exceeded")
ErrCapacityExceeded is returned by Set when a store enforcing a maximum entry count is full and cannot admit a new key.
var ErrClosed = errors.New("kv: store is closed")
ErrClosed is returned by any operation on a Store after Close has run.
var ErrTooLarge = errors.New("kv: value exceeds maximum entry size")
ErrTooLarge is returned when a value exceeds a store's configured maximum entry size.
Functions ¶
This section is empty.
Types ¶
type FileOption ¶
type FileOption func(*fileConfig)
FileOption configures a FileStore.
func WithFileGCInterval ¶
func WithFileGCInterval(d time.Duration) FileOption
WithFileGCInterval starts a background goroutine that sweeps expired entry files every interval. 0 (default) disables background sweeping.
func WithMaxEntrySize ¶
func WithMaxEntrySize(n int64) FileOption
WithMaxEntrySize overrides the default 8 MiB cap on a single stored value. Set attempts exceeding this return ErrTooLarge rather than partially persisting data.
type FileProvider ¶
type FileProvider struct {
// contains filtered or unexported fields
}
FileProvider supplies one durable FileStore per namespace beneath a common root directory. Namespace directory names are SHA-256 hashes, so untrusted namespace text can never become a filesystem path.
func NewFileProvider ¶
func NewFileProvider(root string, opts ...FileOption) (*FileProvider, error)
NewFileProvider creates a durable shared-state provider rooted at root.
func (*FileProvider) Close ¶
func (p *FileProvider) Close() error
Close closes every namespace. It is safe to call more than once.
type FileStore ¶
type FileStore struct {
// contains filtered or unexported fields
}
FileStore is a durable-across-restart implementation of Store. It persists one file per key under a root directory, named by the hex-encoded SHA-256 digest of the key — keys handed to a Store are arbitrary strings (IPs, tokens, cache keys, URLs) and must never be used to build a filesystem path directly, since they are not validated identifiers. Hashing closes that off entirely and keeps filenames a fixed, bounded length.
Writes are atomic: each Set writes to a temp file in the same directory, fsyncs it, renames it over the target path, then fsyncs the directory — a crash never leaves a partially-written entry. The store directory is created with 0700 and every entry file with 0600.
FileStore trades throughput for durability: every Set and every expiring Get does real disk I/O. Middleware packages backing a hot request path with FileStore should size call volume accordingly; MemoryStore remains the default for exactly this reason.
func NewFileStore ¶
func NewFileStore(dir string, opts ...FileOption) (*FileStore, error)
NewFileStore creates a FileStore rooted at dir, creating it (mode 0700) if necessary.
func (*FileStore) GC ¶
func (s *FileStore) GC()
GC removes all expired entry files. It is called automatically on WithFileGCInterval's schedule but may also be invoked directly.
type MemoryOption ¶
type MemoryOption func(*memoryConfig)
MemoryOption configures a MemoryStore.
func WithGCInterval ¶
func WithGCInterval(d time.Duration) MemoryOption
WithGCInterval starts a background goroutine that sweeps expired entries every interval. 0 (default) disables background sweeping — expired entries are still skipped lazily on Get and evicted opportunistically on Set.
func WithMaxEntries ¶
func WithMaxEntries(n int) MemoryOption
WithMaxEntries sets a soft cap on total stored entries, enforced by evicting expired-then-oldest entries within a shard when it is full. 0 (default) means unlimited.
func WithShardCount ¶
func WithShardCount(n int) MemoryOption
WithShardCount sets the number of internal shards (default 16). Higher values reduce lock contention under high concurrency at the cost of more memory overhead; only tune this after profiling.
type MemoryProvider ¶
type MemoryProvider struct {
// contains filtered or unexported fields
}
MemoryProvider supplies isolated in-process stores and owns their lifecycle. It is suitable for one process and for tests. It is not distributed or durable across restarts.
func NewMemoryProvider ¶
func NewMemoryProvider(opts ...MemoryOption) *MemoryProvider
NewMemoryProvider creates a provider that applies opts independently to every namespace it opens.
func (*MemoryProvider) Close ¶
func (p *MemoryProvider) Close() error
Close closes every namespace. It is safe to call more than once.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-process, sharded implementation of Store. It is the default backend for every middleware package that embeds a kv.Store — fast, non-durable, and safe for concurrent use across many goroutines via per-shard locking (mirroring the sharding scheme mw/ratelimiter's original hand-rolled MemoryStore used, generalized here for reuse).
func NewMemoryStore ¶
func NewMemoryStore(opts ...MemoryOption) *MemoryStore
NewMemoryStore creates a ready-to-use MemoryStore.
func (*MemoryStore) Close ¶
func (s *MemoryStore) Close() error
Close stops any background GC goroutine. Safe to call multiple times.
func (*MemoryStore) Delete ¶
func (s *MemoryStore) Delete(key string) error
func (*MemoryStore) Len ¶
func (s *MemoryStore) Len() (int, error)
type Provider ¶
Provider supplies isolated Store instances for application features.
A namespace identifies one logical state domain, for example "sessions/default", "ratelimit/public-api", or "replay/webhooks". Calls for the same namespace must return the same logical store. Different namespaces must not observe each other's keys and Len results.
The provider owns the stores it returns. Callers must close the Provider, rather than individual stores, after every feature using it has stopped. Implementations must be safe for concurrent use.
Redis, PostgreSQL, and other remote adapters should implement Provider as the configuration and lifecycle boundary and return Store implementations whose atomic Mutate operation is atomic across all participating processes.
type Store ¶
type Store interface {
// Get returns the value for key and true if present and not expired.
// A missing or expired key returns (nil, false, nil) — that is not an
// error condition.
Get(key string) ([]byte, bool, error)
// Set stores value for key, replacing any previous value, with the given
// time-to-live (<=0 means no expiry).
Set(key string, value []byte, ttl time.Duration) error
// Delete removes key. Deleting a missing key is not an error.
Delete(key string) error
// Len reports the number of live (non-expired, as of the last sweep for
// implementations that only expire lazily) entries.
Len() (int, error)
// Close releases resources such as background GC goroutines. A closed
// Store returns ErrClosed from every other method. Close is idempotent.
Close() error
// Mutate atomically loads the current value for key (nil, false if
// absent or expired) and passes it to fn. fn returns the next value and
// ttl to store, and ok=false to leave the entry untouched (e.g. a
// capacity check that rejects the mutation). Mutate holds the same lock
// Get/Set use for key for its entire duration, so callers implementing
// counters (rate limiters, window counts, reputation scores) get a
// race-free read-modify-write without needing their own locking or a
// separate compare-and-swap dance.
Mutate(key string, fn func(current []byte, exists bool) (next []byte, ttl time.Duration, ok bool, err error)) error
}
Store is a generic byte-oriented key-value store with optional per-entry TTL. Keys are arbitrary strings; implementations must not assume they are safe to use directly as filesystem paths (FileStore hashes them).
A zero ttl passed to Set means the entry never expires on its own (it can still be evicted under a capacity limit). All methods must be safe for concurrent use.