# 08 — Snapshot Persistence

Demonstrates how `SnapshotPersistence` enables warm starts by caching index state (documents + vectors) between engine restarts.

## What's new

- **SnapshotPersistence** — the interface for saving and loading index snapshots
- **serializeSnapshot / deserializeSnapshot** — gzip-compressed JSON with base64 vectors
- **Warm start** — skip re-embedding by restoring vectors from a cached snapshot
- **Model invalidation** — snapshot automatically rejected when embedding model changes

## Key concepts

### Cold start vs. warm start

Embedding documents is the most expensive part of index construction. A snapshot caches the result so subsequent restarts skip re-embedding:

| Start type   | Snapshot state          | Embeddings computed  | Time |
| ------------ | ----------------------- | -------------------- | ---- |
| Cold         | miss (no cached data)   | N (one per document) | Slow |
| Warm         | hit (model matches)     | 0                    | Fast |
| Model change | hit, but model mismatch | N (re-embedded)      | Slow |

### SnapshotPersistence interface

```ts
interface SnapshotPersistence {
  load<T>(operationId?: string): Promise<IndexSnapshot<T> | null>;
  save<T>(snap: IndexSnapshot<T>, operationId?: string): Promise<void>;
}
```

The engine calls `load()` during `init()` and `save()` after embedding completes. The `operationId` is for logging/tracing.

### IndexSnapshot

A snapshot stores documents and their pre-computed vectors:

```ts
interface IndexSnapshot<T> {
  version: number; // SNAPSHOT_VERSION (currently 2)
  hash: string; // content hash for invalidation
  modelId: string; // embedding model used
  builtAt: string; // ISO timestamp
  documents: T[]; // full document array
  vectors: Float32Array[]; // parallel to documents[]
}
```

Vectors are base64-encoded during serialization and gzip-compressed. A 3-document snapshot with 8-dimensional vectors compresses to ~330 bytes.

### Three invalidation dimensions

The engine rejects a snapshot when any of these change:

1. **SNAPSHOT_VERSION** — serialization format (encoding, field layout). Bumped manually for format changes.
2. **modelId** — embedding model. When you switch from `model-v1` to `model-v2`, cached vectors are incompatible.
3. **Content hash** — derived from glossary entries and builder config. Changes when glossary entries are edited.

### Production implementations

This example uses an in-memory buffer. Production implementations typically use:

- **S3** — the search service stores snapshots in S3 with a key structure: `{prefix}v{version}/{engine}/{modelId}/{hash}/`
- **Redis** — for smaller indexes where latency matters more than durability
- **Filesystem** — for local development

## Running

```bash
npx tsx examples/08-snapshot-persistence/main.ts
```

## Expected output

```
=== Cold start (first boot) ===
  [snapshot] load #1: miss (no cached data)
  [snapshot] save #1: 3 docs, 3 vectors, 0.3 KB compressed
  Documents: 3, Vectors: 3
  Embeddings computed: 3

  Search "contract":
    0.1154  CONTRACT
    0.0741  ACCOUNT
    0.0714  PAYMENT

=== Warm start (from snapshot) ===
  [snapshot] load #2: hit (3 docs, 3 vectors, model=mock-v1)
  [snapshot] save #2: 3 docs, 3 vectors, 0.3 KB compressed
  Documents: 3, Vectors: 3
  Embeddings computed: 0 (restored from snapshot)

  Search "contract":
    0.1154  CONTRACT
    0.0741  ACCOUNT
    0.0714  PAYMENT

=== Model change (snapshot rejected) ===
  [snapshot] load #3: hit (3 docs, 3 vectors, model=mock-v1)
  [snapshot] save #3: 3 docs, 3 vectors, 0.3 KB compressed
  Documents: 3, Vectors: 3
  Embeddings computed: 3 (re-embedded — model changed)

=== Summary ===
  Snapshot loads: 3
  Snapshot saves: 3
  Buffer size: 332 bytes
```
