#![allow( clippy::unwrap_used, clippy::cast_precision_loss, clippy::cast_possible_truncation )] //! Criterion benchmarks for metadata-based sort modes (m6p3). //! //! Validates the m6p3 performance acceptance criterion: //! - Metadata-based sorts (alphabetical, duration) complete in < 50ms at 500 items. //! //! Sort modes exercised: //! - `alphabetical_asc`: packs 8-byte title prefix into u64 for ordering //! - `alphabetical_desc`: inverse of alphabetical_asc //! - `shortest`: reads "duration" metadata, orders ascending //! - `longest`: reads "duration" metadata, orders descending //! //! Dataset: 500 items, each with a unique title and duration. No signals. //! The benchmark uses a shared `LazyLock` to amortize setup. use std::sync::LazyLock; use std::time::Duration; use criterion::{Criterion, black_box, criterion_group, criterion_main}; use tidaldb::TidalDb; use tidaldb::query::retrieve::Retrieve; use tidaldb::schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Window}; const N_ITEMS: u64 = 500; /// Minimal schema: no text fields needed; metadata-only sorts don't use Tantivy. fn sort_schema() -> tidaldb::schema::Schema { let mut builder = SchemaBuilder::new(); let _ = builder .signal( "view", EntityKind::Item, DecaySpec::Exponential { half_life: Duration::from_secs(7 * 24 * 3600), }, ) .windows(&[Window::SevenDays]) .velocity(false) .add(); builder.build().unwrap() } /// Build the shared 500-item database. Called exactly once per benchmark run. /// /// Items are written with: /// - `"title"`: alphabetically varied titles ("Item 000" through "Item 499") /// - `"duration"`: varied integer seconds (1..=500) fn build_sort_db() -> TidalDb { let db = TidalDb::builder() .ephemeral() .with_schema(sort_schema()) .open() .unwrap(); for i in 0..N_ITEMS { let item_id = EntityId::new(i + 1); let mut meta = std::collections::HashMap::new(); // Vary the title so alphabetical sort has real ordering work to do. meta.insert("title".to_string(), format!("Item {:03}", i)); // Duration cycles 1..=500 seconds. meta.insert("duration".to_string(), (i + 1).to_string()); db.write_item_with_metadata(item_id, &meta).unwrap(); } db } /// Shared 500-item DB: built once, reused by all sort benchmarks. static SORT_DB: LazyLock = LazyLock::new(build_sort_db); // ── Benchmarks ──────────────────────────────────────────────────────────────── /// Alphabetical A-Z sort over 500 items (< 50ms AC). fn bench_alphabetical_asc(c: &mut Criterion) { let db: &TidalDb = &SORT_DB; let query = Retrieve::builder() .profile("alphabetical_asc") .limit(N_ITEMS as usize) .build() .unwrap(); let mut group = c.benchmark_group("sort_500"); group.bench_function("alphabetical_asc", |b| { b.iter(|| db.retrieve(black_box(&query)).unwrap()); }); group.finish(); } /// Alphabetical Z-A sort over 500 items (< 50ms AC). fn bench_alphabetical_desc(c: &mut Criterion) { let db: &TidalDb = &SORT_DB; let query = Retrieve::builder() .profile("alphabetical_desc") .limit(N_ITEMS as usize) .build() .unwrap(); let mut group = c.benchmark_group("sort_500"); group.bench_function("alphabetical_desc", |b| { b.iter(|| db.retrieve(black_box(&query)).unwrap()); }); group.finish(); } /// Shortest-first sort over 500 items (< 50ms AC). fn bench_shortest(c: &mut Criterion) { let db: &TidalDb = &SORT_DB; let query = Retrieve::builder() .profile("shortest") .limit(N_ITEMS as usize) .build() .unwrap(); let mut group = c.benchmark_group("sort_500"); group.bench_function("shortest", |b| { b.iter(|| db.retrieve(black_box(&query)).unwrap()); }); group.finish(); } /// Longest-first sort over 500 items (< 50ms AC). fn bench_longest(c: &mut Criterion) { let db: &TidalDb = &SORT_DB; let query = Retrieve::builder() .profile("longest") .limit(N_ITEMS as usize) .build() .unwrap(); let mut group = c.benchmark_group("sort_500"); group.bench_function("longest", |b| { b.iter(|| db.retrieve(black_box(&query)).unwrap()); }); group.finish(); } criterion_group!( benches, bench_alphabetical_asc, bench_alphabetical_desc, bench_shortest, bench_longest, ); criterion_main!(benches);