Fact.RecordCache (Fact v0.3.1)
View SourceAn in-memory LFU (Least Frequently Used) cache for decoded event records.
Since event records are immutable once written, they can be cached indefinitely without concern for invalidation. This cache avoids repeated disk reads and JSON deserialization for frequently accessed records.
The cache uses two ETS tables per database instance:
- A data table (
:set,:public,read_concurrency: true) for O(1) lookups from any process without going through the GenServer - A frequency table (
:ordered_set,:private) for LFU eviction, sorted by{frequency, record_id}
Cache reads bypass the GenServer entirely via direct ETS access. Frequency bumps and inserts are sent as casts to avoid blocking the caller.
To prevent stale entries from monopolizing the cache, a periodic frequency decay sweep halves all frequency counters at a configurable interval (default: 10 minutes). Entries whose frequency decays to zero are evicted. This ensures that records which were hot in the past but are no longer accessed gradually lose their position to actively read records.
The cache is disabled by default and only started when a :max_size is configured
via Fact.open/2:
{:ok, db} = Fact.open("data/my_database", cache: [max_size: 512 * 1024 * 1024])When no cache process is registered for a database, get/2 returns :miss and put/3
is a no-op, adding only a fast registry lookup to the read path.
This process is started and supervised by Fact.DatabaseSupervisor.
Summary
Types
Cache configuration options.
Options accepted by start_link/1.
Cache size information returned by size/1.
Functions
Returns a specification to start this module under a supervisor.
Removes all entries from the cache.
Returns the number of records currently in the cache.
Looks up a cached event record.
Caches a decoded event record.
Returns the current cache size, maximum capacity, and usage percentage.
Starts a Fact.RecordCache process linked to the calling process.
Returns the top n most frequently accessed cached records.
Types
@type cache_option() :: {:max_size, pos_integer()} | {:decay_interval, pos_integer()}
Cache configuration options.
:max_size- Maximum cache size in bytes. Required to enable the cache.:decay_interval- Time in milliseconds between frequency decay sweeps. Defaults to600_000(10 minutes). All frequency counters are halved on each sweep, preventing stale entries from monopolizing the cache indefinitely.
@type option() :: {:database_id, Fact.database_id()} | {:name, GenServer.name()} | {:max_size, pos_integer()} | {:decay_interval, pos_integer()}
Options accepted by start_link/1.
:database_id- (required) The database identifier.:name- (required) The registered process name, typically constructed viaFact.Registry.via/2.:max_size- (required) Maximum cache size in bytes.:decay_interval- Seecache_option/0.
@type size_info() :: %{ current: non_neg_integer(), max: pos_integer(), percentage: float() }
Cache size information returned by size/1.
:current- Current cache usage in bytes.:max- Maximum configured cache size in bytes.:percentage- Current usage as a percentage of max capacity (0.0to100.0).
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec clear(Fact.database_id()) :: :ok | {:error, :not_enabled}
Removes all entries from the cache.
Returns {:error, :not_enabled} when the cache is not active for the given database.
@spec count(Fact.database_id()) :: {:ok, non_neg_integer()} | {:error, :not_enabled}
Returns the number of records currently in the cache.
Performs a direct ETS read without going through the GenServer.
Returns {:error, :not_enabled} when the cache is not active for the given database.
@spec get(Fact.database_id(), Fact.record_id()) :: {:ok, Fact.record()} | :miss
Looks up a cached event record.
Performs a direct ETS read without going through the GenServer. On a cache hit, a frequency bump is cast to the GenServer asynchronously.
Returns {:ok, {record_id, event_map}} on hit, or :miss when the record is not cached
or the cache is disabled for this database.
@spec put(Fact.database_id(), Fact.record_id(), Fact.event_record()) :: :ok
Caches a decoded event record.
The insert is performed asynchronously via a cast to the GenServer. If the cache is disabled for this database, this is a no-op.
@spec size(Fact.database_id()) :: {:ok, size_info()} | {:error, :not_enabled}
Returns the current cache size, maximum capacity, and usage percentage.
Returns {:error, :not_enabled} when the cache is not active for the given database.
@spec start_link([option()]) :: GenServer.on_start()
Starts a Fact.RecordCache process linked to the calling process.
@spec top(Fact.database_id(), pos_integer()) :: {:ok, [{Fact.record_id(), non_neg_integer()}]} | {:error, :not_enabled}
Returns the top n most frequently accessed cached records.
Each entry is a {record_id, frequency} tuple, sorted by frequency in descending order.
Returns {:error, :not_enabled} when the cache is not active for the given database.