Skip to content

Commit

Permalink
feat: Add benchmark for store read/write (#5312)
Browse files Browse the repository at this point in the history
Add benchmark for reading / writing from store.

See #4771

```
cd chain/network/benches
rm -rf /tmp/xxxxx ; cargo bench benchmark_db
```

TODO:
- [x] Add tempdir
- [x] Pregenerate keys
- [x] Add stats to see number of entries read successfully
- [x] Why we can only read 48.5% entries back when we write them to DB?
- [x] Move code to a better place
- [x] Document what the benchmark does
  • Loading branch information
pmnoxx authored Nov 24, 2021
1 parent 18558c5 commit 21c760d
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
4 changes: 4 additions & 0 deletions core/store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ rand = "0.7"
name = "trie_bench"
harness = false

[[bench]]
name = "store_bench"
harness = false

[features]
default = []
no_cache = []
Expand Down
99 changes: 99 additions & 0 deletions core/store/benches/store_bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#[macro_use]
extern crate bencher;

use bencher::{black_box, Bencher};
use near_primitives::borsh::maybestd::sync::Arc;
use near_primitives::errors::StorageError;
use near_store::db::DBCol::ColBlockMerkleTree;
use near_store::{create_store, DBCol, Store};
use std::time::{Duration, Instant};

/// Run a benchmark to generate `num_keys` keys, each of size `key_size`, then write then
/// in random order to column `col` in store, and then read keys back from `col` in random order.
/// Works only for column configured without reference counting, that is `.is_rc() == false`.
fn benchmark_write_then_read_successful(
bench: &mut Bencher,
num_keys: usize,
key_size: usize,
max_value_size: usize,
col: DBCol,
) {
let store = create_store_in_random_folder();
let keys = generate_keys(num_keys, key_size);
write_to_db(&store, &keys, max_value_size, col);

bench.iter(move || {
let start = Instant::now();

let read_records = read_from_db(&store, &keys, col);
let took = start.elapsed();
println!(
"took on avg {:?} op per sec {} got {}/{}",
took / (num_keys as u32),
(num_keys as u128) * Duration::from_secs(1).as_nanos() / took.as_nanos(),
read_records,
keys.len()
);
});
}

/// Create `Store` in a random folder.
fn create_store_in_random_folder() -> Arc<Store> {
let tmp_dir = tempfile::Builder::new().prefix("_test_clear_column").tempdir().unwrap();
let store = create_store(tmp_dir.path());
store
}

/// Generate `count` keys of `key_size` length.
fn generate_keys(count: usize, key_size: usize) -> Vec<Vec<u8>> {
let mut res: Vec<Vec<u8>> = Vec::new();
for _k in 0..count {
let key: Vec<u8> = (0..key_size).map(|_| rand::random::<u8>()).collect();

res.push(key)
}
res
}

/// Read from DB value for given `kyes` in random order for `col`.
/// Works only for column configured without reference counting, that is `.is_rc() == false`.
fn read_from_db(store: &Arc<Store>, keys: &Vec<Vec<u8>>, col: DBCol) -> usize {
let mut read = 0;
for _k in 0..keys.len() {
let r = rand::random::<u32>() % (keys.len() as u32);
let key = &keys[r as usize];

let val = store.get(col, key.as_ref()).map_err(|_| StorageError::StorageInternalError);

if let Ok(Some(x)) = val {
black_box(x);
read += 1;
}
}
read
}

/// Write random value of size between `0` and `max_value_size` to given `keys` at specific column
/// `col.`
/// Works only for column configured without reference counting, that is `.is_rc() == false`.
fn write_to_db(store: &Arc<Store>, keys: &[Vec<u8>], max_value_size: usize, col: DBCol) {
let mut store_update = store.store_update();
for key in keys.iter() {
let x: usize = rand::random::<usize>() % max_value_size;
let val: Vec<u8> = (0..x).map(|_| rand::random::<u8>()).collect();
// NOTE: this
store_update.set(col, key.as_slice().clone(), &val);
}
store_update.commit().unwrap();
}

fn benchmark_write_then_read_successful_10m(bench: &mut Bencher) {
// By adding logs, I've seen a lot of write to keys with size 40, an values with sizes
// between 10 .. 333.
// NOTE: ColBlockMerkleTree was chosen to be a column, where `.is_rc() == false`.
benchmark_write_then_read_successful(bench, 10_000_000, 40, 333, ColBlockMerkleTree);
}

benchmark_group!(benches, benchmark_write_then_read_successful_10m);

benchmark_main!(benches);

0 comments on commit 21c760d

Please sign in to comment.