cache

package
v0.3.5 Latest Latest
Warning

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

Go to latest
Published: Mar 31, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

Documentation

Overview

Package cache provides BoltDB-backed caching with TTL expiration for cost query results.

This package implements persistent caching using bbolt (go.etcd.io/bbolt) to improve CLI performance by avoiding redundant plugin calls for recently-fetched data.

Storage

The cache is stored in a single BoltDB file (cache.db) within the project or global cache directory. BoltDB provides atomic transactions, indexed lookups, and reduced disk I/O compared to individual JSON files.

Bucket Layout

Data is organized into three top-level buckets:

  • projected: Per-resource projected cost results
  • actual: Whole-query actual cost results
  • recommendations: Recommendation query results

Key Format

Keys use human-readable slash-separated paths for easy debugging and prefix scanning:

  • projected/{provider}/{type}/{region}/{sku}
  • actual/{provider}/{types}/{from}/{to}/{filter-hash}
  • recommendations/multi/{sorted-types}

Concurrency

BoltDB supports concurrent reads via read-only transactions. Writes are serialized through DB.Batch() for automatic coalescing of concurrent write operations. The database file is protected by an OS-level file lock (flock/LockFileEx).

TTL Expiration

Entries are checked for expiration on read (lazy expiration). A startup cleanup pass removes expired entries in bulk. TTL is configurable via CLI flag (--cache-ttl), environment variable (FINFOCUS_CACHE_TTL), or config file.

Index

Constants

View Source
const (
	BucketProjected       = "projected"
	BucketActual          = "actual"
	BucketRecommendations = "recommendations"
)

Bucket names for the BoltDB cache.

View Source
const (
	DefaultTTLSeconds     = config.CacheDefaultTTLSeconds
	DefaultCacheMaxSizeMB = config.CacheDefaultMaxSizeMB
	EnvTTLSeconds         = config.CacheEnvTTLSeconds
	EnvTTLSecondsLegacy   = config.CacheEnvTTLSecondsLegacy
	EnvCacheEnabled       = config.CacheEnvEnabled
	EnvCacheDir           = config.CacheEnvDir
	EnvCacheMaxSize       = config.CacheEnvMaxSize
)

Re-exported constants from config for backward compatibility. The canonical definitions live in internal/config/cache_defaults.go to avoid a dependency inversion (config → engine/cache).

View Source
const (
	// MinTTLSeconds is the minimum allowed TTL (1 minute).
	MinTTLSeconds = 60

	// MaxTTLSeconds is the maximum allowed TTL (7 days).
	MaxTTLSeconds = 604800
)

Cache-internal constants.

Variables

View Source
var (
	ErrCacheNotFound   = errors.New("cache entry not found")
	ErrCacheExpired    = errors.New("cache entry expired")
	ErrInvalidCacheKey = errors.New("cache key cannot be empty")
	ErrInvalidCacheTTL = errors.New("cache TTL out of range")
	ErrCacheDisabled   = errors.New("cache is disabled")
	ErrCacheLocked     = errors.New("cache database locked by another process")
)

Common cache errors.

View Source
var (
	ErrInvalidTTL = fmt.Errorf("TTL must be between %d and %d seconds", MinTTLSeconds, MaxTTLSeconds)
)

TTL validation errors.

Functions

func BucketFromKey added in v0.3.0

func BucketFromKey(key string) string

BucketFromKey returns the leading bucket name from a cache key by taking the substring before the first '/'. If the key contains no '/', the entire key is returned.

func BuildActualKey added in v0.3.0

func BuildActualKey(provider string, resourceTypes []string, from, to time.Time, filters map[string]string) string

BuildActualKey constructs a key for whole-query actual cost caching. Format: actual/{provider}/{types}/{from}/{to}/{filter-hash} Empty segments use "_" as a placeholder to ensure fixed-position keys and avoid ambiguity (e.g., provider="aws" vs resourceTypes=["aws"]). The resulting key is safe for use as a top-level bucketed cache key.

func BuildProjectedKey added in v0.3.0

func BuildProjectedKey(provider, resourceType, region, sku string) string

BuildProjectedKey constructs a human-readable cache key for per-resource projected costs. The key has the form "projected/{provider}/{type}/{region}/{sku}". Any empty segment is replaced with "_" to preserve fixed segment positions.

func BuildRecommendationsKey added in v0.3.0

func BuildRecommendationsKey(resourceTypes []string) string

BuildRecommendationsKey constructs a cache key for recommendation results. The key has the format "recommendations/multi/{sorted-types-joined-by-+}". When resourceTypes is empty, the types segment uses "_" as a placeholder (consistent with sibling builders) to avoid a trailing slash.

func CalculatePluginTTL added in v0.3.4

