nautilus_event_store/backend/mod.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Backend abstraction for the event store.
17//!
18//! The [`EventStore`] trait keeps backends interchangeable: in-memory under simulation,
19//! `redb` in production, and any future swap (custom WAL, segmented log) without touching
20//! consumers. Backend-specific types (`redb::Database`, `redb::Error`) never appear in this
21//! trait surface.
22
23pub mod memory;
24pub mod redb;
25
26pub use memory::MemoryBackend;
27pub use redb::RedbBackend;
28
29use crate::{
30 entry::EventStoreEntry,
31 error::EventStoreError,
32 manifest::{RunManifest, RunStatus},
33 snapshot::SnapshotAnchor,
34};
35
36/// The kind of secondary index the reader can look up.
37///
38/// Indices are rebuildable projections from the canonical `seq -> entry` table, not
39/// authoritative storage. The verifier rebuilds them and cross-checks against the stored
40/// rows.
41#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
42pub enum IndexKind {
43 /// `client_order_id -> seq`. Looks up the first entry that mentions a client order id.
44 ClientOrderId,
45 /// `venue_order_id -> seq`. Looks up the first entry that mentions a venue order id.
46 VenueOrderId,
47}
48
49/// Direction for a sequence-keyed scan.
50#[derive(Copy, Clone, Debug, PartialEq, Eq)]
51pub enum ScanDirection {
52 /// Forward scan: ascending `seq`.
53 Forward,
54 /// Reverse scan: descending `seq`.
55 Reverse,
56}
57
58/// A single sidecar index entry recorded atomically with an event store entry.
59///
60/// The writer (or its encoder) produces one [`IndexKey`] per `(IndexKind, key)` pair the
61/// entry should be locatable under. The backend records the first occurrence of each pair;
62/// subsequent occurrences for the same `(kind, key)` are no-ops, so [`EventStore::lookup`]
63/// always returns the earliest `seq` that mentioned the key.
64///
65/// Keys are stringified at the encoder boundary: `client_order_id` and `venue_order_id` are
66/// already strings on the wire types.
67#[derive(Clone, Debug, PartialEq, Eq, Hash)]
68pub struct IndexKey {
69 /// The secondary index this key belongs to.
70 pub kind: IndexKind,
71 /// The stringified key. Owned because the backend retains it.
72 pub key: String,
73}
74
75impl IndexKey {
76 /// Creates a new [`IndexKey`].
77 #[must_use]
78 pub const fn new(kind: IndexKind, key: String) -> Self {
79 Self { kind, key }
80 }
81}
82
83/// One entry plus its sidecar index keys, as accepted by [`EventStore::append_batch`].
84///
85/// Keeping the indices alongside the entry makes the commit atomic: the backend records
86/// the entry and its index keys in a single transaction, so a reader can never observe a
87/// committed `seq` whose secondary indices are missing.
88#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct AppendEntry {
90 /// The captured entry. The writer has already assigned `seq`, `ts_publish`, and the
91 /// canonical `entry_hash` before constructing this value.
92 pub entry: EventStoreEntry,
93 /// Sidecar index keys produced for this entry. May be empty.
94 pub index_keys: Vec<IndexKey>,
95}
96
97impl AppendEntry {
98 /// Creates a new [`AppendEntry`].
99 #[must_use]
100 pub const fn new(entry: EventStoreEntry, index_keys: Vec<IndexKey>) -> Self {
101 Self { entry, index_keys }
102 }
103
104 /// Creates a new [`AppendEntry`] with no sidecar index keys.
105 #[must_use]
106 pub const fn without_indices(entry: EventStoreEntry) -> Self {
107 Self {
108 entry,
109 index_keys: Vec::new(),
110 }
111 }
112}
113
114/// The single-node embedded event store.
115///
116/// One backend instance owns one open run. Writes funnel through `append_batch`; reads are
117/// `seq`-keyed scans or single-key lookups across the secondary indices.
118///
119/// Backends are responsible for:
120///
121/// - Per-run organization on disk (one redb file per run, one in-memory log per run).
122/// - Durable commit semantics (`Durability::Immediate` for redb).
123/// - High-watermark advance after commit acknowledgement.
124/// - Mapping backend-specific errors onto [`EventStoreError`].
125///
126/// This trait does not own batching or thread-management policy: the writer
127/// (`crates/event_store/src/writer/`) is the dedicated thread that batches entries and
128/// invokes `append_batch` against the backend.
129pub trait EventStore: Send {
130 /// Opens an existing run or creates a new one with the supplied manifest.
131 ///
132 /// On reopening a run whose status is [`RunStatus::Running`] without a `RunEnded`
133 /// entry, the backend returns [`EventStoreError::CrashedPredecessor`] so the kernel
134 /// can seal it before opening a new run.
135 ///
136 /// # Errors
137 ///
138 /// Returns [`EventStoreError::Backend`] for unclassified backend failures,
139 /// [`EventStoreError::Corrupted`] for header-region damage, and
140 /// [`EventStoreError::Disk`] for disk pressure during creation.
141 fn open_run(&mut self, manifest: RunManifest) -> Result<(), EventStoreError>;
142
143 /// Appends a batch of `(entry, index_keys)` pairs in a single backend transaction.
144 ///
145 /// The writer assigns `seq`, `ts_publish`, and the canonical `entry_hash`, plus any
146 /// sidecar [`IndexKey`]s the encoder produced, before constructing each
147 /// [`AppendEntry`]. The backend rejects batches whose first `seq` is not exactly
148 /// `high_watermark + 1`, and whose subsequent seqs are not contiguous (each successor
149 /// is `prev + 1`).
150 ///
151 /// On successful commit, the backend advances its high-watermark and returns the new
152 /// value.
153 ///
154 /// # Errors
155 ///
156 /// Returns [`EventStoreError::Closed`] when the run is sealed,
157 /// [`EventStoreError::OutOfOrder`] when the first seq is not exactly
158 /// `high_watermark + 1` or a within-batch seq is not contiguous,
159 /// [`EventStoreError::Disk`] when the backing storage refuses the write, and
160 /// [`EventStoreError::Backend`] for unclassified backend failures.
161 fn append_batch(&mut self, entries: &[AppendEntry]) -> Result<u64, EventStoreError>;
162
163 /// Scans entries by `seq` over the inclusive range `[from, to]`.
164 ///
165 /// Backends may stream rows lazily; the trait's vector return is a simple
166 /// implementation default for the in-memory backend and the verifier hot path. The
167 /// reader (`crates/event_store/src/reader/`) wraps this with iterator semantics.
168 ///
169 /// # Errors
170 ///
171 /// Returns [`EventStoreError::HashMismatch`] when a row's recomputed hash diverges
172 /// from the stored value, [`EventStoreError::Gap`] when the scan observes a missing
173 /// `seq`, and [`EventStoreError::Backend`] for unclassified backend failures.
174 fn scan_range(
175 &self,
176 from: u64,
177 to: u64,
178 direction: ScanDirection,
179 ) -> Result<Vec<EventStoreEntry>, EventStoreError>;
180
181 /// Reads a single entry by `seq`.
182 ///
183 /// # Errors
184 ///
185 /// See [`EventStore::scan_range`].
186 fn scan_seq(&self, seq: u64) -> Result<Option<EventStoreEntry>, EventStoreError>;
187
188 /// Looks up the first `seq` recorded under the given index key.
189 ///
190 /// Returns `None` when the key has not been observed.
191 ///
192 /// # Errors
193 ///
194 /// Returns [`EventStoreError::Backend`] for unclassified backend failures.
195 fn lookup(&self, kind: IndexKind, key: &str) -> Result<Option<u64>, EventStoreError>;
196
197 /// Enumerates every `(key, seq)` pair stored under the given secondary index.
198 ///
199 /// Used by the verifier to cross-check the stored sidecar indices against the
200 /// projection rebuilt from the entry table. The returned vector's order is
201 /// backend-defined; callers that need a stable comparison sort it themselves.
202 ///
203 /// # Errors
204 ///
205 /// Returns [`EventStoreError::Backend`] for unclassified backend failures.
206 fn iter_index_keys(&self, kind: IndexKind) -> Result<Vec<(String, u64)>, EventStoreError>;
207
208 /// Records the latest cache snapshot anchor for the open run.
209 ///
210 /// The anchor's high-watermark must be less than or equal to the durable
211 /// high-watermark and must not move backward relative to the latest recorded
212 /// anchor. Backends persist the anchor independently from the snapshot blob; the
213 /// cache owns the blob and content-hash calculation.
214 ///
215 /// # Errors
216 ///
217 /// Returns [`EventStoreError::Backend`] when no run is open, when the backend does
218 /// not support snapshot anchors, or when the anchor is invalid for the current
219 /// high-watermark; returns [`EventStoreError::Closed`] when the run is sealed.
220 fn record_snapshot_anchor(&mut self, _anchor: SnapshotAnchor) -> Result<(), EventStoreError> {
221 Err(EventStoreError::Backend(
222 "snapshot anchors are not supported by this backend".to_string(),
223 ))
224 }
225
226 /// Returns the latest recorded snapshot anchor for the open run.
227 ///
228 /// Returns `Ok(None)` when no snapshot anchor has been recorded yet. The default
229 /// implementation also returns `Ok(None)`: a backend without anchor support
230 /// truthfully has no anchor to report, and consumers (the verifier, retention
231 /// planning) must be able to distinguish "no anchor" from a real read failure.
232 ///
233 /// # Errors
234 ///
235 /// Returns [`EventStoreError::Backend`] when no run is open and
236 /// [`EventStoreError::Corrupted`] when a stored anchor fails to decode.
237 fn latest_snapshot_anchor(&self) -> Result<Option<SnapshotAnchor>, EventStoreError> {
238 Ok(None)
239 }
240
241 /// Seals the open run with the given final status and persists the manifest update.
242 ///
243 /// Subsequent calls to [`EventStore::append_batch`] return [`EventStoreError::Closed`].
244 ///
245 /// # Errors
246 ///
247 /// Returns [`EventStoreError::Backend`] for unclassified backend failures and
248 /// [`EventStoreError::Disk`] for disk pressure during the seal commit.
249 fn seal(&mut self, status: RunStatus) -> Result<(), EventStoreError>;
250
251 /// Returns the current manifest snapshot.
252 ///
253 /// # Errors
254 ///
255 /// Returns [`EventStoreError::Backend`] when no run is open.
256 fn manifest(&self) -> Result<RunManifest, EventStoreError>;
257
258 /// Returns the largest `seq` durably acknowledged for the open run.
259 ///
260 /// Returns `0` when no entries have been committed yet (the writer assigns `seq`
261 /// starting at `1`).
262 ///
263 /// # Errors
264 ///
265 /// Returns [`EventStoreError::Backend`] when no run is open.
266 fn high_watermark(&self) -> Result<u64, EventStoreError>;
267}