pub struct TieredStore {
hot: Arc<DashMap<Blake3Hash, HotCacheEntry>>,
warm_backend: Box<dyn StorageBackend>,
cold_backend: Box<dyn StorageBackend>,
archive_after_days: u32,
}
impl TieredStore {
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
let warm_config = StorageConfig {
backend_type: crate::tdg::storage_backend::StorageBackendType::Libsql,
path: Some(db_path.as_ref().join(".pmat/tdg-warm.db")),
cache_size_mb: Some(128),
compression: true,
};
let cold_config = StorageConfig {
backend_type: crate::tdg::storage_backend::StorageBackendType::Libsql,
path: Some(db_path.as_ref().join(".pmat/tdg-cold.db")),
cache_size_mb: Some(64),
compression: false, };
Self::with_config(warm_config, cold_config)
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn with_config(warm_config: StorageConfig, cold_config: StorageConfig) -> Result<Self> {
let warm_backend = StorageBackendFactory::create_from_config(&warm_config)?;
let cold_backend = StorageBackendFactory::create_from_config(&cold_config)?;
Ok(Self {
hot: Arc::new(DashMap::new()),
warm_backend,
cold_backend,
archive_after_days: 30,
})
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn in_memory() -> Self {
Self {
hot: Arc::new(DashMap::new()),
warm_backend: StorageBackendFactory::create_in_memory(),
cold_backend: StorageBackendFactory::create_in_memory(),
archive_after_days: 30,
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn store(&self, record: FullTdgRecord) -> Result<()> {
let hash = record.identity.content_hash;
let hot_entry = HotCacheEntry::from_record(&record);
self.hot.insert(hash, hot_entry);
let serialized = serde_json::to_vec(&record)?;
let compressed = compress_prepend_size(&serialized);
self.warm_backend.put(hash.as_bytes(), &compressed)?;
if self.should_archive(&record) {
self.archive_to_cold(record).await?;
}
Ok(())
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_hot(&self, hash: &Blake3Hash) -> Option<HotCacheEntry> {
self.hot.get(hash).map(|entry| *entry.value())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn retrieve_full(&self, hash: &Blake3Hash) -> Result<Option<FullTdgRecord>> {
if let Some(compressed) = self.warm_backend.get(hash.as_bytes())? {
let decompressed = decompress_size_prepended(&compressed)?;
return Ok(Some(serde_json::from_slice(&decompressed)?));
}
if let Some(archived) = self.cold_backend.get(hash.as_bytes())? {
return Ok(Some(serde_json::from_slice(&archived)?));
}
Ok(None)
}
fn should_archive(&self, record: &FullTdgRecord) -> bool {
let age_days = record
.metadata
.analysis_timestamp
.elapsed()
.unwrap_or_default()
.as_secs()
/ (24 * 60 * 60);
age_days > u64::from(self.archive_after_days)
}
async fn archive_to_cold(&self, record: FullTdgRecord) -> Result<()> {
let hash = record.identity.content_hash;
let serialized = serde_json::to_vec(&record)?;
self.cold_backend.put(hash.as_bytes(), &serialized)?;
self.warm_backend.delete(hash.as_bytes())?;
Ok(())
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn cleanup_hot_cache(&self, max_age_seconds: u64) -> usize {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
let mut removed = 0;
self.hot.retain(|_, entry| {
let age = now - entry.timestamp;
if age > max_age_seconds as i64 {
removed += 1;
false
} else {
true
}
});
removed
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn migrate_backend(
&mut self,
new_warm_config: StorageConfig,
new_cold_config: StorageConfig,
) -> Result<()> {
let new_warm = StorageBackendFactory::create_from_config(&new_warm_config)?;
let new_cold = StorageBackendFactory::create_from_config(&new_cold_config)?;
if let Ok(iter) = self.warm_backend.iter() {
for result in iter {
let (key, value) = result?;
new_warm.put(&key, &value)?;
}
}
if let Ok(iter) = self.cold_backend.iter() {
for result in iter {
let (key, value) = result?;
new_cold.put(&key, &value)?;
}
}
self.warm_backend = new_warm;
self.cold_backend = new_cold;
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn flush(&self) -> Result<()> {
self.warm_backend.flush()?;
self.cold_backend.flush()?;
Ok(())
}
const COMPRESSION_SAMPLE_MAX_ENTRIES: usize = 4096;
fn measure_warm_compression_ratio(&self, warm_entries: usize) -> Option<f32> {
if warm_entries == 0 || warm_entries > Self::COMPRESSION_SAMPLE_MAX_ENTRIES {
return None;
}
let mut compressed_total: u64 = 0;
let mut raw_total: u64 = 0;
for item in self.warm_backend.iter().ok()? {
let Ok((_, value)) = item else { continue };
let Some(header) = value.get(..4) else {
continue;
};
raw_total += u64::from(u32::from_le_bytes([
header[0], header[1], header[2], header[3],
]));
compressed_total += value.len() as u64;
}
if raw_total == 0 {
return None;
}
#[allow(clippy::cast_precision_loss)]
Some(compressed_total as f32 / raw_total as f32)
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn get_statistics(&self) -> StorageStatistics {
let hot_entries = self.hot.len();
let hot_memory_kb = (hot_entries * std::mem::size_of::<HotCacheEntry>()) / 1024;
let warm_stats = self.warm_backend.get_stats();
let cold_stats = self.cold_backend.get_stats();
let warm_entries = warm_stats
.get(crate::tdg::storage_backend::STAT_KEY_ENTRIES)
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0);
let cold_entries = cold_stats
.get(crate::tdg::storage_backend::STAT_KEY_ENTRIES)
.and_then(|v| v.parse::<usize>().ok())
.unwrap_or(0);
let total_entries = hot_entries + warm_entries + cold_entries;
let mut backend_stats = HashMap::new();
backend_stats.insert("warm".to_string(), warm_stats);
backend_stats.insert("cold".to_string(), cold_stats);
StorageStatistics {
hot_entries,
warm_entries,
cold_entries,
total_entries,
hot_memory_kb,
compression_ratio: self
.measure_warm_compression_ratio(warm_entries)
.unwrap_or(0.0),
warm_backend: self.warm_backend.backend_name().to_string(),
cold_backend: self.cold_backend.backend_name().to_string(),
backend_stats,
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tiered_statistics_tests {
use super::*;
fn record_for(content: &[u8]) -> FullTdgRecord {
FullTdgRecord {
identity: FileIdentity {
path: PathBuf::from("test.rs"),
content_hash: blake3::hash(content),
size_bytes: content.len() as u64,
modified_time: SystemTime::now(),
},
score: TdgScore::default(),
components: ComponentScores::default(),
semantic_sig: SemanticSignature {
ast_structure_hash: 1,
identifier_pattern: String::new(),
control_flow_pattern: String::new(),
import_dependencies: Vec::new(),
},
metadata: AnalysisMetadata {
analyzer_version: "test".to_string(),
analysis_duration_ms: 1,
language_confidence: 1.0,
analysis_timestamp: SystemTime::now(),
cache_hit: false,
},
git_context: None,
}
}
#[tokio::test]
async fn test_warm_entries_match_the_backend_they_describe() {
let storage = TieredStore::in_memory();
storage.store(record_for(b"fn a() {}")).await.unwrap();
storage.store(record_for(b"fn b() {}")).await.unwrap();
let stats = storage.get_statistics();
let backend_entries: usize = stats.backend_stats["warm"]
[crate::tdg::storage_backend::STAT_KEY_ENTRIES]
.parse()
.unwrap();
assert_eq!(backend_entries, 2, "two records were stored");
assert_eq!(
stats.warm_entries, backend_entries,
"warm_entries must equal the backend count reported beside it"
);
assert_eq!(stats.total_entries, stats.hot_entries + backend_entries);
}
#[tokio::test]
async fn test_compression_ratio_is_measured_not_a_constant() {
let empty = TieredStore::in_memory();
assert_eq!(
empty.get_statistics().compression_ratio,
0.0,
"nothing is stored, so there is no ratio to report"
);
let storage = TieredStore::in_memory();
storage.store(record_for(b"fn a() {}")).await.unwrap();
let ratio = storage.get_statistics().compression_ratio;
assert!(
ratio > 0.0 && ratio < 1.0,
"expected a measured lz4 ratio, got {ratio}"
);
assert!(
(ratio - 0.33).abs() > f32::EPSILON,
"0.33 is the old hardcoded literal"
);
}
#[test]
fn test_backend_names_come_from_the_backends() {
let stats = TieredStore::in_memory().get_statistics();
assert_eq!(stats.warm_backend, "in-memory");
assert_eq!(stats.cold_backend, "in-memory");
}
}