func CalculatePluginTTL(expiresAt *time.Time, defaultTTL int) (int, bool, bool)

CalculatePluginTTL determines the cache TTL from a plugin's expires_at hint.

Returns:

  • ttlSeconds: The TTL to use (0 if skip is true)
  • skip: true if the result should not be cached (past expiration)
  • capped: true if the TTL was capped at MaxTTLSeconds

Behavior:

  • nil expiresAt → returns (defaultTTL, false, false)
  • past/current expiresAt → returns (0, true, false)
  • future expiresAt within MaxTTLSeconds → returns (remaining seconds, false, false)
  • future expiresAt exceeding MaxTTLSeconds → returns (MaxTTLSeconds, false, true)

func FormatDuration

func FormatDuration(d time.Duration) string

FormatDuration formats a duration in a human-readable way. Examples: "1h", "30m", "5m30s".

func GetCacheDirFromEnv

func GetCacheDirFromEnv() string

GetCacheDirFromEnv reads the cache directory from environment variable. Returns an empty string if not set (caller should use default).

func GetCacheEnabledFromEnv

func GetCacheEnabledFromEnv() bool

GetCacheEnabledFromEnv reads the cache enabled flag from environment variable. Returns true by default if the variable is not set.

func GetCacheMaxSizeFromEnv

func GetCacheMaxSizeFromEnv() int

GetCacheMaxSizeFromEnv reads the max cache size from environment variable. Returns DefaultCacheMaxSizeMB if not set or invalid.

func GetTTLFromEnv

func GetTTLFromEnv() int

GetTTLFromEnv reads the TTL from environment variable or returns the default. If the environment variable is invalid, returns the default and logs a warning.

func ParseTTL

func ParseTTL(s string) (int, error)

ParseTTL parses a TTL string in various formats: - Integer seconds: "3600". - Duration string: "1h", "30m", "1h30m".

func StripBucket added in v0.3.0

func StripBucket(key string) string

StripBucket returns the portion of key after the first "/" separator. If key contains no "/", the original key is returned unchanged.

Types

type BoltStore added in v0.3.0

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

BoltStore provides BoltDB-backed caching with TTL expiration. It stores cache entries as JSON values in named buckets. Thread-safe: uses bbolt's internal MVCC for concurrency.

func NewBoltStore added in v0.3.0

func NewBoltStore(ctx context.Context, directory string, enabled bool, ttlSeconds, maxSizeMB int) (*BoltStore, error)

NewBoltStore creates and returns a BoltStore backed by a BoltDB file located in the provided directory.

NewBoltStore will:

  • return a disabled BoltStore when `enabled` is false.
  • create the directory if it does not exist.
  • open or create the BoltDB file at "<directory>/cache.db"; if the database is locked by another process an error is returned, and if the file is detected as corrupted it will be deleted and recreated.
  • initialize the required top-level buckets.
  • run a startup cleanup of expired entries and perform a size check that may trigger compaction if the DB exceeds `maxSizeMB`.

Parameters:

  • ctx: context used to derive a logger.
  • directory: filesystem directory to contain the BoltDB file (must be non-empty).
  • enabled: if false, returns a disabled store without touching the filesystem.
  • ttlSeconds: default time-to-live for new cache entries, in seconds.
  • maxSizeMB: maximum database size in megabytes used to decide compaction.

Returns:

  • *BoltStore on success, or an error if the directory is invalid, directory creation fails, the database cannot be opened/created, or bucket initialization fails.

func (*BoltStore) CleanupExpired added in v0.3.0

func (s *BoltStore) CleanupExpired() (int, error)

CleanupExpired removes all expired entries across all buckets. Returns the number of entries removed.

func (*BoltStore) Clear added in v0.3.0

func (s *BoltStore) Clear() error

Clear removes all entries from all buckets.

func (*BoltStore) Close added in v0.3.0

func (s *BoltStore) Close() error

Close releases the database file handle and flushes pending writes. Safe to call multiple times; only the first call closes the database.

func (*BoltStore) Count added in v0.3.0

func (s *BoltStore) Count() (int, error)

Count returns the total number of entries across all buckets.

func (*BoltStore) Delete added in v0.3.0

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

Delete removes a single cache entry by exact key. Idempotent: no error if key doesn't exist.

func (*BoltStore) Get added in v0.3.0

func (s *BoltStore) Get(key string) (*CacheEntry, error)

Get retrieves a cache entry by key. Returns ErrCacheNotFound if the key does not exist. Returns ErrCacheExpired if the entry exists but has expired (lazily deleted). Returns ErrCacheDisabled if the store is disabled.

func (*BoltStore) GetDirectory added in v0.3.0

func (s *BoltStore) GetDirectory() string

GetDirectory returns the cache directory path.

