Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/skade

bench/examples/cache_ab.rs

//! Throwaway A/B: how much of `load_table` is the per-`Table` ObjectCache alloc?
//!
//! Times the warm read three ways, all over a single pre-seeded table:
//!   1. load_table (full)         — Catalog::load_table (builds a default cache)
//!   2. Table::build default      — build only, metadata pre-resolved
//!   3. Table::build disable_cache — build only, capacity-0 moka
//!   4. resolve_metadata (floor)  — no Table at all
//!
//! Run: cargo run --release --example cache_ab

use std::time::Instant;

use iceberg::spec::{NestedField, PrimitiveType, Schema, Type};
use iceberg::table::Table;
use iceberg::{Catalog, NamespaceIdent, TableCreation, TableIdent};

// factory.rs references `crate::s3_storage::...` only under the `s3` feature;
// alias skade-katalog's one copy on the same gate (this example only exercises
// the embedded catalog).
#[cfg(feature = "s3")]
use skade_katalog::s3_storage;

#[path = "../src/factory.rs"]
#[allow(dead_code)] // shares factory.rs with the bench; only `embedded` is used here
mod factory;

fn schema() -> Schema {
    Schema::builder()
        .with_schema_id(0)
        .with_fields(vec![
            NestedField::required(1, "id", Type::Primitive(PrimitiveType::Long)).into(),
        ])
        .build()
        .unwrap()
}

fn report(label: &str, mut s: Vec<u128>, wall_ns: u128) {
    s.sort_unstable();
    let pct = |q: f64| s[((s.len() - 1) as f64 * q) as usize] as f64 / 1000.0;
    let ops_sec = s.len() as f64 * 1e9 / wall_ns as f64;
    println!(
        "{label:<28} p50 {:>8.3} us  p99 {:>8.3} us  {:>10.0} ops/s",
        pct(0.50),
        pct(0.99),
        ops_sec
    );
}

/// Time a synchronous closure `iters` times (1000-iter warmup).
fn bench_sync(label: &str, iters: usize, mut f: impl FnMut()) {
    for _ in 0..1000 {
        f();
    }
    let mut s = Vec::with_capacity(iters);
    let wall = Instant::now();
    for _ in 0..iters {
        let t = Instant::now();
        f();
        s.push(t.elapsed().as_nanos());
    }
    report(label, s, wall.elapsed().as_nanos());
}

#[tokio::main(flavor = "multi_thread")]
async fn main() -> anyhow::Result<()> {
    let iters: usize =
        std::env::var("ITERS").ok().and_then(|v| v.parse().ok()).unwrap_or(50_000);
    let (cat, _tmp) = factory::embedded().await?;

    let ns = NamespaceIdent::new("bench".to_string());
    cat.create_namespace(&ns, Default::default()).await?;
    let ident = TableIdent::new(ns.clone(), "t".to_string());
    cat.create_table(&ns, TableCreation::builder().name("t".to_string()).schema(schema()).build())
        .await?;

    // Resolve once; reuse the immutable metadata + location + FileIO for builds.
    let metadata = cat.resolve_metadata(&ident).await?;
    let seed = cat.load_table(&ident).await?;
    let location = seed.metadata_location().unwrap().to_string();
    let fileio = seed.file_io().clone();

    println!("# Table::build() A/B — {iters} iters, warm, single thread\n");

    // 1) load_table (full) — async.
    for _ in 0..1000 {
        let _ = cat.load_table(&ident).await;
    }
    {
        let mut s = Vec::with_capacity(iters);
        let wall = Instant::now();
        for _ in 0..iters {
            let t = Instant::now();
            let _ = cat.load_table(&ident).await?;
            s.push(t.elapsed().as_nanos());
        }
        report("load_table (full)", s, wall.elapsed().as_nanos());
    }

    // 2) Table::build with default cache (metadata pre-resolved) — sync.
    bench_sync("Table::build default", iters, || {
        let _: Table = Table::builder()
            .file_io(fileio.clone())
            .identifier(ident.clone())
            .metadata_location(location.clone())
            .metadata(metadata.clone())
            .runtime(iceberg::Runtime::current())
            .build()
            .unwrap();
    });

    // 3) Table::build with cache DISABLED (capacity-0 moka) — sync.
    bench_sync("Table::build disable_cache", iters, || {
        let _: Table = Table::builder()
            .file_io(fileio.clone())
            .identifier(ident.clone())
            .metadata_location(location.clone())
            .metadata(metadata.clone())
            .disable_cache()
            .runtime(iceberg::Runtime::current())
            .build()
            .unwrap();
    });

    // 3b) clone a pre-built Table (shares Arc<ObjectCache>) — sync.
    let template = Table::builder()
        .file_io(fileio.clone())
        .identifier(ident.clone())
        .metadata_location(location.clone())
        .metadata(metadata.clone())
        .runtime(iceberg::Runtime::current())
        .build()
        .unwrap();
    bench_sync("Table::clone (cached)", iters, || {
        let _: Table = template.clone();
    });

    // 4) resolve_metadata — no Table at all (the floor) — async.
    {
        let mut s = Vec::with_capacity(iters);
        let wall = Instant::now();
        for _ in 0..iters {
            let t = Instant::now();
            let _ = cat.resolve_metadata(&ident).await?;
            s.push(t.elapsed().as_nanos());
        }
        report("resolve_metadata (floor)", s, wall.elapsed().as_nanos());
    }

    Ok(())
}