Baton.ResultCache (Baton v0.27.4)

Copy Markdown View Source

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

clear()

@spec clear() :: :ok

Drop every entry (the eviction action, and a test/maintenance hook).

current_bytes()

@spec current_bytes() :: non_neg_integer()

Approximate total bytes currently held. 0 if the table doesn't exist.

enabled?()

@spec enabled?() :: boolean()

Whether the cache is enabled in config.

ensure_table()

@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.

fetch(sha)

@spec fetch(String.t()) :: {:ok, term()} | :miss

Look up a decoded term by its artifact sha256. :miss if absent.

put(sha, term, byte_size)

@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.