Optional, node-local read cache for resolved (decoded) large step results.
When Baton.ResultStore reads a spilled artifact, it gunzips and JSON-decodes
the payload. Fan-in and multi-model synthesis steps frequently read the same
large upstream result more than once; this cache lets them skip the backend
round-trip and the decode on subsequent reads.
The cache is a pure performance layer — the backend remains the source of
truth — so a miss, an eviction, or a dropped table only costs a cold read,
never correctness. It is off by default; enable it with
config :baton, :result_cache_enabled, true.
Keyed by content hash
Entries are keyed by the artifact's sha256, not its storage key. A retried
step overwrites the same storage key with potentially different bytes (the
Postgres backend upserts on key), so a key-addressed entry could go stale. The
sha is content-addressed: different bytes produce a different key and miss the
old entry automatically.
Eviction
A single byte budget (Baton.Config.max_cache_bytes/0) bounds the table. When
an insert would exceed it, the whole table is flushed and rebuilt cold —
deliberately the simplest correct policy. Because the cache is safe to drop,
this trades a periodic cold spell for zero eviction bookkeeping; a smarter
policy (e.g. sampled LRU) can replace clear/0-on-overflow later without
touching callers.
Storage
An owner-less, named, public ETS table created lazily on first write (Baton
has no supervision tree of its own; the optional Baton.Plugin merely warms
it at startup). Data rows are {sha256, term, byte_size}; a reserved
{:__bytes__, n} row tracks the approximate total.
Summary
Functions
Drop every entry (the eviction action, and a test/maintenance hook).
Approximate total bytes currently held. 0 if the table doesn't exist.
Whether the cache is enabled in config.
Create the ETS table if it does not exist yet. Race-safe: a concurrent creator
losing the race is treated as success. Called lazily by put/3 and eagerly by
Baton.Plugin at startup.
Look up a decoded term by its artifact sha256. :miss if absent.
Cache a decoded term under its sha256, sized by byte_size (the stored
compressed size, taken from the reference envelope). A term larger than the
whole budget is not cached; an insert that would overflow the budget flushes
the table first.
Functions
@spec clear() :: :ok
Drop every entry (the eviction action, and a test/maintenance hook).
@spec current_bytes() :: non_neg_integer()
Approximate total bytes currently held. 0 if the table doesn't exist.
@spec enabled?() :: boolean()
Whether the cache is enabled in config.
@spec ensure_table() :: :ok
Create the ETS table if it does not exist yet. Race-safe: a concurrent creator
losing the race is treated as success. Called lazily by put/3 and eagerly by
Baton.Plugin at startup.
Look up a decoded term by its artifact sha256. :miss if absent.
@spec put(String.t(), term(), non_neg_integer()) :: :ok
Cache a decoded term under its sha256, sized by byte_size (the stored
compressed size, taken from the reference envelope). A term larger than the
whole budget is not cached; an insert that would overflow the budget flushes
the table first.