Skip to content

Commit 1496bda

Browse files
committed
refactor(dursto): operations queue for benchmark
Refactors the database benchmark to use a queue of operations in lieu of hardcoded operations.
1 parent 0f093bf commit 1496bda

File tree

3 files changed

+153
-71
lines changed

3 files changed

+153
-71
lines changed

durable-storage/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ cfg-if.workspace = true
1515
log.workspace = true
1616
hex.workspace = true
1717
octez-riscv-data.workspace = true
18+
rand.workspace = true
1819
rocksdb.workspace = true
1920
tempfile.workspace = true
2021
thiserror.workspace = true
2122
tokio.workspace = true
22-
rand.workspace = true
2323

2424
[dev-dependencies]
2525
blake3.workspace = true
@@ -29,6 +29,7 @@ rand.workspace = true
2929

3030
[features]
3131
bench = []
32+
bench_cloning = []
3233

3334
[[bench]]
3435
name = "avl_tree"

durable-storage/benches/database.rs

Lines changed: 148 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use criterion::Criterion;
66
use criterion::criterion_group;
77
use criterion::criterion_main;
88
use octez_riscv_durable_storage::database::Database;
9+
use octez_riscv_durable_storage::database::DatabaseError;
910
use octez_riscv_durable_storage::merkle_layer::Key;
11+
use octez_riscv_durable_storage::persistence_layer::PersistenceLayerError;
1012
use octez_riscv_durable_storage::persistence_layer::utils::TestableTmpdir;
1113
use octez_riscv_durable_storage::random::generate_keys;
1214
use octez_riscv_durable_storage::random::generate_random_bytes;
@@ -15,48 +17,63 @@ use rand::prelude::*;
1517
use rand::rng;
1618
use tokio::runtime::Handle;
1719

18-
struct BenchmarkState {
20+
#[cfg_attr(
21+
not(feature = "bench_cloning"),
22+
expect(
23+
dead_code,
24+
reason = "The handle and repo are only used for cloning the database"
25+
)
26+
)]
27+
struct BenchmarkState<'a> {
1928
database: Database,
20-
large_read_buffer: Vec<u8>,
21-
large_read_choices: Vec<usize>,
22-
prepopulated_large_node_keys: Vec<Key>,
29+
operations: Vec<Operation>,
2330
prepopulated_node_keys: Vec<Key>,
2431
prepopulated_values: Vec<Bytes>,
25-
small_read_buffer: Vec<u8>,
26-
small_read_choices: Vec<usize>,
27-
small_write_choices: Vec<(usize, usize)>,
32+
read_buffer: Vec<u8>,
33+
handle: &'a Handle,
34+
repo: &'a DirectoryManager,
2835
}
2936

