docs(console): clarify API and persistence boundaries

This commit is contained in:
Maofeng
2026-07-30 08:13:33 +08:00
parent aea42ba0a6
commit 223ead37c8
7 changed files with 265 additions and 4 deletions
+31
View File
@@ -3,6 +3,15 @@
//! Component bytes and WIT packages remain filesystem artifacts. This module
//! persists service manifests, deployments, metrics, the bounded event
//! history, and service-scoped Host KV entries.
//!
//! There are two write paths:
//!
//! - [`Persistence::sync`] stores a complete control-plane snapshot.
//! - [`Persistence::record_invocations`] applies ordered metric deltas.
//!
//! Callers serialize these paths with the Console persistence mutex. Existing
//! service counters are intentionally owned by the delta path; a snapshot
//! updates their manifest and timestamp without replacing those counters.
use std::{
collections::BTreeSet,
@@ -55,6 +64,10 @@ pub(crate) struct StoredKvEntry {
}
/// One invocation's durable metric delta and event.
///
/// The caller assigns the event ID before enqueueing the update. A missing
/// service row is tolerated because unregister may race queued telemetry; the
/// event is still retained for diagnostics.
pub(crate) struct InvocationUpdate {
pub service_key: String,
pub failed: bool,
@@ -64,6 +77,10 @@ pub(crate) struct InvocationUpdate {
}
/// Serialized access to the Toasty database connection.
///
/// This mutex protects a single Toasty [`Db`] handle. It is separate from the
/// higher-level Console persistence mutex, which coordinates whole snapshot
/// and invocation-delta operations before they acquire this connection.
#[derive(Clone)]
pub(crate) struct Persistence {
db: Arc<Mutex<Db>>,
@@ -131,6 +148,11 @@ impl Persistence {
}
/// Atomically upserts a Console snapshot and prunes expired events.
///
/// The input is authoritative for services and deployments: rows omitted
/// from those collections are removed. Events older than the oldest
/// supplied retained event are pruned. Existing invocation counters are
/// preserved because [`Self::record_invocations`] owns counter changes.
pub async fn sync(
&self,
services: Vec<StoredService>,
@@ -178,6 +200,8 @@ impl Persistence {
}
}
// Console stores events newest-first, so the last element is the
// oldest ID that should survive snapshot pruning.
if let Some(oldest_event) = events.last() {
StoredEvent::filter(StoredEvent::fields().id().lt(oldest_event.id))
.delete()
@@ -217,6 +241,9 @@ impl Persistence {
}
/// Atomically records invocation deltas and their bounded event history.
///
/// All supplied updates commit together. Metrics use saturating arithmetic
/// so corrupted or extremely old databases cannot wrap counters.
pub(crate) async fn record_invocations(
&self,
updates: Vec<InvocationUpdate>,
@@ -268,6 +295,8 @@ impl Persistence {
service_id: &str,
entry_key: &str,
) -> toasty::Result<Option<Vec<u8>>> {
// The namespace is the stable service ID rather than its revision.
// Deploying a new revision therefore preserves application state.
let mut db = self.db.lock().await;
Ok(
StoredKvEntry::filter_by_service_id_and_entry_key(service_id, entry_key)
@@ -284,6 +313,8 @@ impl Persistence {
entry_key: String,
value: Vec<u8>,
) -> toasty::Result<()> {
// Upsert makes a retry after an ambiguous Host timeout idempotent for
// the same `(service_id, entry_key, value)`.
let mut db = self.db.lock().await;
StoredKvEntry::upsert_by_service_id_and_entry_key(service_id, entry_key)
.value(value)