Grove.Tree.DocumentStorage (Grove v0.1.1)
View SourceHigh-level document persistence using snapshots and event logs.
Implements the Eg-walker persistence pattern:
- Snapshots capture full tree state at critical versions
- Event logs store operations since the last snapshot
- Loading replays events on top of snapshot for fast startup
Storage Keys
{:snapshot, doc_id, clock}- Tree snapshots at critical versions{:events, doc_id, batch_id}- Event batches since last snapshot{:meta, doc_id}- Document metadata (latest snapshot clock, etc.)
Usage
# Initialize storage
{:ok, storage} = DocumentStorage.new(storage_module, storage_ref, "doc_123")
# Save tree (auto-creates snapshots at critical versions)
{:ok, storage} = DocumentStorage.save(storage, tree)
# Load tree (snapshot + event replay)
{:ok, tree, storage} = DocumentStorage.load(storage, "replica_1")
# Force snapshot and truncate old events
{:ok, storage} = DocumentStorage.compact(storage)Performance
With 100K events and periodic snapshots:
- Without snapshot: ~10s to replay all events
- With snapshot: <100ms to load snapshot + replay recent events
Summary
Functions
Forces a snapshot and removes old event batches.
Returns the number of events since the last snapshot.
Loads a tree from storage using snapshot + event replay.
Creates a new DocumentStorage for the given document.
Saves the tree state, potentially creating a snapshot.
Returns whether a snapshot should be created.
Types
@type t() :: %Grove.Tree.DocumentStorage{ document_id: String.t(), last_snapshot_clock: non_neg_integer() | nil, pending_events: [Grove.Tree.Event.t()], storage_module: module(), storage_ref: term() }
Functions
@spec compact(t(), Grove.Tree.t()) :: {:ok, t()} | {:error, term()}
Forces a snapshot and removes old event batches.
This compacts storage by:
- Creating a snapshot at current state (even if not critical)
- Removing event batches before the snapshot
- Keeping only the new snapshot
Examples
{:ok, storage} = DocumentStorage.compact(storage, tree)
@spec events_since_snapshot_count(t(), Grove.Tree.t()) :: non_neg_integer()
Returns the number of events since the last snapshot.
@spec load(t(), String.t()) :: {:ok, Grove.Tree.t() | nil, t()} | {:error, term()}
Loads a tree from storage using snapshot + event replay.
Examples
{:ok, tree, storage} = DocumentStorage.load(storage, "replica_1")
{:ok, nil, storage} = DocumentStorage.load(storage, "replica_1") # No document
Creates a new DocumentStorage for the given document.
Examples
{:ok, storage} = DocumentStorage.new(Grove.Storage.DETS, dets_ref, "doc_123")
@spec save(t(), Grove.Tree.t()) :: {:ok, t()} | {:error, term()}
Saves the tree state, potentially creating a snapshot.
Creates a snapshot if:
- Tree is at a critical version (frontier size == 1)
- Events since last snapshot exceed threshold
Otherwise, saves new events to the event log.
Examples
{:ok, storage} = DocumentStorage.save(storage, tree)
@spec should_snapshot?(Grove.Tree.t(), [Grove.Tree.Event.t()]) :: boolean()
Returns whether a snapshot should be created.