Files
qdrant/lib/gridstore/benches/real_data_bench.rs
Andrey Vasnetsov 03c0fa2cf7 Universal IO: gridstore pages (#8223)
* [manunal] Gridstore page use universal IO

* fmt

* Apply review feedback for universal IO gridstore pages (#8230)

* Apply review feedback from PR #8223

- Use `super::Result` import in mmap.rs instead of fully-qualified `crate::universal_io::Result`
- Restructure ValuePointer destructuring in get_value and delete_value to
  first match Some(pointer), then destructure separately

* Replace Either<E, GridstoreError> with E: From<GridstoreError> in Gridstore::iter

Use a trait bound instead of Either to combine callback and gridstore
errors, allowing `?` to work directly on GridstoreError. This simplifies
callers by removing Either matching and io::Error conversion workarounds.

---------

Co-authored-by: qdrant-claw <qdrant-claw@users.noreply.github.com>

---------

Co-authored-by: qdrant-claw <qdrant-claw@users.noreply.github.com>
2026-02-25 19:46:00 +01:00

139 lines
4.4 KiB
Rust

use std::hint::black_box;
use std::io::BufReader;
use std::path::Path;
use common::counter::hardware_counter::HardwareCounterCell;
use criterion::{Criterion, criterion_group, criterion_main};
use fs_err as fs;
use fs_err::File;
use gridstore::fixtures::{HM_FIELDS, Payload, empty_storage};
use rand::RngExt;
use serde_json::Value;
/// Insert CSV data into the storage
fn append_csv_data(storage: &mut gridstore::Gridstore<Payload>, csv_path: &Path) {
let csv_file = BufReader::new(File::open(csv_path).expect("file should open"));
let mut rdr = csv::Reader::from_reader(csv_file);
let mut point_offset = storage.max_point_offset();
let hw_counter = HardwareCounterCell::new();
let hw_counter_ref = hw_counter.ref_payload_io_write_counter();
for result in rdr.records() {
let record = result.unwrap();
let mut payload = Payload::default();
for (i, &field) in HM_FIELDS.iter().enumerate() {
payload.0.insert(
field.to_string(),
Value::String(record.get(i).unwrap().to_string()),
);
}
storage
.put_value(point_offset, &payload, hw_counter_ref)
.unwrap();
point_offset += 1;
}
}
/// Recursively compute the size of a directory in megabytes
fn compute_folder_size_mb<P: AsRef<Path>>(path: P) -> u64 {
let mut size = 0;
for entry in fs::read_dir(path.as_ref()).unwrap() {
let entry = entry.unwrap();
let metadata = entry.metadata().unwrap();
if metadata.is_dir() {
size += compute_folder_size_mb(entry.path());
} else {
size += metadata.len();
}
}
(size as f32 / 1_000_000.0).ceil() as u64
}
pub fn real_data_data_bench(c: &mut Criterion) {
let (dir, mut storage) = empty_storage();
let csv_path = dataset::Dataset::HMArticles
.download()
.expect("download should succeed");
// check source file size
let file_size_bytes = fs::metadata(csv_path.clone())
.expect("file should exist")
.len();
assert_eq!(file_size_bytes, 36_127_865); // 36MB
// the CSV file has 105_542 rows
let expected_point_count = 105_542;
// insert data once & flush
append_csv_data(&mut storage, &csv_path);
storage.flusher()().unwrap();
assert_eq!(storage.max_point_offset(), expected_point_count);
// flush to get a consistent bitmask
storage.flusher()().unwrap();
// sanity check of storage size
let storage_size = storage.get_storage_size_bytes();
assert_eq!(storage_size, 54_034_048); // 54MB
// check storage folder size
let file_size_mb = compute_folder_size_mb(dir.path());
assert_eq!(file_size_mb, 70); // 70MB (includes metadata)
c.bench_function("compute storage size", |b| {
b.iter(|| black_box(storage.get_storage_size_bytes()));
});
c.bench_function("scan storage", |b| {
let hw_counter = HardwareCounterCell::new();
b.iter(|| {
for i in 0..storage.max_point_offset() {
let res = storage.get_value::<false>(i, &hw_counter).unwrap().unwrap();
assert!(res.0.contains_key("article_id"));
}
});
});
// append the same data again to increase storage size
for _ in 0..10 {
append_csv_data(&mut storage, &csv_path);
storage.flusher()().unwrap();
}
let inflated_storage_size = storage.get_storage_size_bytes();
assert_eq!(inflated_storage_size, 594_374_528); // 594 MB (close to 10x54MB)
c.bench_function("compute storage size (large)", |b| {
b.iter(|| black_box(storage.get_storage_size_bytes()));
});
// delete 30% of the points
let mut rng = rand::rng();
for i in 0..storage.max_point_offset() {
if rng.random_bool(0.3) {
storage.delete_value(i).unwrap();
}
}
// flush to get a consistent bitmask
storage.flusher()().unwrap();
c.bench_function("compute storage size (large sparse)", |b| {
b.iter(|| black_box(storage.get_storage_size_bytes()));
});
c.bench_function("insert real payload (large)", |b| {
b.iter(|| {
append_csv_data(&mut storage, &csv_path);
// do not always flush to build up pending updates
if rng.random_bool(0.3) {
storage.flusher()().unwrap();
}
});
});
}
criterion_group!(benches, real_data_data_bench);
criterion_main!(benches);