Skip to content

Commit 09cf975

Browse files
rymncMitchTurner
andauthored
chore: add benchmarks for varied forms of lookups (#2142)
This PR introduces benchmarks for varied methods of database lookups for blocks and included transactions, namely - 1. `header_and_tx_lookup` 2. `full_block_lookup` 3. `multi_get_lookup` We can use these benchmarks to ascertain which form is best for this use-case, and then implement it. It is suggested to alter the bench matrix before running and using a beefy machine to do so. Run it with `cargo bench --bench db_lookup_times` ## Linked Issues/PRs <!-- List of related issues/PRs --> - #2023 ## Description <!-- List of detailed changes --> - moved the implementation of `KeyValueMutate` for rocksdb out of the test module. - each benchmark is located in `benches/benches/db_lookup_times.rs` - stores `FullFuelBlocks` along with the `CompressedBlocks` and `Transactions` table to not affect single header/tx lookup times in other usages. - implemented various traits for the `FullFuelBlocks` table - the [notion doc](https://www.notion.so/fuellabs/DB-Analysis-for-Lookups-f62aa0cd5cdf4c91b9575b8fb45683f7) has more information about the analysis we did. - each benchmark randomizes the block height that is queries *per iteration* to ensure randomness and fair comparison - rocksdb caching has been `disabled` for these benchmarks ## Checklist - [x] Breaking changes are clearly marked as such in the PR description and changelog - [x] New behavior is reflected in tests - [x] [The specification](https://github.com/FuelLabs/fuel-specs/) matches the implemented behavior (link update PR if changes are needed) ### Before requesting review - [x] I have reviewed the code myself - [x] I have created follow-up issues caused by this PR and linked them here ### After merging, notify other teams [Add or remove entries as needed] - [ ] [Rust SDK](https://github.com/FuelLabs/fuels-rs/) - [ ] [Sway compiler](https://github.com/FuelLabs/sway/) - [ ] [Platform documentation](https://github.com/FuelLabs/devrel-requests/issues/new?assignees=&labels=new+request&projects=&template=NEW-REQUEST.yml&title=%5BRequest%5D%3A+) (for out-of-organization contributors, the person merging the PR will do this) - [ ] Someone else? --------- Co-authored-by: Mitchell Turner <[email protected]>
1 parent f3537fc commit 09cf975

File tree

12 files changed

+450
-37
lines changed

12 files changed

+450
-37
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99
### Added
1010
- [2135](https://github.com/FuelLabs/fuel-core/pull/2135): Added metrics logging for number of blocks served over the p2p req/res protocol.
1111
- [2155](https://github.com/FuelLabs/fuel-core/pull/2155): Added trait declaration for block committer data
12-
12+
- [2142](https://github.com/FuelLabs/fuel-core/pull/2142): Added benchmarks for varied forms of db lookups to assist in optimizations.
1313

1414
## [Version 0.35.0]
1515

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

benches/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ p256 = { version = "0.13", default-features = false, features = [
3434
"digest",
3535
"ecdsa",
3636
] }
37+
postcard = { workspace = true }
3738
primitive-types = { workspace = true, default-features = false }
3839
quanta = "0.12"
3940
rand = { workspace = true }
@@ -66,3 +67,7 @@ name = "block_target_gas"
6667
[[bench]]
6768
harness = false
6869
name = "transaction_throughput"
70+
71+
[[bench]]
72+
harness = false
73+
name = "db_lookup_times"

benches/benches/db_lookup_times.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use crate::db_lookup_times_utils::{
2+
matrix::matrix,
3+
utils::{
4+
get_full_block,
5+
get_random_block_height,
6+
multi_get_block,
7+
open_db,
8+
open_raw_rocksdb,
9+
},
10+
};
11+
use criterion::{
12+
criterion_group,
13+
criterion_main,
14+
Criterion,
15+
};
16+
use db_lookup_times_utils::seed::{
17+
seed_compressed_blocks_and_transactions_matrix,
18+
seed_full_block_matrix,
19+
};
20+
use fuel_core_storage::transactional::AtomicView;
21+
use rand::thread_rng;
22+
23+
mod db_lookup_times_utils;
24+
25+
pub fn header_and_tx_lookup(c: &mut Criterion) {
26+
let method = "header_and_tx";
27+
let mut rng = thread_rng();
28+
29+
seed_compressed_blocks_and_transactions_matrix(method);
30+
let mut group = c.benchmark_group(method);
31+
32+
for (block_count, tx_count) in matrix() {
33+
let database = open_db(block_count, tx_count, method);
34+
let view = database.latest_view().unwrap();
35+
group.bench_function(format!("{block_count}/{tx_count}"), |b| {
36+
b.iter(|| {
37+
let height = get_random_block_height(&mut rng, block_count);
38+
let block = view.get_full_block(&height);
39+
assert!(block.is_ok());
40+
assert!(block.unwrap().is_some());
41+
});
42+
});
43+
}
44+
45+
group.finish();
46+
}
47+
48+
pub fn multi_get_lookup(c: &mut Criterion) {
49+
let method = "multi_get";
50+
let mut rng = thread_rng();
51+
52+
seed_compressed_blocks_and_transactions_matrix(method);
53+
let mut group = c.benchmark_group(method);
54+
55+
for (block_count, tx_count) in matrix() {
56+
let database = open_raw_rocksdb(block_count, tx_count, method);
57+
group.bench_function(format!("{block_count}/{tx_count}"), |b| {
58+
b.iter(|| {
59+
let height = get_random_block_height(&mut rng, block_count);
60+
assert!(multi_get_block(&database, height).is_ok());
61+
});
62+
});
63+
}
64+
65+
group.finish();
66+
}
67+
68+
pub fn full_block_lookup(c: &mut Criterion) {
69+
let method = "full_block";
70+
let mut rng = thread_rng();
71+
72+
seed_full_block_matrix();
73+
let mut group = c.benchmark_group(method);
74+
75+
for (block_count, tx_count) in matrix() {
76+
let database = open_db(block_count, tx_count, method);
77+
let view = database.latest_view().unwrap();
78+
group.bench_function(format!("{block_count}/{tx_count}"), |b| {
79+
b.iter(|| {
80+
let height = get_random_block_height(&mut rng, block_count);
81+
let full_block = get_full_block(&view, &height);
82+
assert!(full_block.is_ok());
83+
assert!(full_block.unwrap().is_some());
84+
});
85+
});
86+
}
87+
88+
group.finish();
89+
}
90+
91+
criterion_group! {
92+
name = benches;
93+
config = Criterion::default().sample_size(100_000).measurement_time(std::time::Duration::from_secs(100));
94+
targets = header_and_tx_lookup, multi_get_lookup, full_block_lookup
95+
}
96+
criterion_main!(benches);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pub const BLOCK_COUNT_MATRIX: [u32; 2] = [10, 100];
2+
pub const TX_COUNT_MATRIX: [u32; 2] = [100, 1000];
3+
4+
pub fn matrix() -> impl Iterator<Item = (u32, u32)> {
5+
BLOCK_COUNT_MATRIX.iter().flat_map(|&block_count| {
6+
TX_COUNT_MATRIX
7+
.iter()
8+
.map(move |&tx_count| (block_count, tx_count))
9+
})
10+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod matrix;
2+
pub mod seed;
3+
pub mod utils;
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use crate::db_lookup_times_utils::{
2+
matrix::matrix,
3+
utils::{
4+
chain_id,
5+
open_raw_rocksdb,
6+
},
7+
};
8+
use fuel_core::{
9+
database::database_description::on_chain::OnChain,
10+
state::rocks_db::RocksDb,
11+
};
12+
use fuel_core_storage::{
13+
column::Column,
14+
kv_store::{
15+
KeyValueMutate,
16+
Value,
17+
},
18+
};
19+
use fuel_core_types::{
20+
blockchain::{
21+
block::{
22+
Block,
23+
PartialFuelBlock,
24+
},
25+
header::{
26+
ConsensusHeader,
27+
PartialBlockHeader,
28+
},
29+
primitives::Empty,
30+
},
31+
fuel_tx::{
32+
Transaction,
33+
UniqueIdentifier,
34+
},
35+
fuel_types::BlockHeight,
36+
};
37+
38+
pub fn seed_compressed_blocks_and_transactions_matrix(method: &str) {
39+
for (block_count, tx_count) in matrix() {
40+
let mut database = open_raw_rocksdb(block_count, tx_count, method);
41+
let _ =
42+
seed_compressed_blocks_and_transactions(&mut database, block_count, tx_count);
43+
}
44+
}
45+
46+
pub fn seed_full_block_matrix() {
47+
for (block_count, tx_count) in matrix() {
48+
let mut database = open_raw_rocksdb(block_count, tx_count, "full_block");
49+
seed_full_blocks(&mut database, block_count, tx_count);
50+
}
51+
}
52+
53+
fn generate_bench_block(height: u32, tx_count: u32) -> Block {
54+
let header = PartialBlockHeader {
55+
application: Default::default(),
56+
consensus: ConsensusHeader::<Empty> {
57+
height: BlockHeight::from(height),
58+
..Default::default()
59+
},
60+
};
61+
let txes = generate_bench_transactions(tx_count);
62+
let block = PartialFuelBlock::new(header, txes);
63+
block.generate(&[], Default::default()).unwrap()
64+
}
65+
66+
fn generate_bench_transactions(tx_count: u32) -> Vec<Transaction> {
67+
let mut txes = vec![];
68+
for _ in 0..tx_count {
69+
txes.push(Transaction::default_test_tx());
70+
}
71+
txes
72+
}
73+
74+
fn height_key(block_height: u32) -> Vec<u8> {
75+
BlockHeight::from(block_height).to_bytes().to_vec()
76+
}
77+
78+
fn insert_compressed_block(
79+
database: &mut RocksDb<OnChain>,
80+
height: u32,
81+
tx_count: u32,
82+
) -> Block {
83+
let block = generate_bench_block(height, tx_count);
84+
85+
let compressed_block = block.compress(&chain_id());
86+
let height_key = height_key(height);
87+
88+
let raw_compressed_block = postcard::to_allocvec(&compressed_block).unwrap().to_vec();
89+
let raw_transactions = block
90+
.transactions()
91+
.iter()
92+
.map(|tx| {
93+
(
94+
tx.id(&chain_id()),
95+
postcard::to_allocvec(tx).unwrap().to_vec(),
96+
)
97+
})
98+
.collect::<Vec<_>>();
99+
100+
// 1. insert into CompressedBlocks table
101+
database
102+
.put(
103+
height_key.as_slice(),
104+
Column::FuelBlocks,
105+
Value::new(raw_compressed_block),
106+
)
107+
.unwrap();
108+
// 2. insert into Transactions table
109+
for (tx_id, tx) in raw_transactions {
110+
database
111+
.put(tx_id.as_slice(), Column::Transactions, Value::new(tx))
112+
.unwrap();
113+
}
114+
115+
block
116+
}
117+
118+
fn insert_full_block(database: &mut RocksDb<OnChain>, height: u32, tx_count: u32) {
119+
// we seed compressed blocks and transactions to not affect individual
120+
// lookup times
121+
let block = insert_compressed_block(database, height, tx_count);
122+
123+
let height_key = height_key(height);
124+
let raw_full_block = postcard::to_allocvec(&block).unwrap().to_vec();
125+
126+
database
127+
.put(
128+
height_key.as_slice(),
129+
Column::FullFuelBlocks,
130+
Value::new(raw_full_block),
131+
)
132+
.unwrap();
133+
}
134+
135+
fn seed_compressed_blocks_and_transactions(
136+
database: &mut RocksDb<OnChain>,
137+
block_count: u32,
138+
tx_count: u32,
139+
) -> Vec<Block> {
140+
let mut blocks = vec![];
141+
for block_number in 0..block_count {
142+
blocks.push(insert_compressed_block(database, block_number, tx_count));
143+
}
144+
blocks
145+
}
146+
147+
fn seed_full_blocks(database: &mut RocksDb<OnChain>, block_count: u32, tx_count: u32) {
148+
for block_number in 0..block_count {
149+
insert_full_block(database, block_number, tx_count);
150+
}
151+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use anyhow::anyhow;
2+
use fuel_core::{
3+
database::{
4+
database_description::on_chain::OnChain,
5+
Database,
6+
},
7+
state::{
8+
historical_rocksdb::StateRewindPolicy,
9+
rocks_db::RocksDb,
10+
IterableKeyValueView,
11+
},
12+
};
13+
use fuel_core_storage::{
14+
column::Column,
15+
kv_store::{
16+
KeyValueInspect,
17+
StorageColumn,
18+
},
19+
tables::FullFuelBlocks,
20+
StorageAsRef,
21+
};
22+
use fuel_core_types::{
23+
blockchain::block::{
24+
Block,
25+
CompressedBlock,
26+
},
27+
fuel_tx::Transaction,
28+
fuel_types::{
29+
BlockHeight,
30+
ChainId,
31+
},
32+
};
33+
use rand::{
34+
rngs::ThreadRng,
35+
Rng,
36+
};
37+
use std::{
38+
borrow::Cow,
39+
path::Path,
40+
};
41+
42+
pub fn get_random_block_height(rng: &mut ThreadRng, block_count: u32) -> BlockHeight {
43+
BlockHeight::from(rng.gen_range(0..block_count))
44+
}
45+
46+
pub fn open_db(block_count: u32, tx_count: u32, method: &str) -> Database {
47+
Database::open_rocksdb(
48+
Path::new(format!("./{block_count}/{method}/{tx_count}").as_str()),
49+
None, // no caching
50+
StateRewindPolicy::NoRewind,
51+
)
52+
.unwrap()
53+
}
54+
55+
pub fn open_raw_rocksdb(
56+
block_count: u32,
57+
tx_count: u32,
58+
method: &str,
59+
) -> RocksDb<OnChain> {
60+
RocksDb::default_open(
61+
Path::new(format!("./{block_count}/{method}/{tx_count}").as_str()),
62+
None,
63+
)
64+
.unwrap()
65+
}
66+
67+
pub fn chain_id() -> ChainId {
68+
ChainId::default()
69+
}
70+
71+
pub fn get_full_block(
72+
view: &IterableKeyValueView<Column>,
73+
height: &BlockHeight,
74+
) -> anyhow::Result<Option<Block>> {
75+
let db_block = view.storage::<FullFuelBlocks>().get(height)?;
76+
if let Some(block) = db_block {
77+
Ok(Some(Cow::into_owned(block)))
78+
} else {
79+
Ok(None)
80+
}
81+
}
82+
83+
pub fn multi_get_block(
84+
database: &RocksDb<OnChain>,
85+
height: BlockHeight,
86+
) -> anyhow::Result<()> {
87+
let height_key = height.to_bytes();
88+
89+
let raw_block = database
90+
.get(&height_key, Column::FuelBlocks)?
91+
.ok_or(anyhow!("empty raw block"))?;
92+
let block: CompressedBlock = postcard::from_bytes(raw_block.as_slice())?;
93+
let tx_ids = block.transactions().iter();
94+
let raw_txs = database.multi_get(Column::Transactions.id(), tx_ids)?;
95+
for raw_tx in raw_txs.iter().flatten() {
96+
let _: Transaction = postcard::from_bytes(raw_tx.as_slice())?;
97+
}
98+
99+
Ok(())
100+
}

0 commit comments

Comments
 (0)