30-
impl BenchmarkState {
31-
fn clone_with(&self, handle: &Handle, repo: &DirectoryManager) -> Self {
37+
impl<'a> BenchmarkState<'a> {
38+
fn clone_with(&self, handle: &'a Handle, repo: &'a DirectoryManager) -> Self {
3239
Self {
3340
database: self
3441
.database
3542
.try_clone_with(handle, repo)
3643
.expect("Cloning the database should work"),
37-
large_read_buffer: vec![0u8; LARGE_READ_MIN_SIZE],
38-
large_read_choices: self.large_read_choices.clone(),
39-
prepopulated_large_node_keys: self.prepopulated_large_node_keys.clone(),
44+
operations: self.operations.clone(),
45+
read_buffer: vec![0u8; LARGE_READ_MAX_SIZE],
4046
prepopulated_node_keys: self.prepopulated_node_keys.clone(),
4147
prepopulated_values: self.prepopulated_values.clone(),
42-
small_read_buffer: vec![0u8; SMALL_READ_SIZE],
43-
small_read_choices: self.small_read_choices.clone(),
44-
small_write_choices: self.small_write_choices.clone(),
48+
handle,
49+
repo,
4550
}
4651
}
4752
}
4853

54+
#[derive(Clone)]
55+
#[expect(dead_code, reason = "Some operations aren't benchmarked yet")]
56+
enum Operation {
57+
Clone,
58+
Delete { key_index: usize },
59+
Exists { key_index: usize },
60+
Hash,
61+
Read { key_index: usize, size: usize },
62+
ValueLength { key_index: usize },
63+
Write { key_index: usize, data_index: usize },
64+
}
65+
4966
const BLOCK_FREQUENCY: usize = 5_000;
5067
const ERC_20_TRANSACTIONS: usize = 10_000;
51-
const LARGE_READ_MIN_SIZE: usize = 48000;
68+
const LARGE_READ_MIN_SIZE: usize = 96000;
5269
const LARGE_READ_MAX_SIZE: usize = LARGE_READ_MIN_SIZE * 2;
5370
const PREPOPULATED_LARGE_NODE_KEYS_COUNT: usize = 100;
5471
const PREPOPULATED_NODE_KEYS_COUNT: usize = 10_000_000 - PREPOPULATED_LARGE_NODE_KEYS_COUNT;
5572
const SMALL_READS: usize = 13;
5673
const SMALL_READ_SIZE: usize = 32;
5774
const SMALL_WRITES: usize = 7;
5875

59-
fn setup_benchmark_state(handle: &Handle, repo: &DirectoryManager) -> BenchmarkState {
76+
fn setup_benchmark_state<'a>(handle: &'a Handle, repo: &'a DirectoryManager) -> BenchmarkState<'a> {
6077
let mut database = Database::try_new(handle, repo).expect("Creating a database should succeed");
6178
let mut rng = rng();
6279

@@ -92,68 +109,135 @@ fn setup_benchmark_state(handle: &Handle, repo: &DirectoryManager) -> BenchmarkS
92109
prepopulated_large_node_keys.push(key);
93110
}
94111

95-
let large_read_choices = (0..ERC_20_TRANSACTIONS)
96-
.map(|_| rng.random_range(0..prepopulated_large_node_keys.len()))
97-
.collect();
112+
let mut operations =
113+
Vec::<Operation>::with_capacity(ERC_20_TRANSACTIONS * (1 + SMALL_READS + SMALL_WRITES));
114+
115+
for i in 0..ERC_20_TRANSACTIONS {
116+
let key_index = rng.random_range(0..prepopulated_large_node_keys.len());
117+
let size = rng.random_range(LARGE_READ_MIN_SIZE..LARGE_READ_MAX_SIZE);
118+
let operation = Operation::Read { key_index, size };
98119

99-
let small_read_choices = (0..ERC_20_TRANSACTIONS * SMALL_READS)
100-
.map(|_| rng.random_range(0..prepopulated_node_keys.len()))
101-
.collect();
120+
let mut vec = vec![0u8; size];
121+
rng.fill(vec.as_mut_slice());
102122

103-
let small_write_choices = (0..ERC_20_TRANSACTIONS * SMALL_WRITES)
104-
.map(|_| {
105-
(
106-
rng.random_range(0..prepopulated_node_keys.len()),
107-
rng.random_range(0..prepopulated_values.len()),
123+
// Write to the node so there's something meaningful and large to read
124+
database
125+
.write(
126+
prepopulated_large_node_keys[key_index].clone(),
127+
0,
128+
Bytes::from(vec),
108129
)
109-
})
110-
.collect();
130+
.expect("The write should succeed");
131+
operations.push(operation);
132+
133+
for _ in 0..SMALL_READS {
134+
let key_index = rng.random_range(0..prepopulated_large_node_keys.len());
135+
let size = SMALL_READ_SIZE;
136+
operations.push(Operation::Read { key_index, size });
137+
138+
let mut vec = vec![0u8; size];
139+
rng.fill(vec.as_mut_slice());
140+
141+
// Write to the node so there's something meaningful to read
142+
database
143+
.write(
144+
prepopulated_node_keys[key_index].clone(),
145+
0,
146+
Bytes::from(vec),
147+
)
148+
.expect("The write should succeed");
149+
}
150+
151+
for _ in 0..SMALL_WRITES {
152+
operations.push(Operation::Write {
153+
key_index: rng.random_range(0..prepopulated_node_keys.len()),
154+
data_index: rng.random_range(0..prepopulated_values.len()),
155+
});
156+
}
157+
158+
if i % BLOCK_FREQUENCY == 0 {
159+
operations.push(Operation::Hash);
160+
}
161+
}
111162

112163
BenchmarkState {
113164
database,
114-
large_read_buffer: vec![0u8; LARGE_READ_MAX_SIZE],
115-
large_read_choices,
116-
prepopulated_large_node_keys,
165+
operations,
117166
prepopulated_node_keys,
118167
prepopulated_values,
119-
small_read_buffer: vec![0u8; SMALL_READ_SIZE],
120-
small_read_choices,
121-
small_write_choices,
168+
read_buffer: vec![0u8; LARGE_READ_MAX_SIZE],
169+
handle,
170+
repo,
122171
}
123172
}
124173

125174
#[inline(never)]
126175
fn bench_run(mut state: BenchmarkState) {
127-
let large_node_keys = &state.prepopulated_large_node_keys;
128-
let node_keys = &state.prepopulated_node_keys;
129-
let values = &state.prepopulated_values;
130-
131-
for i in 0..ERC_20_TRANSACTIONS {
132-
let key = &large_node_keys[state.large_read_choices[i]];
133-
state
134-
.database
135-
.read(key, 0, &mut state.large_read_buffer)
136-
.expect("The read should succeed");
137-
138-
for _ in 0..SMALL_READS {
139-
let key = &node_keys[state.small_read_choices[i]];
140-
state
176+
// `KeyNotFound` errors are allowed, since the (conceptual, not std::hint) black box that is the transaction inputs may delete keys
177+
// which are later used.
178+
for operation in state.operations {
179+
match operation {
180+
Operation::Clone => {
181+
// TODO
182+
// Remove this feature after resolving RV-855 (Persistence layer cloning is slow).
183+
#[cfg(feature = "bench_cloning")]
184+
black_box(
185+
state
186+
.database
187+
.try_clone_with(state.handle, state.repo)
188+
.expect("The clone should succeed"),
189+
);
190+
}
191+
Operation::Delete { key_index } => match state
141192
.database
142-
.read(key, 0, &mut state.small_read_buffer)
143-
.expect("The read should succeed");
144-
}
145-
146-
for _ in 0..SMALL_WRITES {
147-
let key = node_keys[state.small_write_choices[i].0].clone();
148-
let value = values[state.small_write_choices[i].1].clone();
149-
state
193+
.delete(state.prepopulated_node_keys[key_index].clone())
194+
{
195+
Ok(_)
196+
| Err(DatabaseError::PersistenceLayer(PersistenceLayerError::KeyNotFound)) => {}
197+
Err(e) => panic!("The deletion should succeed: {e:?}"),
198+
},
199+
Operation::Exists { key_index } => {
200+
state
201+
.database
202+
.exists(&state.prepopulated_node_keys[key_index])
203+
.expect("The existence check should succeed");
204+
}
205+
Operation::Hash => {
206+
black_box(state.database.hash());
207+
}
208+
Operation::Read { key_index, size } => {
209+
match state.database.read(
210+
&state.prepopulated_node_keys[key_index],
211+
0,
212+
&mut state.read_buffer[0..size],
213+
) {
214+
Ok(_)
215+
| Err(DatabaseError::PersistenceLayer(PersistenceLayerError::KeyNotFound)) => {}
216+
Err(e) => panic!("The read should succeed: {e:?}"),
217+
}
218+
}
219+
Operation::ValueLength { key_index } => match state
150220
.database
151-
.write(key, 0, value)
152-
.expect("The read should succeed");
153-
}
154-
155-
if i % BLOCK_FREQUENCY == 0 {
156-
black_box(state.database.hash());
221+
.value_length(&state.prepopulated_node_keys[key_index])
222+
{
223+
Ok(_)
224+
| Err(DatabaseError::PersistenceLayer(PersistenceLayerError::KeyNotFound)) => {}
225+
Err(e) => panic!("The value length calculation should succeed: {e:?}"),
226+
},
227+
Operation::Write {
228+
key_index,
229+
data_index,
230+
} => {
231+
match state.database.write(
232+
state.prepopulated_node_keys[key_index].clone(),
233+
0,
234+
state.prepopulated_values[data_index].clone(),
235+
) {
236+
Ok(_)
237+
| Err(DatabaseError::PersistenceLayer(PersistenceLayerError::KeyNotFound)) => {}
238+
Err(e) => panic!("The write should succeed: {e:?}"),
239+
}
240+
}
157241
}
158242
}
159243
}

durable-storage/src/database.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,15 @@ pub enum DatabaseError {
4646
}
4747

4848
impl Database {
49-
#[cfg_attr(not(test), expect(dead_code, reason = "Added in RV-827"))]
5049
/// Remove a key from the database.
51-
pub(crate) fn delete(&mut self, key: Key) -> Result<(), DatabaseError> {
50+
pub fn delete(&mut self, key: Key) -> Result<(), DatabaseError> {
5251
self.persistent.delete(key.as_ref())?;
5352
self.merkle.delete(key);
5453
Ok(())
5554
}
5655

57-
#[cfg_attr(not(test), expect(dead_code, reason = "Implemented in RV-827"))]
5856
/// Returns true if the provided key exists in the database, false if it does not.
59-
pub(crate) fn exists(&self, key: &Key) -> Result<bool, DatabaseError> {
57+
pub fn exists(&self, key: &Key) -> Result<bool, DatabaseError> {
6058
match self.persistent.get(key.as_ref()) {
6159
Ok(_) => Ok(true),
6260
Err(PersistenceLayerError::KeyNotFound) => Ok(false),
@@ -135,12 +133,11 @@ impl Database {
135133
})
136134
}
137135

138-
#[cfg_attr(not(test), expect(dead_code, reason = "Implemented in RV-827"))]
139136
/// Retrieve the length of the value associated with the provided key.
140137
///
141138
/// Fails if:
142139
/// - The key does not exist in the database.
143-
pub(crate) fn value_length(&self, key: &Key) -> Result<usize, DatabaseError> {
140+
pub fn value_length(&self, key: &Key) -> Result<usize, DatabaseError> {
144141
let value = self.persistent.get(key.as_ref())?;
145142
Ok(value.as_ref().len())
146143
}

0 commit comments

Comments
 (0)