mod common;
use std::sync::Arc;
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use tokio::runtime::Runtime;
use common::{SAMPLE_SIZE_SLOW, select_storage};
use laurus::analysis::analyzer::analyzer::Analyzer;
use laurus::analysis::analyzer::standard::StandardAnalyzer;
use laurus::lexical::TextOption;
use laurus::lexical::core::field::IntegerOption;
use laurus::storage::Storage;
use laurus::{Document, Engine, Result, Schema};
const COMMON_TERMS: &str = "search engine system data query";
const TOPIC_PHRASES: &[&str] = &[
"rust programming language systems safety concurrency memory ownership",
"python data science machine learning artificial intelligence numpy",
"javascript web development frontend backend node react framework",
"database query optimization indexing performance search engine",
"network protocol distributed systems cloud computing infrastructure",
"security cryptography authentication authorization encryption",
"algorithms data structures sorting searching graph traversal",
"operating systems kernel processes threads scheduling memory",
];
const LONG_TAIL: &[&str] = &[
"compaction",
"histogram",
"lattice",
"registry",
"compiler",
"scheduler",
"interpreter",
"garbage",
"collector",
"allocator",
"telemetry",
"regression",
"snapshot",
"replication",
"consensus",
"quorum",
"leader",
"follower",
];
const LONG_TAIL_PER_DOC: usize = 5;
const CATEGORIES: &[&str] = &["programming", "data-science", "web", "database", "systems"];
fn build_body(i: usize) -> String {
let topic = TOPIC_PHRASES[i % TOPIC_PHRASES.len()];
let mut tail_words = Vec::with_capacity(LONG_TAIL_PER_DOC);
for k in 0..LONG_TAIL_PER_DOC {
let idx = (i.wrapping_mul(7) + k * 11) % LONG_TAIL.len();
tail_words.push(LONG_TAIL[idx]);
}
let tail = tail_words.join(" ");
format!("Document {i} {COMMON_TERMS} {topic} {tail} should match relevant terms")
}
fn build_document(i: usize) -> Document {
let body = build_body(i);
Document::builder()
.add_text("title", format!("Title for document {i}"))
.add_text("body", &body)
.add_text("category", CATEGORIES[i % CATEGORIES.len()])
.add_integer("year", 2020 + (i % 5) as i64)
.build()
}
fn memory_storage() -> Result<Arc<dyn Storage>> {
Ok(select_storage())
}
async fn build_populated_engine(n: usize) -> Result<Engine> {
let storage = memory_storage()?;
let analyzer: Arc<dyn Analyzer> = Arc::new(StandardAnalyzer::default());
let schema = Schema::builder()
.add_text_field("title", TextOption::default())
.add_text_field("body", TextOption::default())
.add_text_field("category", TextOption::default())
.add_integer_field("year", IntegerOption::default())
.add_default_field("body")
.build();
let engine = Engine::builder(storage, schema)
.analyzer(analyzer)
.build()
.await?;
for i in 0..n {
engine
.add_document(&i.to_string(), build_document(i))
.await?;
}
engine.commit().await?;
Ok(engine)
}
const M_OPS: usize = 100;
fn bench_delete_documents(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("mutation/delete_documents");
group.sample_size(SAMPLE_SIZE_SLOW);
for &n in &[1000usize, 10_000] {
{
let engine = rt.block_on(build_populated_engine(n)).unwrap();
rt.block_on(engine.delete_documents("0")).unwrap();
}
group.throughput(Throughput::Elements(M_OPS as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, &n| {
b.iter_batched(
|| {
let engine = rt.block_on(build_populated_engine(n)).unwrap();
let ids: Vec<String> =
(0..M_OPS).map(|i| ((i * n) / M_OPS).to_string()).collect();
(engine, ids)
},
|(engine, ids)| {
rt.block_on(async {
for id in ids {
engine.delete_documents(&id).await.unwrap();
}
});
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_put_document(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("mutation/put_document");
group.sample_size(SAMPLE_SIZE_SLOW);
for &n in &[1000usize, 10_000] {
{
let engine = rt.block_on(build_populated_engine(n)).unwrap();
let new_doc = build_document(0);
rt.block_on(engine.put_document("0", new_doc)).unwrap();
}
group.throughput(Throughput::Elements(M_OPS as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, &n| {
b.iter_batched(
|| {
let engine = rt.block_on(build_populated_engine(n)).unwrap();
let updates: Vec<(String, Document)> = (0..M_OPS)
.map(|i| {
let id = ((i * n) / M_OPS).to_string();
let new_doc = build_document(n + i);
(id, new_doc)
})
.collect();
(engine, updates)
},
|(engine, updates)| {
rt.block_on(async {
for (id, doc) in updates {
engine.put_document(&id, doc).await.unwrap();
}
});
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_delete_then_commit(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("mutation/delete_then_commit");
group.sample_size(SAMPLE_SIZE_SLOW);
for &n in &[1000usize, 10_000] {
let to_delete = n / 10;
{
let engine = rt.block_on(build_populated_engine(n)).unwrap();
rt.block_on(async {
for i in 0..to_delete {
engine.delete_documents(&i.to_string()).await.unwrap();
}
engine.commit().await.unwrap();
});
}
group.throughput(Throughput::Elements(to_delete as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, &n| {
b.iter_batched(
|| {
let engine = rt.block_on(build_populated_engine(n)).unwrap();
rt.block_on(async {
for i in 0..to_delete {
engine.delete_documents(&i.to_string()).await.unwrap();
}
});
engine
},
|engine| {
rt.block_on(engine.commit()).unwrap();
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
criterion_group!(
benches,
bench_delete_documents,
bench_put_document,
bench_delete_then_commit,
);
criterion_main!(benches);