kv

package
v0.0.26 Latest Latest
Warning

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

Go to latest
Published: Sep 24, 2026 License: MIT Imports: 14 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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")
)
View Source
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.

View Source
var ErrClosed = errors.New("kv: store is closed")

ErrClosed is returned by any operation on a Store after Close has run.

View Source
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.

func (*FileProvider) Store

func (p *FileProvider) Store(ctx context.Context, namespace string) (Store, error)

Store returns the isolated durable store for namespace.

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) Close

func (s *FileStore) Close() error

Close stops any background GC goroutine. Safe to call multiple times.

func (*FileStore) Delete

func (s *FileStore) Delete(key string) error

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.

func (*FileStore) Get

func (s *FileStore) Get(key string) ([]byte, bool, error)

func (*FileStore) Len

func (s *FileStore) Len() (int, error)

func (*FileStore) Mutate

func (s *FileStore) Mutate(key string, fn func(current []byte, exists bool) (next []byte, ttl time.Duration, ok bool, err error)) error

func (*FileStore) Set

func (s *FileStore) Set(key string, value []byte, ttl time.Duration) error

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.

func (*MemoryProvider) Store

func (p *MemoryProvider) Store(ctx context.Context, namespace string) (Store, error)

Store returns the isolated in-memory store for namespace.

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) Get

func (s *MemoryStore) Get(key string) ([]byte, bool, error)

func (*MemoryStore) Len

func (s *MemoryStore) Len() (int, error)

func (*MemoryStore) Mutate

func (s *MemoryStore) Mutate(key string, fn func(current []byte, exists bool) (next []byte, ttl time.Duration, ok bool, err error)) error

func (*MemoryStore) Set

func (s *MemoryStore) Set(key string, value []byte, ttl time.Duration) error

type Provider

type Provider interface {
	Store(context.Context, string) (Store, error)
	Close() error
}

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.

func MustStore

func MustStore(provider Provider, namespace string) Store

MustStore obtains a namespaced store or panics. It is intended for application initialization where constructor failure is fatal. Libraries should call Provider.Store and return the error instead.

Jump to

Keyboard shortcuts

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