use seerdb::DBOptions;
use std::path::PathBuf;
use tempfile::TempDir;
#[test]
#[ignore] fn test_memory_pressure_80_percent_trigger() {
let temp_dir = TempDir::new().unwrap();
let data_dir = PathBuf::from(temp_dir.path());
let db = DBOptions::default()
.max_memory_bytes(Some(100 * 1024 * 1024))
.memtable_capacity(20 * 1024 * 1024)
.background_flush(true)
.open(&data_dir)
.unwrap();
let initial_memory = db.estimate_memory_usage();
println!("Initial memory: {} bytes", initial_memory);
let mut wrote_count = 0;
for i in 0..80_000 {
let key = format!("key_{:010}", i);
let value = vec![b'x'; 1000];
db.put(key.as_bytes(), &value).unwrap();
wrote_count += 1;
if i % 5000 == 0 && i > 0 {
let mem = db.estimate_memory_usage();
let pressure = (mem as f64) / (100.0 * 1024.0 * 1024.0);
println!(
"After {} writes: {} MB ({:.1}% pressure)",
i,
mem / 1024 / 1024,
pressure * 100.0
);
if pressure >= 0.95 {
panic!(
"Memory pressure exceeded 95% ({:.1}%) - backpressure failed",
pressure * 100.0
);
}
}
}
println!("Successfully wrote {} operations without OOM", wrote_count);
db.flush().unwrap();
let stats = db.stats();
println!("\n=== DB Stats after flush ===");
println!("Total flushes: {}", stats.total_flushes);
println!("Total puts: {}", stats.total_puts);
println!(
"Memory usage: {} MB",
db.estimate_memory_usage() / 1024 / 1024
);
println!("\n=== SSTable files on disk ===");
let mut sstable_paths = Vec::new();
if let Ok(entries) = std::fs::read_dir(&temp_dir) {
for entry in entries {
if let Ok(entry) = entry {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("sst") {
let size = std::fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
println!(" {:?}: {} bytes", path.file_name().unwrap(), size);
sstable_paths.push(path);
}
}
}
}
println!("\n=== Manually checking SSTables for key_0000000000 ===");
use seerdb::sstable::SSTable;
for sst_path in &sstable_paths {
match SSTable::open(sst_path) {
Ok(mut sst) => {
let contains = sst.contains(b"key_0000000000").unwrap_or(false);
println!(
" {:?}: {}",
sst_path.file_name().unwrap(),
if contains { "FOUND" } else { "NOT FOUND" }
);
}
Err(e) => {
println!(
" {:?}: ERROR opening - {}",
sst_path.file_name().unwrap(),
e
);
}
}
}
for i in (0..wrote_count as i32).step_by(10000) {
let key = format!("key_{:010}", i);
let result = db.get(key.as_bytes()).unwrap();
if result.is_none() {
println!("\n=== MISSING KEY: {} (write #{}) ===", key, i);
for j in (i.saturating_sub(5))..=(i + 5) {
let test_key = format!("key_{:010}", j);
let test_result = db.get(test_key.as_bytes()).unwrap();
println!(
" key_{:010}: {}",
j,
if test_result.is_some() {
"FOUND"
} else {
"MISSING"
}
);
}
}
assert!(
result.is_some(),
"Key {} should exist after memory pressure test",
key
);
}
}
#[test]
#[ignore] fn test_memory_pressure_no_oom() {
let temp_dir = TempDir::new().unwrap();
let data_dir = PathBuf::from(temp_dir.path());
let db = DBOptions::default()
.max_memory_bytes(Some(50 * 1024 * 1024))
.memtable_capacity(10 * 1024 * 1024)
.background_flush(true)
.open(&data_dir)
.unwrap();
let mut max_memory_seen = 0;
for i in 0..100_000 {
let key = format!("stress_{:010}", i);
let value = vec![b'y'; 500];
db.put(key.as_bytes(), &value).unwrap();
if i % 10000 == 0 {
let mem = db.estimate_memory_usage();
max_memory_seen = max_memory_seen.max(mem);
assert!(
mem < 60 * 1024 * 1024, "Memory usage {} exceeds safe limit",
mem
);
}
}
println!(
"Maximum memory observed: {} MB (limit: 50 MB)",
max_memory_seen / 1024 / 1024
);
db.flush().unwrap();
assert!(db.get(b"stress_0000000000").unwrap().is_some());
assert!(db.get(b"stress_0000099999").unwrap().is_some());
}
#[test]
#[ignore] fn test_memory_pressure_recovery() {
let temp_dir = TempDir::new().unwrap();
let data_dir = PathBuf::from(temp_dir.path());
let db = DBOptions::default()
.max_memory_bytes(Some(80 * 1024 * 1024))
.memtable_capacity(15 * 1024 * 1024)
.background_flush(true)
.open(&data_dir)
.unwrap();
for i in 0..50_000 {
let key = format!("phase1_{:010}", i);
let value = vec![b'a'; 1000];
db.put(key.as_bytes(), &value).unwrap();
}
let high_memory = db.estimate_memory_usage();
println!("High memory: {} MB", high_memory / 1024 / 1024);
std::thread::sleep(std::time::Duration::from_secs(2));
let recovered_memory = db.estimate_memory_usage();
println!("Recovered memory: {} MB", recovered_memory / 1024 / 1024);
assert!(
recovered_memory < high_memory / 2,
"Memory should recover after flush: {} > {}",
recovered_memory,
high_memory / 2
);
for i in 0..50_000 {
let key = format!("phase2_{:010}", i);
let value = vec![b'b'; 1000];
db.put(key.as_bytes(), &value).unwrap();
}
assert!(db.get(b"phase1_0000000000").unwrap().is_some());
assert!(db.get(b"phase2_0000000000").unwrap().is_some());
}
#[test]
fn test_memory_pressure_disabled() {
let temp_dir = TempDir::new().unwrap();
let data_dir = PathBuf::from(temp_dir.path());
let db = DBOptions::default()
.max_memory_bytes(None)
.open(&data_dir)
.unwrap();
for i in 0..50_000 {
let key = format!("unlimited_{:010}", i);
let value = vec![b'z'; 1000];
db.put(key.as_bytes(), &value).unwrap();
}
db.flush().unwrap();
assert!(db.get(b"unlimited_0000000000").unwrap().is_some());
}