openpine_vm/snapshot/mod.rs
1//! Save and restore VM instance state.
2//!
3//! This module provides [`Instance::save_state`] and
4//! [`Instance::restore_state`] for pausing execution at bar N and resuming
5//! from bar N+1 later.
6//!
7//! # Server-side best practices
8//!
9//! In a server deployment, each running script typically processes thousands
10//! of historical bars before reaching the live feed. Replaying all historical
11//! bars on every restart is expensive. Snapshots let you checkpoint the VM
12//! state after the historical replay, then reload it instantly on the next
13//! start.
14//!
15//! ## Recommended workflow
16//!
17//! ```text
18//! Cold start
19//! │
20//! ├─ Build Instance (InstanceBuilder)
21//! ├─ Feed all historical bars (instance.run_bar(…))
22//! ├─ Call instance.save_state() → Vec<u8>
23//! ├─ Persist the bytes (database, object storage, local disk, …)
24//! └─ Continue feeding live bars
25//!
26//! Warm restart
27//! │
28//! ├─ Load the persisted bytes
29//! ├─ Instance::restore_state(&bytes)?.build()?
30//! └─ Continue feeding live bars from the next bar index
31//! ```
32//!
33//! ## When to save
34//!
35//! Save once after the last confirmed historical bar — i.e. after the final
36//! bar for which the data will never change — and resave whenever you want to
37//! advance the checkpoint. Prefer saving on a confirmed bar close rather than
38//! mid-bar: intrabar data can still be revised, and on restart you will
39//! typically resume from the next complete bar rather than replaying the
40//! remaining intrabar updates of a partial bar.
41//!
42//! ## Versioning and invalidation
43//!
44//! Snapshots embed a format version (`SNAPSHOT_VERSION`) and the compiled
45//! [`Program`](openpine_compiler::program::Program). A snapshot is only valid
46//! for the exact binary that created it: a new OpenPine release, a changed
47//! script, or modified inputs all require discarding the snapshot and
48//! replaying history from scratch.
49//!
50//! A simple invalidation strategy: store the snapshot alongside a hash of the
51//! Pine source (or the compiled program bytes). On startup, compare the hash;
52//! if it differs, delete the snapshot and do a full cold replay.
53//!
54//! ## Example
55//!
56//! ```rust,ignore
57//! use openpine_vm::{Instance, snapshot::SnapshotError};
58//!
59//! // --- Cold start: replay history and checkpoint ---
60//! let mut instance = build_instance(script, symbol_info, timeframe);
61//! for bar in historical_bars {
62//! instance.run_bar(bar)?;
63//! }
64//! let snapshot: Vec<u8> = instance.save_state()?;
65//! persist_to_storage(&snapshot); // store to DB / disk / S3 / …
66//!
67//! // --- Warm restart: restore and continue ---
68//! let snapshot: Vec<u8> = load_from_storage();
69//! let mut instance = Instance::restore_state(&snapshot)?.build()?;
70//! for bar in live_bars { // resume from bar_index + 1
71//! instance.run_bar(bar)?;
72//! }
73//! ```
74
75pub(crate) mod restore;
76pub(crate) mod save;
77pub(crate) mod types;
78
79pub use restore::RestoreBuilder;
80pub use types::SnapshotError;