candystore 1.0.0

A lean, efficient and fast persistent in-process key-value store
Documentation
use candystore::{CandyStore, Config};
use std::time::Duration;

const ROW_WIDTH: u64 = (16 * 21) as u64;

#[test]
fn test_metrics_updates() -> Result<(), Box<dyn std::error::Error>> {
    let dir = tempfile::tempdir()?;
    let config = Config {
        initial_capacity: 1000,
        ..Default::default()
    };

    let db = CandyStore::open(dir.path(), config)?;

    let stats = db.stats();
    assert_eq!(stats.num_rows, 8);
    assert_eq!(stats.index_capacity(), 8 * ROW_WIDTH);
    assert_eq!(stats.num_items, 0);
    assert_eq!(stats.num_positive_lookups, 0);
    assert_eq!(stats.num_negative_lookups, 0);
    assert_eq!(stats.num_collisions, 0);
    assert_eq!(stats.last_remap_dur, Duration::ZERO);
    assert_eq!(stats.last_compaction_dur, Duration::ZERO);
    assert_eq!(stats.last_compaction_reclaimed_bytes, 0);
    assert_eq!(stats.last_compaction_moved_bytes, 0);
    assert_eq!(stats.num_read_ops, 0);
    assert_eq!(stats.num_read_bytes, 0);
    assert_eq!(stats.num_write_ops, 0);
    assert_eq!(stats.num_write_bytes, 0);
    assert_eq!(stats.num_inserted, 0);
    assert_eq!(stats.num_removed, 0);
    assert_eq!(stats.num_updated, 0);
    assert_eq!(stats.num_rebuilt_entries, 0);
    assert_eq!(stats.num_rebuild_purged_bytes, 0);
    assert_eq!(stats.data_bytes(), 0);
    assert_eq!(stats.waste_bytes, 0);

    db.set("key1", "val1")?;

    let stats = db.stats();
    assert_eq!(stats.num_items, 1);
    assert_eq!(stats.num_inserted, 1);
    assert_eq!(stats.num_updated, 0);
    assert_eq!(stats.num_removed, 0);
    assert_eq!(stats.num_inserted, 1);
    assert_eq!(stats.num_removed, 0);
    assert_eq!(stats.num_updated, 0);
    assert!(stats.data_bytes() > 0);
    assert_eq!(stats.waste_bytes, 0);
    assert_eq!(stats.num_write_ops, 1);
    assert!(stats.num_write_bytes > 0);
    assert!(stats.index_size_bytes > 0);
    assert_eq!(stats.num_data_files, 1);
    assert!(stats.data_bytes() > 0);

    db.set("key1", "val2")?;

    let stats = db.stats();
    assert_eq!(stats.num_items, 1);
    assert_eq!(stats.num_updated, 1);
    assert_eq!(stats.num_inserted, 1);
    assert_eq!(stats.num_updated, 1);
    assert_eq!(stats.num_removed, 0);
    assert!(stats.data_bytes() > 0);
    assert!(stats.waste_bytes > 0);
    assert_eq!(stats.num_write_ops, 2);

    db.remove("key1")?;

    let stats = db.stats();
    assert_eq!(stats.num_items, 0);
    assert_eq!(stats.num_removed, 1);
    assert!(stats.waste_bytes > 0);
    assert_eq!(stats.num_write_ops, 3);
    assert_eq!(stats.num_inserted, 1);
    assert_eq!(stats.num_updated, 1);
    assert_eq!(stats.num_removed, 1);
    assert_eq!(stats.data_bytes(), 0);

    assert_eq!(db.get("missing")?, None);
    assert_eq!(db.get("key1")?, None);

    let stats = db.stats();
    assert_eq!(stats.num_positive_lookups, 0);
    assert_eq!(stats.num_negative_lookups, 2);
    assert_eq!(stats.num_read_ops, 2);
    assert!(stats.num_read_bytes > 0);

    Ok(())
}

#[test]
fn test_metrics_compaction() -> Result<(), Box<dyn std::error::Error>> {
    let dir = tempfile::tempdir()?;
    let config = Config {
        max_data_file_size: 4096,
        compaction_min_threshold: 10,
        ..Default::default()
    };

    let db = CandyStore::open(dir.path(), config)?;

    for i in 0..500 {
        db.set(
            "key",
            format!("value_that_is_long_enough_to_take_up_space_{}", i),
        )?;
    }

    for i in 0..100 {
        db.set(format!("other_key_{}", i), "val")?;
        std::thread::sleep(std::time::Duration::from_millis(2));
    }

    let stats = db.stats();
    assert!(stats.num_updated > 0);
    assert!(stats.data_bytes() > 0);
    assert!(stats.num_items > 0);
    assert!(stats.index_capacity() >= stats.num_items);
    assert!(stats.num_write_ops > 0);
    assert!(stats.num_write_bytes > 0);

    Ok(())
}