func (*BoltStore) GetTTL added in v0.3.0

func (s *BoltStore) GetTTL() int

GetTTL returns the default TTL in seconds.

func (*BoltStore) InvalidateByPrefix added in v0.3.0

func (s *BoltStore) InvalidateByPrefix(prefix string) (int, error)

InvalidateByPrefix removes all cache entries whose keys start with the given prefix. Returns the count of entries removed. An empty prefix clears the entire cache.

func (*BoltStore) IsEnabled added in v0.3.0

func (s *BoltStore) IsEnabled() bool

IsEnabled returns true if caching is enabled.

func (*BoltStore) Set added in v0.3.0

func (s *BoltStore) Set(key string, data json.RawMessage) error

Set stores a cache entry with the given key and data using the store's default TTL. The key format determines which bucket the entry is stored in. Concurrent calls are batched for efficiency via db.Batch(). Returns ErrCacheDisabled if caching is disabled. Returns ErrInvalidCacheKey if key is empty.

func (*BoltStore) SetWithTTL added in v0.3.4

func (s *BoltStore) SetWithTTL(key string, data json.RawMessage, ttlSeconds int) error

SetWithTTL stores a cache entry with a caller-specified TTL instead of the store default. Used when a plugin provides an expires_at hint that should override the default TTL. Returns ErrCacheDisabled if caching is disabled. Returns ErrInvalidCacheKey if key is empty.

func (*BoltStore) Size added in v0.3.0

func (s *BoltStore) Size() (int64, error)

Size returns the current database file size in bytes.

type Cache added in v0.3.0

type Cache interface {
	Get(key string) (*CacheEntry, error)
	Set(key string, data json.RawMessage) error
	SetWithTTL(key string, data json.RawMessage, ttlSeconds int) error
	IsEnabled() bool
	Close() error
	InvalidateByPrefix(prefix string) (int, error)
}

Cache defines the interface for cache operations used by the engine. All implementations must be safe for concurrent use by multiple goroutines.

type CacheEntry

type CacheEntry struct {
	// Key is the cache key (structured, human-readable, `/`-separated).
	Key string `json:"key"`

	// Data is the cached value (JSON-serializable).
	Data json.RawMessage `json:"data"`

	// CreatedAt is the timestamp when the entry was created.
	CreatedAt time.Time `json:"created_at"`

	// ExpiresAt is the timestamp when the entry expires.
	ExpiresAt time.Time `json:"expires_at"`

	// TTLSeconds is the time-to-live in seconds (for reference).
	TTLSeconds int `json:"ttl_seconds"`
}

CacheEntry represents a single cached value with TTL metadata. It wraps arbitrary JSON-serializable data with expiration information.

func NewCacheEntry

func NewCacheEntry(key string, data json.RawMessage, ttlSeconds int) *CacheEntry

NewCacheEntry creates a new cache entry with the given TTL. The entry is created with the current time and calculates expiration based on TTL.

func (*CacheEntry) Age

func (e *CacheEntry) Age() time.Duration

Age returns the duration since the entry was created.

func (*CacheEntry) IsExpired

func (e *CacheEntry) IsExpired() bool

IsExpired checks if the cache entry has expired based on current time. Returns true if the current time is after the expiration time.

func (*CacheEntry) IsValid

func (e *CacheEntry) IsValid() bool

IsValid checks if the cache entry is valid (not expired). This is the inverse of IsExpired() and is provided for readability.

func (*CacheEntry) MarshalJSON

func (e *CacheEntry) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler for CacheEntry. Times are stored as Unix timestamps (int64) for bbolt storage efficiency.

func (*CacheEntry) TimeUntilExpiration

func (e *CacheEntry) TimeUntilExpiration() time.Duration

TimeUntilExpiration returns the duration until the entry expires. Returns 0 if already expired.

func (*CacheEntry) Touch

func (e *CacheEntry) Touch()

Touch updates the entry's expiration time by extending it by the original TTL. This is useful for implementing "refresh on access" caching strategies.

func (*CacheEntry) UnmarshalJSON

func (e *CacheEntry) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler for CacheEntry. Parses Unix timestamps from stored data.

type TTLConfig

type TTLConfig struct {
	// Seconds is the TTL duration in seconds.
	Seconds int

	// Duration is the TTL as a time.Duration.
	Duration time.Duration
}

TTLConfig holds cache TTL configuration with validation.

func DefaultTTLConfig

func DefaultTTLConfig() *TTLConfig

DefaultTTLConfig returns the default TTL configuration.

func NewTTLConfig

func NewTTLConfig(seconds int) (*TTLConfig, error)

NewTTLConfig creates a TTL configuration with validation. Returns an error if the TTL is outside the valid range.

Jump to

Keyboard shortcuts

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