Skip to content

Basic Usage

Complete examples for building and reading SSTables.


Minimal Example

use nori_sstable::{Entry, SSTableBuilder, SSTableReader, SSTableConfig};
use std::path::PathBuf;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Build
    let config = SSTableConfig {
        path: PathBuf::from("data.sst"),
        estimated_entries: 100,
        ..Default::default()
    };

    let mut builder = SSTableBuilder::new(config).await?;
    builder.add(&Entry::put("alice", "engineer")).await?;
    builder.add(&Entry::put("bob", "designer")).await?;
    builder.add(&Entry::put("carol", "manager")).await?;
    builder.finish().await?;

    // Read
    let reader = Arc::new(SSTableReader::open(PathBuf::from("data.sst")).await?);
    if let Some(entry) = reader.get(b"bob").await? {
        println!("bob is a {}", String::from_utf8_lossy(&entry.value));
    }

    Ok(())
}

Building with Sorted Data

use std::collections::BTreeMap;

let mut data = BTreeMap::new();
data.insert(b"user:1".to_vec(), b"alice".to_vec());
data.insert(b"user:2".to_vec(), b"bob".to_vec());
data.insert(b"user:3".to_vec(), b"carol".to_vec());

let mut builder = SSTableBuilder::new(config).await?;
for (key, value) in data {
    builder.add(&Entry::put(key, value)).await?;
}
let metadata = builder.finish().await?;

println!("Built SSTable:");
println!("  Entries: {}", metadata.entry_count);
println!("  Size: {} bytes", metadata.file_size);
println!("  Blocks: {}", metadata.block_count);

Reading with Error Handling

match reader.get(b"nonexistent").await {
    Ok(Some(entry)) => {
        if entry.tombstone {
            println!("Key was deleted");
        } else {
            println!("Found: {:?}", entry.value);
        }
    }
    Ok(None) => println!("Key not found"),
    Err(e) => eprintln!("Error: {}", e),
}

Range Scanning

use futures::TryStreamExt;

let mut iter = reader.iter_range(
    Bytes::from("user:1"),
    Bytes::from("user:9")
);

while let Some(entry) = iter.try_next().await? {
    println!("{:?} = {:?}",
        String::from_utf8_lossy(&entry.key),
        String::from_utf8_lossy(&entry.value)
    );
}

With Compression

use nori_sstable::Compression;

let config = SSTableConfig {
    path: PathBuf::from("compressed.sst"),
    estimated_entries: 1000,
    compression: Compression::Lz4,
    ..Default::default()
};

let mut builder = SSTableBuilder::new(config).await?;
// Add entries...
builder.finish().await?;

// Reader automatically detects compression
let reader = Arc::new(SSTableReader::open("compressed.sst".into()).await?);

Custom Cache Size

use nori_observe::NoopMeter;

// 256MB cache for hot workloads
let reader = SSTableReader::open_with_config(
    PathBuf::from("data.sst"),
    Arc::new(NoopMeter),
    256  // MB
).await?;

Batch Building

let entries = vec![
    Entry::put("key1", "value1"),
    Entry::put("key2", "value2"),
    Entry::put("key3", "value3"),
];

let mut builder = SSTableBuilder::new(config).await?;
for entry in entries {
    builder.add(&entry).await?;
}
builder.finish().await?;

Concurrent Reads

let reader = Arc::new(SSTableReader::open("data.sst".into()).await?);

let mut handles = vec![];
for i in 0..10 {
    let r = reader.clone();
    let handle = tokio::spawn(async move {
        let key = format!("key{}", i);
        r.get(key.as_bytes()).await
    });
    handles.push(handle);
}

for handle in handles {
    let result = handle.await??;
    println!("Result: {:?}", result);
}