What this process is holding beside the stores — one register every derived, droppable structure declares itself in, and one read over the lot.
The stores are measured elsewhere: catalog/heap reports the JVM's own figure and
catalog/footprint estimates what a loaded KB costs. Neither says anything about the
caches — the atoms and plain maps holding answers the engine would otherwise
recompute — and a hit rate is the only evidence a cost model has. "The
second query was fast" is a demo; "the second query was fast because it was served
from a cache, and here is the rate" is a measurement.
A register rather than a dozen accessors. This namespace requires only config and
the logger, neither of which holds a cache, so the reader still has no require edge down
to a namespace holding one: every such namespace requires this one and declares itself at load, and
there is no list here that a new cache has to be added to twice. The config edge reads
one switch, VAELII_CACHE_SCALE, and limit-of applies it to every count-bounded
cache's limit. A cache in a namespace this
process never loaded — a qualitative calculus nobody registered, the metric-time
reasoner — is absent from the read because it is absent from the process, rather than
present as a row of zeroes.
Two scopes, and never one wearing the other's clothes. :scope says what a row's
:entries counts: :kb for a cache hanging off a KB record, :process for a static
one every KB in the JVM shares. :counters says the same about :hits / :misses,
separately: the closure neighbours keep process counters over entries only a live
search step can count, and a row counted by a derived-state tally counts per structure.
:unit is not decoration. One cache counts literals, another counts networks, a
third counts symbols, and a column of bare integers compares none of them.
A row whose :entries is nil is one that cannot be counted from outside — the
scope-bound caches, bound for the length of one chaining run or one search step and
garbage when it returns. They are registered all the same, with the reason in
:note, so the list is complete rather than merely finite.
A row answers for itself, and fails for itself. The register is open, so a read
here runs code this namespace has never seen; one that throws is reported as a row
carrying :error rather than allowed to take the answer down with it. A diagnostic
is worth most while something is already wrong, which is exactly when it must not be
the next thing to break.
What this process is holding beside the stores — one register every derived, droppable structure declares itself in, and one read over the lot. The stores are measured elsewhere: `catalog/heap` reports the JVM's own figure and `catalog/footprint` estimates what a loaded KB costs. Neither says anything about the **caches** — the atoms and plain maps holding answers the engine would otherwise recompute — and a hit rate is the only evidence a cost model has. "The second query was fast" is a demo; "the second query was fast because it was served from a cache, and here is the rate" is a measurement. **A register rather than a dozen accessors.** This namespace requires only `config` and the logger, neither of which holds a cache, so the reader still has no require edge down to a namespace holding one: every such namespace requires *this* one and declares itself at load, and there is no list here that a new cache has to be added to twice. The `config` edge reads one switch, `VAELII_CACHE_SCALE`, and `limit-of` applies it to every count-bounded cache's limit. A cache in a namespace this process never loaded — a qualitative calculus nobody registered, the metric-time reasoner — is absent from the read because it is absent from the process, rather than present as a row of zeroes. **Two scopes, and never one wearing the other's clothes.** `:scope` says what a row's `:entries` counts: `:kb` for a cache hanging off a KB record, `:process` for a static one every KB in the JVM shares. `:counters` says the same about `:hits` / `:misses`, separately: the closure neighbours keep process counters over entries only a live search step can count, and a row counted by a derived-state tally counts per structure. **`:unit` is not decoration.** One cache counts literals, another counts networks, a third counts symbols, and a column of bare integers compares none of them. A row whose `:entries` is nil is one that cannot be counted from outside — the scope-bound caches, bound for the length of one chaining run or one search step and garbage when it returns. They are registered all the same, with the reason in `:note`, so the list is complete rather than merely finite. **A row answers for itself, and fails for itself.** The register is open, so a read here runs code this namespace has never seen; one that throws is reported as a row carrying `:error` rather than allowed to take the answer down with it. A diagnostic is worth most while something is already wrong, which is exactly when it must not be the next thing to break.
(assoc-bounded m limit k v)Store v at k in map m, clearing m wholesale when it already holds limit
entries.
The bound policy, in one place rather than spelled out at each cache. Wholesale
clearing rather than eviction is literal-cache/cache-limit's argument and
observe/resident-limit's before it: evicting exactly the right entry costs more
bookkeeping than the entry saved, and a cache that has grown past its bound is one
whose queries have moved on. That is a judgement about every cache here at once, so
it is worth being able to revisit in one edit rather than six.
Store `v` at `k` in map `m`, **clearing `m` wholesale** when it already holds `limit` entries. The bound policy, in one place rather than spelled out at each cache. Wholesale clearing rather than eviction is `literal-cache/cache-limit`'s argument and `observe/resident-limit`'s before it: evicting exactly the right entry costs more bookkeeping than the entry saved, and a cache that has grown past its bound is one whose queries have moved on. That is a judgement about every cache here at once, so it is worth being able to revisit in one edit rather than six.
(clear-caches kb)(clear-caches kb {:keys [counters?]})Drop every cache that offers a clear, and say what went: {:cleared [{:cache :label :entries} …] :entries total}, ranked like rows.
Not !, and the reason is the whole point of the control: every entry is derived, the
next read recomputes it, and no belief moves. That makes a clear a measuring
instrument rather than an edit — clear, ask the same question again, and watch the
miss the second ask no longer gets to skip.
Scoped to kb, because the argument says so. Every :clear drops that cache's
entries for this KB and nothing else. The hit and miss counters some caches keep are
process-wide — they measure the mechanism rather than a store — and zeroing one would
reset a rate every other KB in the JVM is reporting, mid-measurement. So it is not
done here: {:counters? true} asks for it, in a call that says out loud it is reaching
past its argument, and the answer then carries :counters-reset naming the caches it
touched. A function whose signature names one KB must not quietly be a per-process
control; caches' :counters column is how a caller knows which rows the option is
about. The same option zeroes kb's derived-state tallies (row-tally), and the
answer carries :tallies-reset, [{:row id …what it held}].
A cache with no :clear is left alone and is not in the answer. Those are the
structural ones — the symbol pool, the compiled relation algebras — where dropping the
entries costs the sharing they exist for and buys no measurement.
A clear that throws costs its own entry and no other, the way a read does: its row
carries :error and an entry count of zero.
Drop every cache that offers a clear, and say what went: `{:cleared [{:cache :label
:entries} …] :entries total}`, ranked like `rows`.
Not `!`, and the reason is the whole point of the control: every entry is derived, the
next read recomputes it, and no belief moves. That makes a clear a measuring
instrument rather than an edit — clear, ask the same question again, and watch the
miss the second ask no longer gets to skip.
**Scoped to `kb`, because the argument says so.** Every `:clear` drops that cache's
entries *for this KB* and nothing else. The hit and miss counters some caches keep are
process-wide — they measure the mechanism rather than a store — and zeroing one would
reset a rate every other KB in the JVM is reporting, mid-measurement. So it is not
done here: `{:counters? true}` asks for it, in a call that says out loud it is reaching
past its argument, and the answer then carries `:counters-reset` naming the caches it
touched. A function whose signature names one KB must not quietly be a per-process
control; `caches`' `:counters` column is how a caller knows which rows the option is
about. The same option zeroes `kb`'s derived-state tallies (`row-tally`), and the
answer carries `:tallies-reset`, `[{:row id …what it held}]`.
A cache with no `:clear` is left alone and is not in the answer. Those are the
structural ones — the symbol pool, the compiled relation algebras — where dropping the
entries costs the sharing they exist for and buys no measurement.
A clear that throws costs its own entry and no other, the way a read does: its row
carries `:error` and an entry count of zero.How an event retires a row's entries, the values of a row's :retired-by.
How an event retires a row's entries, the values of a row's `:retired-by`.
(compare-retired t k stamp v)While the instrument runs, compare v, recomputed at key k of the row whose tally is
t, with an earlier value there (compared). stamp nil compares with the entry an
event retired at k, which the instrument keeps, and takes it: for a row whose entry
is the value read. A non-nil stamp compares with the last value computed at k when
that was computed under another stamp, and keeps v under stamp in its place: for a
row whose entries are stamped. A value is kept as its hash and compared by it, so the
instrument holds no closure the cache itself has evicted.
While the instrument runs, compare `v`, recomputed at key `k` of the row whose tally is `t`, with an earlier value there (`compared`). `stamp` nil compares with the entry an event retired at `k`, which the instrument keeps, and takes it: for a row whose entry is the value read. A non-nil `stamp` compares with the last value computed at `k` when that was computed under another stamp, and keeps `v` under `stamp` in its place: for a row whose entries are stamped. A value is kept as its hash and compared by it, so the instrument holds no closure the cache itself has evicted.
(compared t same?)While the instrument runs, count on tally t a miss compared with the value it
replaced, and a spurious one when same?.
While the instrument runs, count on tally `t` a miss compared with the value it replaced, and a spurious one when `same?`.
(derived-state)(derived-state kb)The register as data: {:rows [row …] :events [event …] :edges [[reader read] …]}.
A row is its descriptor less :value, ordered by id. With kb, a row that is a
registered cache also carries that cache's :entries.
The register as data: `{:rows [row …] :events [event …] :edges [[reader read] …]}`.
A row is its descriptor less `:value`, ordered by id. With `kb`, a row that is a
registered cache also carries that cache's `:entries`.(derived-value kb id)Row id's current value in kb: its :value called on kb, else the values at its
:at locations. Nil for a row that cannot be read from outside its pass.
Row `id`'s current value in `kb`: its `:value` called on `kb`, else the values at its `:at` locations. Nil for a row that cannot be read from outside its pass.
The closed set of write events that move derived state, {event {:n n :at [var-symbol …] :names what-the-event-names}}. :n numbers the event in the generated table, and
:at names the vars the event passes through, each called through its var, so a test
can wrap them (derived_state_test).
The closed set of write events that move derived state, `{event {:n n :at [var-symbol
…] :names what-the-event-names}}`. `:n` numbers the event in the generated table, and
`:at` names the vars the event passes through, each called through its var, so a test
can wrap them (`derived_state_test`).(grow!)Raise pressure one step back toward the operator's scale, and answer the new pressure. No trim: growing a bound drops nothing, it only lets the next store hold more.
Raise pressure one step back toward the operator's scale, and answer the new pressure. No trim: growing a bound drops nothing, it only lets the next store hold more.
(hit t)Count a hit on tally t, when there is one.
Count a hit on tally `t`, when there is one.
(image-fields section)The Reasoning fields a row imaged as section (:state or :cache) is located in
by its first :at location, sorted.
The `Reasoning` fields a row imaged as `section` (`:state` or `:cache`) is located in by its first `:at` location, sorted.
(install-memory-guard! {:keys [kbs]})Attach a post-collection listener to the JVM's garbage collectors that moves the cache
profile's :pressure with how full the old generation is: over pressure-high it shrinks
the caches so the next collection reclaims, under pressure-low it grows them back.
:kbs is a thunk answering the live KB records whose per-KB caches the trim reaches — the
host supplies it from its catalog, since the engine holds no roster of open KBs.
Attached by the servers and by nothing at engine load, so a library embedding pays for no
listener it did not ask for. Idempotent: a second call replaces the :kbs thunk and arms
no second listener, and two concurrent calls arm one between them (guard-lifecycle). A
JVM whose collectors emit no such notification keeps pressure at 1.0 — the guard is a
best-effort relief, not a guarantee. ! because it attaches to the process's collectors;
uninstall-memory-guard! detaches.
Attach a post-collection listener to the JVM's garbage collectors that moves the cache profile's `:pressure` with how full the old generation is: over `pressure-high` it shrinks the caches so the next collection reclaims, under `pressure-low` it grows them back. `:kbs` is a thunk answering the live KB records whose per-KB caches the trim reaches — the host supplies it from its catalog, since the engine holds no roster of open KBs. Attached by the servers and by nothing at engine load, so a library embedding pays for no listener it did not ask for. Idempotent: a second call replaces the `:kbs` thunk and arms no second listener, and two concurrent calls arm one between them (`guard-lifecycle`). A JVM whose collectors emit no such notification keeps pressure at 1.0 — the guard is a best-effort relief, not a guarantee. `!` because it attaches to the process's collectors; `uninstall-memory-guard!` detaches.
What a row is: :cache (droppable without moving a belief), :index (kept at the
write, rebuilt only by recover), :journal, :queue, :counter, :pass.
What a row is: `:cache` (droppable without moving a belief), `:index` (kept at the write, rebuilt only by recover), `:journal`, `:queue`, `:counter`, `:pass`.
(limit-of id default)The bound cache id enforces now, given its shipped default default. An override names
an absolute limit and replaces default: the operator's :scale leaves it alone, but the
guard's :pressure still multiplies it, so a filling heap shrinks a pinned cache like every
other. A default with no override is multiplied by both :scale and :pressure. Either
result is floored at min-limit and saturates at Long/MAX_VALUE: set-cache-scale
takes any number 0 or more, ##Inf included, and a product past the range of a long
is a bound no cache reaches rather than a throw on every store. A nil default with no
override — a cache bounded by something other than a count — stays nil, since no
multiplier acts on it.
Read on a cache's store path and by its rows entry, so the bound enforced and the bound
reported are one number. At scale 1.0 and pressure 1.0 with no override the shipped
default is returned as it stands.
The bound cache `id` enforces now, given its shipped default `default`. An override names an absolute limit and replaces `default`: the operator's `:scale` leaves it alone, but the guard's `:pressure` still multiplies it, so a filling heap shrinks a pinned cache like every other. A `default` with no override is multiplied by both `:scale` and `:pressure`. Either result is floored at `min-limit` and saturates at `Long/MAX_VALUE`: `set-cache-scale` takes any number 0 or more, `##Inf` included, and a product past the range of a long is a bound no cache reaches rather than a throw on every store. A nil `default` with no override — a cache bounded by something other than a count — stays nil, since no multiplier acts on it. Read on a cache's store path and by its `rows` entry, so the bound enforced and the bound reported are one number. At scale 1.0 and pressure 1.0 with no override the shipped `default` is returned as it stands.
(limit-thunk id default)#(limit-of id default), for a descriptor's :limit, so its rows entry reports the
effective bound rather than the shipped default. A default that is a var, such as
#'*a-dynamic-limit*, is dereferenced on each call, so the row reads the current
binding. See register-cache.
`#(limit-of id default)`, for a descriptor's `:limit`, so its `rows` entry reports the effective bound rather than the shipped default. A `default` that is a var, such as `#'*a-dynamic-limit*`, is dereferenced on each call, so the row reads the current binding. See `register-cache`.
(lru-clear! {:keys [map weight]})Empty lru, and answer how many entries went.
Empty `lru`, and answer how many entries went.
(lru-evict-if! {:keys [map weight weigh]} pick? drop?)Drop every entry of lru whose key both pick? and drop? hold of, and answer how many
went. pick? runs under the map's monitor and must not read lru; drop? runs over
the keys pick? kept, outside it, so it may.
Drop every entry of `lru` whose key both `pick?` and `drop?` hold of, and answer how many went. `pick?` runs under the map's monitor and must not read `lru`; `drop?` runs over the keys `pick?` kept, outside it, so it may.
(lru-get {:keys [map tally]} k)The value lru holds at k, or nil, marking it the most recently used; a hit or a
miss on its tally.
The value `lru` holds at `k`, or nil, marking it the most recently used; a hit or a miss on its tally.
(lru-put! {:keys [map weight weigh limit] :as lru} k v)Hold v at k in lru, evicting the least recently used entries until the weight is
back under the bound, and answer v. A value heavier than the whole bound is evicted
by its own insertion: answered, and not held.
Hold `v` at `k` in `lru`, evicting the least recently used entries until the weight is back under the bound, and answer `v`. A value heavier than the whole bound is evicted by its own insertion: answered, and not held.
(lru-size {:keys [map]})How many entries lru holds.
How many entries `lru` holds.
(lru-trim! {:keys [map] :as lru} target)Evict from the cold end of lru until it weighs at most target; answer how many
entries went. The weighted cache's :trim: the recent half survives a trim.
Evict from the cold end of `lru` until it weighs at most `target`; answer how many entries went. The weighted cache's `:trim`: the recent half survives a trim.
(lru-weight {:keys [map weight]})What lru holds, in its weight's unit.
What `lru` holds, in its weight's unit.
(memory-guard)Whether the guard is attached to the collectors, and the pressure it currently holds:
{:installed? bool :pressure p}. Pressure below 1.0 says the guard has shrunk the caches
under a heap it is watching fill.
Whether the guard is attached to the collectors, and the pressure it currently holds:
`{:installed? bool :pressure p}`. Pressure below 1.0 says the guard has shrunk the caches
under a heap it is watching fill.(miss t)Count a miss on tally t, when there is one, whose time spent adds.
Count a miss on tally `t`, when there is one, whose time `spent` adds.
(missed t start)Count a miss on tally t, when there is one, whose recompute started at start
(System/nanoTime).
Count a miss on tally `t`, when there is one, whose recompute started at `start` (`System/nanoTime`).
(on-every code){event code} for every event in events: the :retired-by of a row any write
retires, such as one stamped with the change clock.
`{event code}` for every event in `events`: the `:retired-by` of a row any write
retires, such as one stamped with the change clock.(pin-problem id)Why a pin cannot move registered cache id's bound, as a phrase, or nil when it can.
A pin moves a bound exactly when the descriptor's :limit is the limit-thunk for id,
the one bound limit-of reads the overrides into. Two kinds of registered cache fail
that: a nil-bound cache, bounded by something other than a count the profile holds
(hot records by its own knob, vaelii.disk.cache), and a bound standing outside the
profile — the symbol pool's and the scoped-closure budget's dynamic vars
(docs/caches.md). set-cache-limit refuses a pin on either rather than recording one
nothing enforces. Nil for an id nothing has registered, which registered? answers.
Why a pin cannot move registered cache `id`'s bound, as a phrase, or nil when it can. A pin moves a bound exactly when the descriptor's `:limit` is the `limit-thunk` for `id`, the one bound `limit-of` reads the overrides into. Two kinds of registered cache fail that: a nil-bound cache, bounded by something other than a count the profile holds (hot records by its own knob, `vaelii.disk.cache`), and a bound standing outside the profile — the symbol pool's and the scoped-closure budget's dynamic vars (docs/caches.md). `set-cache-limit` refuses a pin on either rather than recording one nothing enforces. Nil for an id nothing has registered, which `registered?` answers.
(pressure-response frac pressure)What a post-collection old-generation frac (used over max) asks of the caches at the
current pressure: :shrink over pressure-high, :grow under pressure-low while
pressure is still below the operator's ceiling of 1.0, else :hold. A pure function of
the two readings, so the decision is tested without a heap that is actually full.
What a post-collection old-generation `frac` (used over max) asks of the caches at the current `pressure`: `:shrink` over `pressure-high`, `:grow` under `pressure-low` while pressure is still below the operator's ceiling of 1.0, else `:hold`. A pure function of the two readings, so the decision is tested without a heap that is actually full.
(profile)The cache profile in force: {:scale <operator multiplier> :pressure <guard multiplier> :overrides {cache-id limit}}. A cache's effective bound is its shipped default times
:scale times :pressure (an override replaces the default but the two multipliers still
apply, so the guard can shrink a pinned cache under memory pressure).
The cache profile in force: `{:scale <operator multiplier> :pressure <guard multiplier>
:overrides {cache-id limit}}`. A cache's effective bound is its shipped default times
`:scale` times `:pressure` (an override replaces the default but the two multipliers still
apply, so the guard can shrink a pinned cache under memory pressure).(read-through cache limit k compute)The value at k in the map held by atom cache, else (compute) — stored under
assoc-bounded's bound, and returned.
find rather than get, so a computed nil is a hit rather than a miss recomputed
forever. compute runs outside the swap! because it is the expensive half and a
swap! retry must not run it twice: two callers racing one key both compute and both
store, and the second store is a no-op — the trade a memo of derived values wants over
holding a lock across the computation.
The value at `k` in the map held by atom `cache`, else `(compute)` — stored under `assoc-bounded`'s bound, and returned. `find` rather than `get`, so a computed `nil` is a hit rather than a miss recomputed forever. `compute` runs outside the `swap!` because it is the expensive half and a `swap!` retry must not run it twice: two callers racing one key both compute and both store, and the second store is a no-op — the trade a memo of derived values wants over holding a lock across the computation.
(recomputed t & body)body's value, counted on tally t as a miss with the time body took.
`body`'s value, counted on tally `t` as a miss with the time `body` took.
(register-cache {:keys [cache] :as descriptor})Declare that this namespace holds a cache. Called at namespace load, once per cache.
Bare, not !: it installs a descriptor the next load replaces, the way set-solver
installs a setting.
The descriptor:
:cache a keyword naming it, unique across the process
:label what to call it on screen
:scope :kb or :process — what :entries counts
:unit what one entry is, since entries mix units across caches
:limit entries held before it is cleared wholesale, or nil for a cache
bounded by something other than a count (say what, in :note).
A thunk where the bound is a dynamic var or profile-scaled —
limit-thunk builds the profile-scaled one; see below
:counters :kb, :process, or nil when nothing counts hits and misses
:note one line: what it holds, and what retires an entry
:read (fn [kb]) -> {:entries n :hits h :misses m}, any key absent where
there is no number, and any further count the row reports. O(1) —
this runs on a page that polls.
A nil :entries says the cache cannot be counted from here.
:clear (fn [kb]) -> entries dropped, or absent when nothing drops it by hand.
Scoped to kb. A clear that reached past its argument would make
clear-caches a process-wide control wearing a per-KB signature
:trim (fn [kb target]) -> entries dropped, or absent. The partial drop the
memory-pressure guard uses: bring the cache down to target entries while
keeping the rest, where :clear drops everything. A :process cache
ignores kb; trim-map! is the plain-map one, the LRU trims by recency
:reset-counters (fn [kb]) -> the counters as they stood, or absent. Only a cache
whose :counters are :process has one, and it is separate from :clear
precisely because it is wider than kb
:read, :clear and :reset-counters all take the KB even when the cache is
process-wide, so a caller needs no second calling convention for the static ones; they
ignore it.
:limit takes a thunk for the same reason :read is a function. A descriptor is
built once, at namespace load, so a constant captured into it is that constant forever
— which is right for a def and wrong for a ^:dynamic var, since being rebindable is
the only reason such a var is dynamic. Reporting the root bound while the engine
enforces a bound somebody rebound would misreport the one field a reader uses to judge
whether a cache is about to flush. Write :limit (fn [] *the-var*) and the row reads
it where it is read.
Declare that this namespace holds a cache. Called at namespace load, once per cache.
Bare, not `!`: it installs a descriptor the next load replaces, the way `set-solver`
installs a setting.
The descriptor:
:cache a keyword naming it, unique across the process
:label what to call it on screen
:scope :kb or :process — what `:entries` counts
:unit what one entry *is*, since entries mix units across caches
:limit entries held before it is cleared wholesale, or nil for a cache
bounded by something other than a count (say what, in `:note`).
**A thunk where the bound is a dynamic var or profile-scaled** —
`limit-thunk` builds the profile-scaled one; see below
:counters :kb, :process, or nil when nothing counts hits and misses
:note one line: what it holds, and what retires an entry
:read (fn [kb]) -> {:entries n :hits h :misses m}, any key absent where
there is no number, and any further count the row reports. **O(1)** —
this runs on a page that polls.
A nil `:entries` says the cache cannot be counted from here.
:clear (fn [kb]) -> entries dropped, or absent when nothing drops it by hand.
**Scoped to `kb`.** A clear that reached past its argument would make
`clear-caches` a process-wide control wearing a per-KB signature
:trim (fn [kb target]) -> entries dropped, or absent. The **partial** drop the
memory-pressure guard uses: bring the cache down to `target` entries while
keeping the rest, where `:clear` drops everything. A `:process` cache
ignores `kb`; `trim-map!` is the plain-map one, the LRU trims by recency
:reset-counters (fn [kb]) -> the counters as they stood, or absent. Only a cache
whose `:counters` are `:process` has one, and it is separate from `:clear`
precisely because it is wider than `kb`
`:read`, `:clear` and `:reset-counters` all take the KB even when the cache is
process-wide, so a caller needs no second calling convention for the static ones; they
ignore it.
**`:limit` takes a thunk for the same reason `:read` is a function.** A descriptor is
built once, at namespace load, so a constant captured into it is that constant forever
— which is right for a `def` and wrong for a `^:dynamic` var, since being rebindable is
the only reason such a var is dynamic. Reporting the root bound while the engine
enforces a bound somebody rebound would misreport the one field a reader uses to judge
whether a cache is about to flush. Write `:limit (fn [] *the-var*)` and the row reads
it where it is read.(register-derived descriptor)Declare a row of derived state. Called at namespace load, once per row; the loading
namespace is recorded as its :owner. The descriptor:
:id :label the row's id and name
:kind one of kinds
:keyed-by what an entry is keyed by
:reads the ids of the rows and stores it is computed from
:retired-by {event code}: each event in events that retires entries, and how
:computed :write, :settle, :read or :pass: where an entry is computed
:imaged? which section of a reasoning image carries it, or false
:at its locations, each [reasoning-field & keys], where it is one; a
symbol in a key, a reader, is written ::caches/reader
:var the var holding it, where it is one
:cache the register-cache id it is registered under as well, where it is one
:bound its bound, as a phrase, where :cache gives none
:value (fn [kb]) -> its current value, where :at does not reach it
:live (fn [kb]) -> how many of its entries are current, for a row whose
retired entries stay held until a read replaces them; start-tally!
counts a retirement of such a row as a fall in this number
:note one line
Declare a row of derived state. Called at namespace load, once per row; the loading
namespace is recorded as its `:owner`. The descriptor:
:id :label the row's id and name
:kind one of `kinds`
:keyed-by what an entry is keyed by
:reads the ids of the rows and `stores` it is computed from
:retired-by `{event code}`: each event in `events` that retires entries, and how
:computed :write, :settle, :read or :pass: where an entry is computed
:imaged? which section of a reasoning image carries it, or false
:at its locations, each `[reasoning-field & keys]`, where it is one; a
symbol in a key, a reader, is written `::caches/reader`
:var the var holding it, where it is one
:cache the `register-cache` id it is registered under as well, where it is one
:bound its bound, as a phrase, where `:cache` gives none
:value `(fn [kb])` -> its current value, where `:at` does not reach it
:live `(fn [kb])` -> how many of its entries are current, for a row whose
retired entries stay held until a read replaces them; `start-tally!`
counts a retirement of such a row as a fall in this number
:note one line(registered? id)Is id a cache registered in this process now? A membership test rather than a
refusal, because the register fills lazily: a cache is registered when its namespace
loads, and a qualitative calculus or the metric-time reasoner may not be loaded yet. So
set-limit reads this to warn on an id nothing has registered rather than to refuse
it — a not-yet-loaded cache would take the pin when it registers, where a refusal would
reject the very configuration a bulk load sets up before touching the calculus.
Is `id` a cache registered in *this* process now? A membership test rather than a refusal, because the register fills lazily: a cache is registered when its namespace loads, and a qualitative calculus or the metric-time reasoner may not be loaded yet. So `set-limit` reads this to *warn* on an id nothing has registered rather than to refuse it — a not-yet-loaded cache would take the pin when it registers, where a refusal would reject the very configuration a bulk load sets up before touching the calculus.
(reset-profile)Restore the configured profile — the VAELII_CACHE_SCALE scale, pressure 1.0 and no
overrides — and return it.
Restore the configured profile — the `VAELII_CACHE_SCALE` scale, pressure 1.0 and no overrides — and return it.
(retired-entries before after)The [key value] entries before held that after drops or replaces: a map's by key,
a set's members, a vector of locations' per location, else [[nil before]] when the
two differ. A nil before held no entry, so a structure made where none was retires
nothing.
The `[key value]` entries `before` held that `after` drops or replaces: a map's by key, a set's members, a vector of locations' per location, else `[[nil before]]` when the two differ. A nil `before` held no entry, so a structure made where none was retires nothing.
(row-tally kb id)Row id's tally in kb, or nil: held by the structure at the row's first :at
location, else by that location's Reasoning field. A row held by the process rather
than by a KB has none, since its tally would count every KB's reads.
Row `id`'s tally in `kb`, or nil: held by the structure at the row's first `:at` location, else by that location's `Reasoning` field. A row held by the process rather than by a KB has none, since its tally would count every KB's reads.
(rows kb)Every registered cache, read against kb, ranked by entries.
A row is the descriptor's static half — :cache :label :scope :unit :limit :counters :note — plus whatever its :read answered, plus :hit-rate and :clearable?. No
row walks the KB: each is a count off a map the engine is already holding, which is
what makes this pollable.
A row is data all the way down. The descriptor's three function slots — :read,
:clear, :reset-counters — are dropped, and what a caller needs of the last two is
the :clearable? flag and the :counters scope beside it. This is a public read
(vaelii.core/caches), served over RPC and rendered on a page, so a function left in a
row is a value neither can carry.
A read that throws costs its own row and no other, and the row carries :error
saying what went wrong. One broken descriptor taking the whole answer down would fail
the read exactly when the process is in the state it exists to describe.
A cache a derived-state row counts (row-tally) reports that row's tally: :hits
and :misses where its :read gives none, :recompute-ns, :retired, :compared,
:spurious and :evicted, with :counters :kb.
Ranked by entries descending, ties broken on the cache's own name, so the order is a function of the content and two processes holding the same caches list them the same way. A row that cannot be counted sorts last, since a nil is not a small number.
Every registered cache, read against `kb`, ranked by entries. A row is the descriptor's static half — `:cache :label :scope :unit :limit :counters :note` — plus whatever its `:read` answered, plus `:hit-rate` and `:clearable?`. No row walks the KB: each is a count off a map the engine is already holding, which is what makes this pollable. **A row is data all the way down.** The descriptor's three function slots — `:read`, `:clear`, `:reset-counters` — are dropped, and what a caller needs of the last two is the `:clearable?` flag and the `:counters` scope beside it. This is a public read (`vaelii.core/caches`), served over RPC and rendered on a page, so a function left in a row is a value neither can carry. **A read that throws costs its own row and no other**, and the row carries `:error` saying what went wrong. One broken descriptor taking the whole answer down would fail the read exactly when the process is in the state it exists to describe. **A cache a derived-state row counts** (`row-tally`) reports that row's tally: `:hits` and `:misses` where its `:read` gives none, `:recompute-ns`, `:retired`, `:compared`, `:spurious` and `:evicted`, with `:counters :kb`. Ranked by entries **descending, ties broken on the cache's own name**, so the order is a function of the content and two processes holding the same caches list them the same way. A row that cannot be counted sorts last, since a nil is not a small number.
(set-limit id n)Pin cache id's bound to n regardless of scale, or clear the pin when n is nil, and
return the profile. A caller who names both a cache and a number has stated the bound it
wants, so the scale does not then move it.
Pin cache `id`'s bound to `n` regardless of scale, or clear the pin when `n` is nil, and return the profile. A caller who names both a cache and a number has stated the bound it wants, so the scale does not then move it.
(set-scale x)Multiply every count-bounded cache's shipped limit by x, and return the profile.
Reversible — 1.0 restores the shipped bounds — so bare, not !, as set-solver is:
it installs a setting the next cache store reads, and no belief moves.
Multiply every count-bounded cache's shipped limit by `x`, and return the profile. Reversible — `1.0` restores the shipped bounds — so bare, not `!`, as `set-solver` is: it installs a setting the next cache store reads, and no belief moves.
(shrink! kbs)Lower pressure one step and trim the caches to the new, lower bound; answer
{:pressure p :dropped n}. Public so the guard's response can be driven in a test without
a heap that is actually full.
Lower pressure one step and trim the caches to the new, lower bound; answer
`{:pressure p :dropped n}`. Public so the guard's response can be driven in a test without
a heap that is actually full.(spent t start)Add the time since start (System/nanoTime) to tally t's recompute time, for a
miss counted already whose recompute ended later.
Add the time since `start` (`System/nanoTime`) to tally `t`'s recompute time, for a miss counted already whose recompute ended later.
(start-tally! kb)(start-tally! kb {:keys [rows observe events]})Run the event instrument over kb, or over the first KB an event is handed when kb is
nil, until stop-tally!. Every var events names is wrapped process-wide; one
instrument runs at a time, and a second start refuses. Each event's firings, and the
entries it retired of each row, are counted: in derived-state's :events while it
runs, and in each row's tally (:retired). A miss compares what it recomputed while
it runs (compare-retired).
opts: :rows, the row ids to read (default every row not of kind :pass), for a KB
whose rows are too large to read at every event; :observe, (fn [row charged])
called for each row that moved with the set of events it is charged to. Without
:observe, a row with :live is counted by it alone and its value is not read.
:events, the event ids to wrap (default every one): each wrapped call reads the rows
twice, so a KB whose write fires an event a million times leaves that event out.
Run the event instrument over `kb`, or over the first KB an event is handed when `kb` is nil, until `stop-tally!`. Every var `events` names is wrapped process-wide; one instrument runs at a time, and a second start refuses. Each event's firings, and the entries it retired of each row, are counted: in `derived-state`'s `:events` while it runs, and in each row's tally (`:retired`). A miss compares what it recomputed while it runs (`compare-retired`). `opts`: `:rows`, the row ids to read (default every row not of kind `:pass`), for a KB whose rows are too large to read at every event; `:observe`, `(fn [row charged])` called for each row that moved with the set of events it is charged to. Without `:observe`, a row with `:live` is counted by it alone and its value is not read. `:events`, the event ids to wrap (default every one): each wrapped call reads the rows twice, so a KB whose write fires an event a million times leaves that event out.
(stop-tally!)Stop the instrument and restore every var it wrapped; answer {:kb :fired {event n} :retired {[event row] n}}, or nil when none runs.
Stop the instrument and restore every var it wrapped; answer `{:kb :fired {event n}
:retired {[event row] n}}`, or nil when none runs.The ids a row's :reads may name beside other rows' ids.
The ids a row's `:reads` may name beside other rows' ids.
(tallied r ids)r, a reference, answered with a fresh tally for each row id of ids in its metadata.
`r`, a reference, answered with a fresh tally for each row id of `ids` in its metadata.
(tally-map t)Tally t as {slot n}, or nil for no tally.
Tally `t` as `{slot n}`, or nil for no tally.
(tally-of x id)Row id's tally held by x: a weighted LRU's own, or the one tallied put in x's
metadata; nil when x holds none.
Row `id`'s tally held by `x`: a weighted LRU's own, or the one `tallied` put in `x`'s metadata; nil when `x` holds none.
(tally-ranking kb)The rows of kb that hold a tally, ranked for the consolidation plan, as {:by-spurious-cost [row …] :by-hit-rate [row …]}. A row is {:row :recompute-ns :hit-rate :spurious-fraction :spurious-cost}: :spurious-fraction is :spurious over :compared,
nil when nothing was compared, and :spurious-cost is :recompute-ns times it.
:by-spurious-cost is descending, a row with no fraction last; :by-hit-rate is
ascending, a row with no lookup last. Ties are broken on the row's name.
The rows of `kb` that hold a tally, ranked for the consolidation plan, as `{:by-spurious-cost
[row …] :by-hit-rate [row …]}`. A row is `{:row :recompute-ns :hit-rate
:spurious-fraction :spurious-cost}`: `:spurious-fraction` is `:spurious` over `:compared`,
nil when nothing was compared, and `:spurious-cost` is `:recompute-ns` times it.
`:by-spurious-cost` is descending, a row with no fraction last; `:by-hit-rate` is
ascending, a row with no lookup last. Ties are broken on the row's name.The slots of a tally, in order. :retired is filled by start-tally!, :compared and
:spurious only while it runs, :evicted by a weighted LRU.
The slots of a tally, in order. `:retired` is filled by `start-tally!`, `:compared` and `:spurious` only while it runs, `:evicted` by a weighted LRU.
(tallying?)Is the instrument running, so that a miss compares what it recomputed?
Is the instrument running, so that a miss compares what it recomputed?
(trim-map! a target)Drop entries from the plain map held by atom a until it holds at most target, keeping
the target that iteration reaches first, and answer how many went. The kept set is
arbitrary rather than the most recent — a plain map records no recency — which is the trade
against a wholesale clear: half the entries survive a trim where none survive a clear, so
the reads they serve are not all recomputed at once. A cache whose entries carry recency
or a different shape supplies its own :trim rather than calling this.
Drop entries from the plain map held by atom `a` until it holds at most `target`, keeping the `target` that iteration reaches first, and answer how many went. The kept set is arbitrary rather than the most recent — a plain map records no recency — which is the trade against a wholesale clear: half the entries survive a trim where none survive a clear, so the reads they serve are not all recomputed at once. A cache whose entries carry recency or a different shape supplies its own `:trim` rather than calling this.
(uninstall-memory-guard!)Detach the guard's listener from every collector it armed and restore pressure to 1.0; answer the guard state. Safe when nothing is installed.
Detach the guard's listener from every collector it armed and restore pressure to 1.0; answer the guard state. Safe when nothing is installed.
(value-at kb [field & path])The value at [field & keys] in kb's Reasoning value: the field's atom
dereferenced, then keys followed into it.
The value at `[field & keys]` in `kb`'s `Reasoning` value: the field's atom dereferenced, then `keys` followed into it.
(weighted-lru limit weigh)An empty weighted LRU. limit is a thunk answering the most weight it holds — a
limit-thunk, so the profile's scale and the memory guard's pressure move it — and
weigh answers an entry's weight from its value. Read and written through lru-get
and lru-put!; an access-ordered map reorders on a read, so every operation holds its
monitor, uncontended on a single writer.
An empty weighted LRU. `limit` is a thunk answering the most weight it holds — a `limit-thunk`, so the profile's scale and the memory guard's pressure move it — and `weigh` answers an entry's weight from its value. Read and written through `lru-get` and `lru-put!`; an access-ordered map reorders on a read, so every operation holds its monitor, uncontended on a single writer.
(with-tally kb opts f)(f) run under the instrument over kb (start-tally! with opts); answers what
stop-tally! answers.
`(f)` run under the instrument over `kb` (`start-tally!` with `opts`); answers what `stop-tally!` answers.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |