#![allow(clippy::unwrap_used)] //! Criterion benchmarks for the SEARCH query pipeline. //! //! Measures end-to-end `db.search()` latency at 10K items to validate the //! < 50ms target specified in the m5p3 phase acceptance criteria. use std::collections::HashMap; use std::time::Duration; use criterion::{Criterion, black_box, criterion_group, criterion_main}; use tidaldb::TidalDb; use tidaldb::query::search::Search; use tidaldb::schema::{ DecaySpec, EntityId, EntityKind, SchemaBuilder, TextFieldDef, TextFieldType, Timestamp, Window, }; fn search_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::TwentyFourHours]) .velocity(false) .add(); let _ = builder .signal( "like", EntityKind::Item, DecaySpec::Exponential { half_life: Duration::from_secs(30 * 24 * 3600), }, ) .windows(&[Window::TwentyFourHours]) .velocity(false) .add(); builder.text_field("title", TextFieldType::Text); builder.text_field("description", TextFieldType::Text); builder.text_field("category", TextFieldType::Keyword); builder.build().unwrap() } /// Build a TidalDb with N items indexed for text search. fn make_db(n: u64) -> TidalDb { let db = TidalDb::builder() .ephemeral() .with_schema(search_schema()) .open() .unwrap(); let ts = Timestamp::now(); for i in 0..n { let mut meta = HashMap::new(); // Vary titles to produce realistic IDF scores. meta.insert( "title".to_string(), format!("Rust tutorial {i} async concurrency programming"), ); meta.insert( "description".to_string(), "Learn Rust with practical examples and real projects.".to_string(), ); let cat = if i % 3 == 0 { "programming" } else if i % 3 == 1 { "systems" } else { "web" }; meta.insert("category".to_string(), cat.to_string()); db.write_item_with_metadata(EntityId::new(i), &meta) .unwrap(); // Add some signals to make profile scoring non-trivial. if i % 5 == 0 { db.signal("view", EntityId::new(i), 1.0, ts).unwrap(); } } // Wait for the background text syncer to commit all pending documents // (syncer commits every 1_000 items or 2s; 10K items = 10 batch commits). // Then reload the reader so the searcher sees all committed documents. std::thread::sleep(std::time::Duration::from_millis(500)); db.reload_text_index().unwrap(); db } /// Benchmark: `db.search()` with a text-only query at 10K items. /// /// Target: < 50ms. fn bench_search_text_10k(c: &mut Criterion) { let db = make_db(10_000); let query = Search::builder() .query("Rust async") .limit(20) .build() .unwrap(); c.bench_function("search_text_10k", |b| { b.iter(|| db.search(black_box(&query)).unwrap()); }); } /// Benchmark: `db.search()` with a keyword-scoped query at 10K items. /// /// Target: < 50ms. fn bench_search_keyword_10k(c: &mut Criterion) { let db = make_db(10_000); let query = Search::builder() .query("category:programming") .limit(20) .build() .unwrap(); c.bench_function("search_keyword_10k", |b| { b.iter(|| db.search(black_box(&query)).unwrap()); }); } criterion_group!( search_benches, bench_search_text_10k, bench_search_keyword_10k ); criterion_main!(search_benches);