The claim gateway's memory of what it has read: pack indexes, pack digests and whole claim bodies, so a repeat read never reaches the object store.
What is cached
- a pack — the store and key it was found under, and its first bytes
(
Ankusa.ClaimCheck.Pack's index), with how many bytes were asked for (read), so a short prefix (a small pack) is told apart from one that was never asked for that far. Every later claim of the pack is located without a store read. Once the gateway has checked a claim of the pack (claim_check.integrity_check: :manifest) the entry also holds the sha256 of every claim in it, 32 bytes each, taken from the pack's manifest: the rest of the pack's claims are checked without a store read. Kept 10 minutes. - a claim's whole bytes, when they are at most
claim_check.read_cache_max_entry_bytes; larger claims are served and never inserted, and ranged reads are never cached. Kept 2 minutes.
Packs and claims are written once and never rewritten, so an entry is never
wrong while its pack exists. The only staleness is a pack swept by
retention: a cached index answers HEAD 200 for up to 10 minutes after
the sweep, a cached claim is served for up to 2, and a GET whose bytes
read answers :not_found forgets the pack (Ankusa.ClaimCheck.read_bytes/5).
Memory
claim_check.read_cache_bytes bounds what is held, counted as each entry's
bytes plus a fixed per-entry overhead. The entries live in two generations,
each an ETS table: inserts go to the newer one, and when it holds half the
budget it becomes the older one and the previous older one is dropped whole.
A hit in the older generation is copied into the newer one (and counted
again), so an entry that keeps being read outlives the generation it was
written in, up to its own TTL. Every 10 minutes the generations turn over
regardless, so an idle cache empties within 20.
An insert reserves its bytes before it lands, and one that finds the newer generation already full is dropped (the next read goes to the store), so a generation never holds more than half the budget plus the one entry that filled it; an entry larger than half the budget on its own is never inserted. The insert that fills a generation asks this process for the turnover, and this process also checks every second, in case that insert's caller died first; inserts dropped until the turnover is handled are what keeps the bound while it is pending.
Ankusa.Routes.Cache uses Nebulex; this cache can't. Nebulex bounds memory
by :ets.info(tab, :memory), which counts a binary longer than 64 bytes as
the few words of its reference, never its bytes (a thousand cached 50 KiB
claims measure about 140 KB), and every claim and pack index here is such a
binary.
Instance-scoped: this process registers under
Ankusa.via(instance, :claim_cache) and owns the tables. When it isn't
running (no :claim_check role, or read_cache_bytes: 0) every lookup
misses and every insert is dropped.
Summary
Types
A located pack: the claim store it was found in, its key there, its first
bytes, and how many bytes were asked for (prefix is shorter only when the
object is); digests once its manifest has been read
(Ankusa.ClaimCheck.Pack.digests/3).
Functions
The cached whole bytes of the claim at index of pack pack_id, if any.
Returns a specification to start this module under a supervisor.
The sha256 of every claim of a cached pack (Ankusa.ClaimCheck.Pack.digests/3), if known.
Forget a pack (one that turned out to be gone), if it was cached.
The cached pack pack_id of tenant_id, if any.
Cache a claim's whole bytes, or do nothing if the cache isn't running. The
caller keeps them under claim_check.read_cache_max_entry_bytes.
Add a pack's digests to its cached entry, counted against the same budget as the entry itself. Nothing happens when the pack is not cached (the next read of it reads the index again, and its manifest after it).
Cache a located pack, or do nothing if the cache isn't running.
Types
@type pack() :: %{ :store => non_neg_integer(), :key => String.t(), :prefix => binary(), :read => pos_integer(), optional(:digests) => binary() }
A located pack: the claim store it was found in, its key there, its first
bytes, and how many bytes were asked for (prefix is shorter only when the
object is); digests once its manifest has been read
(Ankusa.ClaimCheck.Pack.digests/3).
Functions
@spec body(atom(), String.t(), String.t(), non_neg_integer()) :: {:ok, binary()} | :error
The cached whole bytes of the claim at index of pack pack_id, if any.
@spec child_spec(keyword()) :: Supervisor.child_spec()
Returns a specification to start this module under a supervisor.
See Supervisor.
The sha256 of every claim of a cached pack (Ankusa.ClaimCheck.Pack.digests/3), if known.
Forget a pack (one that turned out to be gone), if it was cached.
The cached pack pack_id of tenant_id, if any.
@spec put_body(atom(), String.t(), String.t(), non_neg_integer(), binary()) :: :ok
Cache a claim's whole bytes, or do nothing if the cache isn't running. The
caller keeps them under claim_check.read_cache_max_entry_bytes.
Add a pack's digests to its cached entry, counted against the same budget as the entry itself. Nothing happens when the pack is not cached (the next read of it reads the index again, and its manifest after it).
Cache a located pack, or do nothing if the cache isn't running.
@spec start_link(keyword()) :: GenServer.on_start()