perf(console): decouple invocation telemetry writes
Move Axum management and gateway adapters into a dedicated module while preserving the existing public routers and response contracts. Replace per-invocation full snapshots with a bounded asynchronous writer that batches metric deltas and events. Serialize it with control snapshots, flush before lifecycle persistence and graceful shutdown, and keep invocation results independent from telemetry failures. Enable the Runtime compilation cache beside the Console artifact directory and verify metric recovery across a lifecycle flush.
This commit is contained in:
@@ -54,6 +54,15 @@ pub(crate) struct StoredKvEntry {
|
||||
pub updated_at_ms: u64,
|
||||
}
|
||||
|
||||
/// One invocation's durable metric delta and event.
|
||||
pub(crate) struct InvocationUpdate {
|
||||
pub service_key: String,
|
||||
pub failed: bool,
|
||||
pub updated_at_ms: u64,
|
||||
pub latency_ms: u64,
|
||||
pub event: StoredEvent,
|
||||
}
|
||||
|
||||
/// Serialized access to the Toasty database connection.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Persistence {
|
||||
@@ -131,24 +140,42 @@ impl Persistence {
|
||||
let mut db = self.db.lock().await;
|
||||
let mut tx = db.transaction().await?;
|
||||
|
||||
let stored_services = StoredService::all().exec(&mut tx).await?;
|
||||
let service_keys = services
|
||||
.iter()
|
||||
.map(|service| service.service_key.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
for stored in StoredService::all().exec(&mut tx).await? {
|
||||
let existing_service_keys = stored_services
|
||||
.iter()
|
||||
.map(|service| service.service_key.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
for stored in stored_services {
|
||||
if !service_keys.contains(&stored.service_key) {
|
||||
StoredService::delete_by_service_key(&mut tx, &stored.service_key).await?;
|
||||
}
|
||||
}
|
||||
for service in services {
|
||||
StoredService::upsert_by_service_key(service.service_key)
|
||||
.manifest_toml(service.manifest_toml)
|
||||
.updated_at_ms(service.updated_at_ms)
|
||||
.calls(service.calls)
|
||||
.errors(service.errors)
|
||||
.last_latency_ms(service.last_latency_ms)
|
||||
.exec(&mut tx)
|
||||
.await?;
|
||||
if existing_service_keys.contains(&service.service_key) {
|
||||
// Invocation metrics are written as ordered deltas by
|
||||
// `record_invocations`; control snapshots must not overwrite
|
||||
// or double-count those concurrent updates.
|
||||
StoredService::filter_by_service_key(&service.service_key)
|
||||
.update()
|
||||
.manifest_toml(service.manifest_toml)
|
||||
.updated_at_ms(service.updated_at_ms)
|
||||
.exec(&mut tx)
|
||||
.await?;
|
||||
} else {
|
||||
StoredService::create()
|
||||
.service_key(service.service_key)
|
||||
.manifest_toml(service.manifest_toml)
|
||||
.updated_at_ms(service.updated_at_ms)
|
||||
.calls(0)
|
||||
.errors(0)
|
||||
.last_latency_ms(None)
|
||||
.exec(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(oldest_event) = events.last() {
|
||||
@@ -189,6 +216,53 @@ impl Persistence {
|
||||
tx.commit().await
|
||||
}
|
||||
|
||||
/// Atomically records invocation deltas and their bounded event history.
|
||||
pub(crate) async fn record_invocations(
|
||||
&self,
|
||||
updates: Vec<InvocationUpdate>,
|
||||
) -> toasty::Result<()> {
|
||||
let mut db = self.db.lock().await;
|
||||
let mut tx = db.transaction().await?;
|
||||
let mut newest_event_id = 0;
|
||||
|
||||
for update in updates {
|
||||
if let Some(stored) = StoredService::filter_by_service_key(&update.service_key)
|
||||
.first()
|
||||
.exec(&mut tx)
|
||||
.await?
|
||||
{
|
||||
StoredService::filter_by_service_key(&update.service_key)
|
||||
.update()
|
||||
.calls(stored.calls.saturating_add(1))
|
||||
.errors(stored.errors.saturating_add(u64::from(update.failed)))
|
||||
.updated_at_ms(update.updated_at_ms)
|
||||
.last_latency_ms(Some(update.latency_ms))
|
||||
.exec(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
newest_event_id = newest_event_id.max(update.event.id);
|
||||
StoredEvent::upsert_by_id(update.event.id)
|
||||
.timestamp_ms(update.event.timestamp_ms)
|
||||
.kind(update.event.kind)
|
||||
.service_id(update.event.service_id)
|
||||
.revision(update.event.revision)
|
||||
.message(update.event.message)
|
||||
.exec(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let oldest_retained_event_id = newest_event_id.saturating_sub(255);
|
||||
if oldest_retained_event_id > 0 {
|
||||
StoredEvent::filter(StoredEvent::fields().id().lt(oldest_retained_event_id))
|
||||
.delete()
|
||||
.exec(&mut tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
tx.commit().await
|
||||
}
|
||||
|
||||
pub(crate) async fn kv_get(
|
||||
&self,
|
||||
service_id: &str,
|
||||
|
||||
Reference in New Issue
Block a user