perf: speed up startup from ~15s to ~2s (#280)

Three high-impact changes eliminate most startup latency:

1. Enable wasmtime persistent compilation cache — call
   cache_config_load_default() so compiled native code is serialized to
   disk (~/.cache/wasmtime). Subsequent startups deserialize instead of
   recompiling, dropping the WASM phase from ~13s to <1s.

2. Cache compiled Component in PreparedModule — store the compiled
   wasmtime::component::Component directly instead of raw bytes.
   Eliminates ~2.6s recompilation on every first tool/channel execution.

3. Move blocking housekeeping to background tasks — embedding backfill
   (~1.3s of failing HTTP calls) and stale job cleanup are fire-and-forget
   work that no longer blocks the critical startup path.

Also: deduplicate Workspace creation in main.rs (two identical instances
reduced to one), and replace leftover println! in session validation with
tracing calls.

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Illia Polosukhin
2026-02-21 02:30:57 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 2cdd1acb1e
commit 98ee648fcb
8 changed files with 130 additions and 81 deletions
+25 -8
View File
@@ -64,8 +64,8 @@ impl WasmRuntimeConfig {
/// A compiled WASM component ready for instantiation.
///
/// Contains the pre-compiled component plus cached metadata extracted
/// from the component during preparation.
#[derive(Debug)]
/// from the component during preparation. Stores the compiled `Component`
/// directly so instantiation doesn't require recompilation.
pub struct PreparedModule {
/// Tool name.
pub name: String,
@@ -73,16 +73,26 @@ pub struct PreparedModule {
pub description: String,
/// Parameter schema JSON (cached from component).
pub schema: serde_json::Value,
/// Compiled component bytes (can be serialized for caching).
component_bytes: Vec<u8>,
/// Pre-compiled component (cheaply cloneable via internal Arc).
component: wasmtime::component::Component,
/// Resource limits for this tool.
pub limits: ResourceLimits,
}
impl PreparedModule {
/// Get the compiled component bytes.
pub fn component_bytes(&self) -> &[u8] {
&self.component_bytes
/// Get the pre-compiled component for instantiation.
pub fn component(&self) -> &wasmtime::component::Component {
&self.component
}
}
impl std::fmt::Debug for PreparedModule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PreparedModule")
.field("name", &self.name)
.field("description", &self.description)
.field("limits", &self.limits)
.finish()
}
}
@@ -123,6 +133,13 @@ impl WasmToolRuntime {
// Disable debug info in production for smaller modules
wasmtime_config.debug_info(false);
// Enable persistent compilation cache. Wasmtime serializes compiled native
// code to disk (~/.cache/wasmtime by default), so subsequent startups
// deserialize instead of recompiling — typically 10-50x faster.
if let Err(e) = wasmtime_config.cache_config_load_default() {
tracing::warn!("Failed to enable wasmtime compilation cache: {}", e);
}
let engine = Engine::new(&wasmtime_config).map_err(|e| {
WasmError::EngineCreationFailed(format!("Failed to create Wasmtime engine: {}", e))
})?;
@@ -199,7 +216,7 @@ impl WasmToolRuntime {
name: name.clone(),
description,
schema,
component_bytes: wasm_bytes,
component,
limits: limits.unwrap_or(default_limits),
})
})
+3 -4
View File
@@ -13,7 +13,7 @@ use std::time::{Duration, Instant};
use async_trait::async_trait;
use wasmtime::Store;
use wasmtime::component::{Component, Linker};
use wasmtime::component::Linker;
use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};
use crate::context::JobContext;
@@ -581,9 +581,8 @@ impl WasmToolWrapper {
// Set up resource limiter
store.limiter(|data| &mut data.limiter);
// Compile the component (uses cached bytes)
let component = Component::new(engine, self.prepared.component_bytes())
.map_err(|e| WasmError::CompilationFailed(e.to_string()))?;
// Use the pre-compiled component (no recompilation needed)
let component = self.prepared.component().clone();
// Create linker with all host functions properly namespaced
let mut linker = Linker::new(engine);