use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::broadcast;
use crate::executor::execute_query;
use crate::parser::parse_query;
use crate::storage::StorageEngine;
use crate::types::Record;
#[derive(Debug, Clone)]
pub struct LivenConfig {
pub max_streams: usize,
pub max_index_ram_mb: usize,
pub max_segment_mb: usize,
pub max_open_fds: usize,
pub broadcast_capacity: usize,
pub compaction_threshold_segments: usize,
pub compaction_threshold_bytes: u64,
pub max_scan_results: usize,
}
impl Default for LivenConfig {
fn default() -> Self {
let max_index_ram_mb = if let Some(system_ram) = crate::sysinfo::detect_system_ram_mb() {
crate::sysinfo::calculate_auto_budget(system_ram) as usize
} else {
512 };
Self {
max_streams: 32,
max_index_ram_mb,
max_segment_mb: 16,
max_open_fds: 64,
broadcast_capacity: 4096,
compaction_threshold_segments: 4,
compaction_threshold_bytes: 64 * 1024 * 1024,
max_scan_results: 100_000,
}
}
}
pub struct Liven {
engine: Arc<StorageEngine>,
}
impl Liven {
pub fn open(path: impl AsRef<Path>) -> crate::error::Result<Self> {
Self::open_with_config(path, LivenConfig::default())
}
pub fn open_with_config(
path: impl AsRef<Path>,
config: LivenConfig,
) -> crate::error::Result<Self> {
let segment_size = config.max_segment_mb as u64 * 1024 * 1024;
let mut engine = StorageEngine::new(path, segment_size)?;
engine.set_max_streams(config.max_streams);
engine.set_max_index_ram_bytes(config.max_index_ram_mb as u64 * 1024 * 1024);
engine.set_max_fds(config.max_open_fds);
engine.set_max_scan_results(config.max_scan_results);
engine.compaction_threshold_segments = config.compaction_threshold_segments;
engine.compaction_threshold_bytes = config.compaction_threshold_bytes;
Ok(Self {
engine: Arc::new(engine),
})
}
pub fn query(&self, query_str: &str) -> crate::error::Result<Vec<Record>> {
let query = parse_query(query_str)?;
execute_query(&self.engine, &query)
}
pub fn subscribe(&self) -> broadcast::Receiver<Record> {
self.engine.subscribe()
}
pub fn subscribe_sync(&self, timeout: Duration) -> Result<Option<Record>, String> {
let mut rx = self.engine.subscribe();
match rx.try_recv() {
Ok(record) => Ok(Some(record)),
Err(broadcast::error::TryRecvError::Empty) => {
std::thread::sleep(timeout);
match rx.try_recv() {
Ok(record) => Ok(Some(record)),
Err(_) => Ok(None),
}
}
Err(broadcast::error::TryRecvError::Closed) => {
Err("Broadcast channel closed".to_string())
}
Err(broadcast::error::TryRecvError::Lagged(n)) => {
Err(format!("Subscriber lagged by {} messages", n))
}
}
}
pub fn engine(&self) -> Arc<StorageEngine> {
Arc::clone(&self.engine)
}
pub fn metrics(&self) -> crate::error::Result<(u64, u64, u64, usize)> {
self.engine.metrics()
}
pub fn compact(&self) -> crate::error::Result<()> {
self.engine.compact()
}
pub fn start_auto_compact(
&self,
handle: &tokio::runtime::Handle,
check_interval: std::time::Duration,
) {
let engine = self.engine.clone();
handle.spawn(async move {
let mut interval = tokio::time::interval(check_interval);
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
interval.tick().await;
if engine.should_compact() {
tracing::info!("Auto-compaction triggered");
let eng = engine.clone();
tokio::task::spawn_blocking(move || {
if let Err(e) = eng.compact() {
tracing::warn!("Auto-compaction failed: {}", e);
}
})
.await
.ok();
}
}
});
}
}