pub struct EventJournal { /* private fields */ }Expand description
Per-view append-only event log with bounded retention.
Implementations§
Source§impl EventJournal
impl EventJournal
pub fn new(config: JournalConfig) -> Self
pub fn config(&self) -> &JournalConfig
pub fn is_enabled(&self) -> bool
pub async fn epoch(&self) -> JournalEpoch
Sourcepub async fn append_with<E>(
&self,
view_id: &str,
key: &str,
build_frame: impl FnOnce(u64) -> Result<Arc<Bytes>, E>,
) -> Result<Option<(u64, Arc<Bytes>)>, E>
pub async fn append_with<E>( &self, view_id: &str, key: &str, build_frame: impl FnOnce(u64) -> Result<Arc<Bytes>, E>, ) -> Result<Option<(u64, Arc<Bytes>)>, E>
Append one published event, building its frame from the offset it is about to take.
Reserving and committing happen under one lock so the offset embedded in the published frame is always the offset the record takes. A live subscriber checkpoints that value, so a mismatch would hand it a cursor that means something else.
Sourcepub fn seal(&self)
pub fn seal(&self)
Record that events were lost before the tape resumed.
Called when the stream starts live over a hole: a restore that hydrates state without resuming, or an ingestion runtime that gave up on its checkpoint. The retained records stay valid, but everything between them and the first live append is missing, and dense offsets would otherwise present that hole as continuous. Stop issuing offsets, permanently.
The final snapshot is taken while the parser is still running — it has to be, or an update can be cut between its VM write and its batch — so publishing continues after the consistency cut releases, for as long as encoding and storing the snapshot takes. Offsets issued in that window are not in the file, and a restore that adopted the epoch would re-issue them for different records under cursors that still validate.
Sealing at the cut makes “a shutdown snapshot is offset-exact” true rather than assumed. Events after it still publish and still reach subscribers; they simply carry no cursor, so a consumer’s last position stays at the cut and the resume after restart replays them.
pub async fn mark_gap(&self)
Sourcepub async fn window(&self, view_id: &str) -> ReplayWindow
pub async fn window(&self, view_id: &str) -> ReplayWindow
Offsets this view can currently serve.
Prunes first: the age bound has to hold for a view that has gone quiet, otherwise expired records stay advertised and replayable.
Sourcepub async fn replay_after(
&self,
view_id: &str,
cursor: Option<&Cursor>,
) -> Result<Vec<JournalRecord>, ReplayError>
pub async fn replay_after( &self, view_id: &str, cursor: Option<&Cursor>, ) -> Result<Vec<JournalRecord>, ReplayError>
Every retained record strictly after cursor, in offset order.
A consumer that has seen nothing passes None, which replays the whole
retained window.
Sourcepub async fn dump(&self) -> JournalSnapshot
pub async fn dump(&self) -> JournalSnapshot
Durable form of the retained tape, for the snapshot payload.
Payload Arcs are cloned, not the bytes, so this stays cheap inside
the snapshot’s consistency guard.
Sourcepub async fn hydrate(&self, snapshot: JournalSnapshot, exact: bool)
pub async fn hydrate(&self, snapshot: JournalSnapshot, exact: bool)
Restore a dumped tape, so the advertised replay window survives a restart and a consumer’s cursor stays meaningful.
exact says whether the snapshot’s offsets are exactly what was
published — true only for a snapshot taken at shutdown, under the
consistency cut with nothing in flight. A periodic snapshot can be up
to its write interval behind, so restoring one rewinds next_offset
below offsets that have already been on the wire. Keeping the epoch
there would re-issue those offsets for different records under a
cursor that still validates: refused at first because the window has
not caught up, then silently served once it has. A fresh epoch makes
those cursors fail closed instead, which is the honest answer — after
a rewind they genuinely cannot be honoured.
A snapshot without an epoch predates cursor epochs and is discarded — adopting it under a fresh epoch would be indistinguishable from a cold start anyway, and adopting its offsets under this tape’s epoch would validate cursors that should fail.
Retention is re-applied on load: a snapshot restored after a long outage must not advertise records the age bound has already retired.
Sourcepub async fn entry_counts(&self) -> Vec<(String, u64)>
pub async fn entry_counts(&self) -> Vec<(String, u64)>
Retained record count per view, for snapshot diagnostics.
Sourcepub async fn retained_bytes(&self) -> Vec<(String, u64)>
pub async fn retained_bytes(&self) -> Vec<(String, u64)>
Retained bytes per view, for capacity reporting.
Source§impl EventJournal
impl EventJournal
Sourcepub async fn scope<F>(self: &Arc<Self>, future: F) -> F::Outputwhere
F: Future,
pub async fn scope<F>(self: &Arc<Self>, future: F) -> F::Outputwhere
F: Future,
Run the generated ingestion runtime with this server’s tape in scope, so it can report a stream discontinuity without being handed a journal it has no other use for.
Separate from the snapshot scope: the tape can be enabled with snapshots off, and that combination is exactly the one where a lost checkpoint has no other way to become visible.