---
title: "snapshot module"
summary: "Serde-based disk persistence for petgraph graphs — key-as-filename, atomic write, mtime rotation, optional zstd compression."
read_when:
  - Implementing or modifying snapshot save/load/inspect/purge
  - Understanding binary and JSON file layout
  - Adding or changing compression support
status: implemented
last_updated: "2026-05-02"
---

# Specification: `snapshot` module

**Feature flags:** `snapshot` / `snapshot-zstd` / `snapshot-lz4`

## File naming

```
{name}-{sanitized_key}.snap
{name}-{sanitized_key}.snap.zst
{name}-{sanitized_key}.snap.lz4
{name}-{sanitized_key}.json
{name}-{sanitized_key}.json.zst
{name}-{sanitized_key}.json.lz4
```

`sanitize_key`: replace chars outside `[a-zA-Z0-9_.-]` with `_`. Error if result trims to empty.
Same key → same filename → idempotent overwrite.

## Binary layout (bincode)

```
meta_len   : u64 le
meta_bytes : [u8; meta_len]   (bincode SnapshotMeta)
graph_bytes: [u8]             (bincode G, remainder)
```

`inspect` reads only `meta_len + meta_bytes`. Graph bytes never touched.

## JSON layout

```json
{"meta": <SnapshotMeta>, "graph": <G>}
```

## Key semantics

- `Some(k)` → load looks for `{name}-{sanitize(k)}.*`. Missing → `Err(KeyNotFound)`.
- `None` → load returns most recent by mtime. Empty dir → `Ok(None)`.
- `#[serde(skip)]` — always `None` after deserialization. Set at runtime only.
- `GraphState` uses `key = None`; key management is internal there.

## Rotation

mtime-based (not filename order). On every `save`: delete all but `keep` newest files.
`.tmp` files excluded from rotation and list.

## Bincode API

Uses `bincode 2.x` — `bincode::serde::encode_to_vec` / `bincode::serde::decode_from_slice`
with `bincode::config::standard()`. Requires `features = ["serde"]` on the bincode dep.
Not the 1.x API. Not `bincode::Encode`/`Decode` traits.

## Known constraints

- `gen` is a reserved keyword in Rust 2024 — never use as variable name.
- `std::io::Error` has no `PartialEq` — `SnapshotError` uses a manual impl comparing by `ErrorKind`.
- Tmp path: `format!("{}.tmp", final_path)` not `path.with_extension(...)` — double extensions (`.snap.zst`) break the latter.
- Test graph types must be owned (e.g. `Graph<String, ()>`), not borrowed (`Graph<&str, ()>`), for `DeserializeOwned`.
