Skip to main content

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}