Liking cljdoc? Tell your friends :D

vaelii.impl.io.snapshot

A snapshot of the KB's derived state, and the two-op sink it is written through.

Derived state — the index, and (from 0.9.0's second thread) the taxonomy and the JTMS labels — is rebuilt from the records on every open by reindex / recover, at a cost that is O(records). A snapshot is a cache of that rebuild: written once, read back on the next open, and installed instead of recomputed. It is never a source of truth. Its only failure mode is "recompute", never "wrong answer", which is what keeps it clear of order independence — a snapshot is stamped to the exact records it was derived from, and a mismatch takes the slow path (reindex/recover), never a stale belief.

One image, many sinks

There is more than one place a snapshot might live — a directory of files, a Postgres blob, memory for a test — and letting each invent its own serialization is the drift the shared [key value] index projection was created to avoid one layer up. So the shape is one sink:

  • a snapshot is a set of named sections plus a manifest;
  • a SnapshotSink knows only how to write a named section (a constant-memory stream of frames) and to commit a manifest; a SnapshotSource knows how to read a section back and to read the manifest;
  • the projections (the index's [key value], and later the taxonomy's edges and the JTMS's labels) and the validity check (decision) live here, above the sink, written once;
  • the targetsfile-sink / file-source over vaelii.impl.io.frames, and memory-medium for a test — live below it, each a small adapter.

A section written to one sink loads from another, the same property p/index-entries / p/index-load already give the index across backends.

The manifest is the commit point

It is written last, exactly as a dump's meta.edn is: a half-written image has no manifest, so read-manifest returns nil and the image is never offered — the caller rebuilds. Opening a file-sink deletes any existing manifest first, so the window in which an old manifest could describe half-rewritten sections does not exist.

Validity is the whole design

decision is lifted from vaelii.impl.disk.index-snapshot/decision: one reason per mismatch class, and any non-nil reason discards the whole image and falls back to a rebuild. The classes:

  • :absent — no manifest (missing, or a save that never committed);
  • :layout-changed — the snapshot format version, or kv/index-layout-version, is not this build's (an index in another layout reads as empty rather than wrong, which is the undiagnosable failure this forecloses);
  • :records-differ — the manifest's records stamp is not the store's now (a different KB, or the same one after a write);
  • :entries-truncated — a section is short or unreadable, caught while installing (a torn nippy chunk reads as a clean EOF, so truncation shows only as a frame count below what the manifest recorded).

There is no :byte-order class here, though index-snapshot has one: that image writes raw little-endian int runs, so an image from a machine of the other endianness must be refused; these sections are nippy frames, which are endian-neutral, so a byte-order mismatch cannot arise and the format version subsumes any encoding change.

The stamp is vaelii.impl.io.fingerprint's records digest, which ports: a :disk store hashes its slots, a :memory store folds its records, a SQL store answers a count — and all compare against the same manifest number. Which digest a caller passes is its business; the sink only compares.

A **snapshot** of the KB's derived state, and the two-op *sink* it is written through.

Derived state — the index, and (from 0.9.0's second thread) the taxonomy and the JTMS
labels — is rebuilt from the records on every open by `reindex` / `recover`, at a cost
that is O(records).  A snapshot is a *cache* of that rebuild: written once, read back on
the next open, and installed instead of recomputed.  It is never a source of truth.  Its
only failure mode is "recompute", never "wrong answer", which is what keeps it clear of
order independence — a snapshot is stamped to the exact records it was derived from, and a
mismatch takes the slow path (`reindex`/`recover`), never a stale belief.

## One image, many sinks

There is more than one place a snapshot might live — a directory of files, a Postgres
blob, memory for a test — and letting each invent its own serialization is the drift the
shared `[key value]` index projection was created to avoid one layer up.  So the shape is
one **sink**:

* a snapshot is a set of **named sections** plus a **manifest**;
* a `SnapshotSink` knows only how to *write a named section* (a constant-memory stream of
  frames) and to *commit a manifest*; a `SnapshotSource` knows how to *read a section back*
  and to *read the manifest*;
* the **projections** (the index's `[key value]`, and later the taxonomy's edges and the
  JTMS's labels) and the **validity check** (`decision`) live here, above the sink, written
  once;
* the **targets** — `file-sink` / `file-source` over `vaelii.impl.io.frames`, and
  `memory-medium` for a test — live below it, each a small adapter.

A section written to one sink loads from another, the same property `p/index-entries` /
`p/index-load` already give the index across backends.

## The manifest is the commit point

It is written **last**, exactly as a dump's `meta.edn` is: a half-written image has no
manifest, so `read-manifest` returns nil and the image is never offered — the caller
rebuilds.  Opening a `file-sink` deletes any existing manifest first, so the window in
which an old manifest could describe half-rewritten sections does not exist.

## Validity is the whole design

`decision` is lifted from `vaelii.impl.disk.index-snapshot/decision`: one reason per
mismatch class, and any non-nil reason discards the *whole* image and falls back to a
rebuild.  The classes:

* `:absent`          — no manifest (missing, or a save that never committed);
* `:layout-changed`  — the snapshot format version, or `kv/index-layout-version`, is not
                       this build's (an index in another layout reads as *empty* rather
                       than wrong, which is the undiagnosable failure this forecloses);
* `:records-differ`  — the manifest's records stamp is not the store's now (a different
                       KB, or the same one after a write);
* `:entries-truncated` — a section is short or unreadable, caught while installing (a torn
                       nippy chunk reads as a clean EOF, so truncation shows only as a
                       frame count below what the manifest recorded).

There is no `:byte-order` class here, though `index-snapshot` has one: that image writes
raw little-endian `int` runs, so an image from a machine of the other endianness must be
refused; these sections are nippy frames, which are endian-neutral, so a byte-order
mismatch cannot arise and the format version subsumes any encoding change.

The stamp is `vaelii.impl.io.fingerprint`'s records digest, which **ports**: a `:disk`
store hashes its slots, a `:memory` store folds its records, a SQL store answers a count
— and all compare against the same manifest number.  Which digest a caller passes is its
business; the sink only compares.
raw docstring

decisionclj

(decision m stamp)

Why the image described by manifest m cannot be trusted against stamp (the store's current records fingerprint), or nil when it can. Lifted from vaelii.impl.disk.index-snapshot/decision: the image is a cache, so any non-nil reason discards the whole of it and the caller rebuilds. :entries-truncated is not decided here — a nippy stream's truncation reads as a clean EOF, so it can only be caught while the section is installed, against the count the manifest recorded.

Why the image described by manifest `m` cannot be trusted against `stamp` (the store's
current records fingerprint), or nil when it can.  Lifted from
`vaelii.impl.disk.index-snapshot/decision`: the image is a cache, so any non-nil reason
discards the whole of it and the caller rebuilds.  `:entries-truncated` is not decided
here — a nippy stream's truncation reads as a clean EOF, so it can only be caught while
the section is installed, against the count the manifest recorded.
sourceraw docstring

file-sinkclj

(file-sink root)
(file-sink root
           {:keys [compression chunk-size]
            :or {compression :none chunk-size 10000}})

A sink writing sections and a manifest.edn under root. Opening it deletes any existing manifest, so from here until commit! the image reads as absent — the window in which a stale manifest could describe half-rewritten sections is closed by construction. :compression defaults to :none (an image is a fast local cache, and the CPU a codec costs is usually the wrong trade for it); :chunk-size to 10000.

A sink writing sections and a `manifest.edn` under `root`.  Opening it deletes any
existing manifest, so from here until `commit!` the image reads as absent — the window
in which a stale manifest could describe half-rewritten sections is closed by
construction.  `:compression` defaults to `:none` (an image is a fast local cache, and
the CPU a codec costs is usually the wrong trade for it); `:chunk-size` to 10000.
sourceraw docstring

file-sourceclj

(file-source root)

A read-only source over a directory file-sink wrote. Reads nothing until asked — read-manifest for the validity check, read-section for the install.

A read-only source over a directory `file-sink` wrote.  Reads nothing until asked —
`read-manifest` for the validity check, `read-section` for the install.
sourceraw docstring

format-versionclj

The snapshot's own layout number, beside kv/index-layout-version (which says what the section entries mean). Bump when a section's shape or the manifest's shape changes.

The snapshot's own layout number, beside `kv/index-layout-version` (which says what the
section *entries* mean).  Bump when a section's shape or the manifest's shape changes.
sourceraw docstring

index-framesclj

(index-frames index)

The index's entries as [key value] frames — the portable projection every backend shares (p/index-entries). This streams what the store holds rather than deriving anything: a writer that called index-sentex would be reindex with extra steps, writing an index it had just computed instead of the one the KB was answering from.

Each pair is normalized to a vector: the backends emit a mix of MapEntrys and plain vectors (the map-backed stores yield entries, the tiered one yields vectors, the columnar one yields both), and nippy gives MapEntry its own type id — so without this, two byte-identical logical indexes froze to byte-different streams according to which backend held them. Shared verbatim with the export dump (vaelii.impl.io.export), so a dump's index and a standalone image are one projection rather than two.

The index's entries as `[key value]` frames — the portable projection every backend
shares (`p/index-entries`).  This streams what the store holds rather than deriving
anything: a writer that called `index-sentex` would be `reindex` with extra steps,
writing an index it had just computed instead of the one the KB was answering from.

Each pair is normalized to a **vector**: the backends emit a mix of `MapEntry`s and plain
vectors (the map-backed stores yield entries, the tiered one yields vectors, the columnar
one yields both), and nippy gives `MapEntry` its own type id — so without this, two
byte-identical logical indexes froze to byte-different streams according to which backend
held them.  Shared verbatim with the export dump (`vaelii.impl.io.export`), so a dump's
index and a standalone image are one projection rather than two.
sourceraw docstring

index-manifestclj

(index-manifest stamp entry-count)

The manifest for an index image: the two format numbers, the records stamp it is valid against, and the section's frame count for the truncation check.

The manifest for an index image: the two format numbers, the records stamp it is valid
against, and the section's frame count for the truncation check.
sourceraw docstring

index-mismatchclj

(index-mismatch meta stamp)

The content-validity core an index cache shares wherever it is stored: the layout it is keyed in, and the records it was derived from. Returns :layout-changed, :records-differ, or nil. Each container adds its own classes around this — an image (decision) adds :absent and a format-version check; a dump (vaelii.impl.io.import/index-decision) adds :absent and :handles-remapped.

The content-validity core an index cache shares wherever it is stored: the layout it is
keyed in, and the records it was derived from.  Returns `:layout-changed`,
`:records-differ`, or nil.  Each container adds its own classes around this — an image
(`decision`) adds `:absent` and a format-version check; a dump
(`vaelii.impl.io.import/index-decision`) adds `:absent` and `:handles-remapped`.
sourceraw docstring

index-sectionclj

The section name the index's [key value] projection is written under.

The section name the index's `[key value]` projection is written under.
sourceraw docstring

load-index!clj

(load-index! source index stamp)

Validate the image behind source against stamp (the store's current records fingerprint) and install its index section into index, or report why it was discarded. Returns {:index :replayed :entries n} or {:index :rebuild :reason r} with r one of :absent :layout-changed :records-differ :entries-truncated.

The caller reindexes on any :rebuild, which is always legal because the index is derived state and this is a cache of it — and reindex clears the index first, so a partial install left by a truncated section is wiped. index must be empty on entry, the same contract p/index-load carries.

Validate the image behind `source` against `stamp` (the store's current records
fingerprint) and install its index section into `index`, or report why it was discarded.
Returns `{:index :replayed :entries n}` or `{:index :rebuild :reason r}` with `r` one of
`:absent :layout-changed :records-differ :entries-truncated`.

The caller reindexes on any `:rebuild`, which is always legal because the index is derived
state and this is a cache of it — and reindex clears the index first, so a partial install
left by a truncated section is wiped.  `index` must be **empty** on entry, the same
contract `p/index-load` carries.
sourceraw docstring

memory-mediumclj

(memory-medium)

An in-heap snapshot medium, usable as both a SnapshotSink and a SnapshotSource. Holds the whole image in RAM — the price of being a test double and a portability oracle, not a scale target.

An in-heap snapshot medium, usable as both a `SnapshotSink` and a `SnapshotSource`.
Holds the whole image in RAM — the price of being a test double and a portability oracle,
not a scale target.
sourceraw docstring

save-index!clj

(save-index! sink index stamp)

Write index's projection through sink as the index section, then commit a manifest stamped with stamp (the records fingerprint). Returns the committed manifest.

The manifest is committed after the section, so a crash mid-write leaves an image with no manifest, which load-index! reads as :absent and rebuilds.

Write `index`'s projection through `sink` as the index section, then commit a manifest
stamped with `stamp` (the records fingerprint).  Returns the committed manifest.

The manifest is committed **after** the section, so a crash mid-write leaves an image
with no manifest, which `load-index!` reads as `:absent` and rebuilds.
sourceraw docstring

SnapshotSinkcljprotocol

Where a snapshot's bytes go — a directory, a database, memory. Two ops: stream a named section, and commit the manifest that vouches for the lot.

Where a snapshot's bytes go — a directory, a database, memory.  Two ops: stream a
named section, and commit the manifest that vouches for the lot.

commit!clj

(commit! sink manifest)

Write manifest as the completion marker — last, after every section, so an image with no committed manifest is never offered.

Write `manifest` as the completion marker — **last**, after every section, so an
image with no committed manifest is never offered.

write-section!clj

(write-section! sink name frames)

Write the lazy seq frames to the section named name, one chunk in memory at a time. Returns the number of frames written.

Write the lazy seq `frames` to the section named `name`, one chunk in memory at a
time.  Returns the number of frames written.
sourceraw docstring

SnapshotSourcecljprotocol

Where a snapshot's bytes come from. Two ops mirroring the sink: read the manifest, and stream a named section back.

Where a snapshot's bytes come from.  Two ops mirroring the sink: read the manifest, and
stream a named section back.

read-manifestclj

(read-manifest source)

The committed manifest map, or nil when the image is absent or was never committed.

The committed manifest map, or nil when the image is absent or was never committed.

read-sectionclj

(read-section source name)

A constant-memory lazy seq of the frames in the section named name.

A constant-memory lazy seq of the frames in the section named `name`.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close