From 42fea4754738d69a8244b932ea9dda04f81e7b31 Mon Sep 17 00:00:00 2001 From: rymnc <43716372+rymnc@users.noreply.github.com> Date: Mon, 26 Aug 2024 02:42:27 +0530 Subject: [PATCH] feat(benches): add benchmarks for varied forms of db lookups --- CHANGELOG.md | 3 + Cargo.lock | 1 + benches/Cargo.toml | 5 + benches/benches/db_lookup_times.rs | 93 +++++++++++ .../benches/db_lookup_times_utils/matrix.rs | 13 ++ benches/benches/db_lookup_times_utils/mod.rs | 3 + benches/benches/db_lookup_times_utils/seed.rs | 155 ++++++++++++++++++ .../benches/db_lookup_times_utils/utils.rs | 89 ++++++++++ crates/fuel-core/src/state/rocks_db.rs | 71 ++++---- crates/storage/src/column.rs | 4 + .../storage/src/structured_storage/blocks.rs | 30 +++- crates/storage/src/tables.rs | 15 +- crates/types/src/blockchain/block.rs | 7 + 13 files changed, 453 insertions(+), 36 deletions(-) create mode 100644 benches/benches/db_lookup_times.rs create mode 100644 benches/benches/db_lookup_times_utils/matrix.rs create mode 100644 benches/benches/db_lookup_times_utils/mod.rs create mode 100644 benches/benches/db_lookup_times_utils/seed.rs create mode 100644 benches/benches/db_lookup_times_utils/utils.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8267a01c499..7d7067ad24d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- [2142](https://github.com/FuelLabs/fuel-core/pull/2142): Added benchmarks for varied forms of db lookups to assist in optimizations. + ## [Version 0.35.0] ### Added diff --git a/Cargo.lock b/Cargo.lock index 56845a7ce5a..68930f7687b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3220,6 +3220,7 @@ dependencies = [ "fuel-core-types", "futures", "p256", + "postcard", "primitive-types", "quanta", "rand", diff --git a/benches/Cargo.toml b/benches/Cargo.toml index ea283195ce4..eeb92dbc481 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -34,6 +34,7 @@ p256 = { version = "0.13", default-features = false, features = [ "digest", "ecdsa", ] } +postcard = { workspace = true } primitive-types = { workspace = true, default-features = false } quanta = "0.12" rand = { workspace = true } @@ -66,3 +67,7 @@ name = "block_target_gas" [[bench]] harness = false name = "transaction_throughput" + +[[bench]] +harness = false +name = "db_lookup_times" diff --git a/benches/benches/db_lookup_times.rs b/benches/benches/db_lookup_times.rs new file mode 100644 index 00000000000..84095986716 --- /dev/null +++ b/benches/benches/db_lookup_times.rs @@ -0,0 +1,93 @@ +use crate::db_lookup_times_utils::{ + matrix::matrix, + utils::{ + get_full_block, + get_random_block_height, + multi_get_block, + open_db, + open_raw_rocksdb, + }, +}; +use criterion::{ + criterion_group, + criterion_main, + Criterion, +}; +use db_lookup_times_utils::seed::{ + seed_compressed_blocks_and_transactions_matrix, + seed_full_block_matrix, +}; +use fuel_core_storage::transactional::AtomicView; + +mod db_lookup_times_utils; + +pub fn header_and_tx_lookup(c: &mut Criterion) { + let method = "header_and_tx"; + + seed_compressed_blocks_and_transactions_matrix(method); + let mut group = c.benchmark_group(method); + + for (block_count, tx_count) in matrix() { + let database = open_db(block_count, tx_count, method); + let height = get_random_block_height(block_count); + let view = database.latest_view().unwrap(); + group.bench_function(format!("{block_count}/{tx_count}"), |b| { + b.iter(|| { + let block = (&view).get_full_block(&height); + assert!(block.is_ok()); + assert!(block.unwrap().is_some()); + }); + }); + } + + group.finish(); +} + +pub fn multi_get_lookup(c: &mut Criterion) { + let method = "multi_get"; + + seed_compressed_blocks_and_transactions_matrix(method); + let mut group = c.benchmark_group(method); + + for (block_count, tx_count) in matrix() { + let database = open_raw_rocksdb(block_count, tx_count, method); + let height = get_random_block_height(block_count); + group.bench_function(format!("{block_count}/{tx_count}"), |b| { + b.iter(|| { + // todo: clean up + multi_get_block(&database, height); + }); + }); + } + + group.finish(); +} + +pub fn full_block_lookup(c: &mut Criterion) { + let method = "full_block"; + + seed_full_block_matrix(); + let mut group = c.benchmark_group(method); + + for (block_count, tx_count) in matrix() { + let database = open_db(block_count, tx_count, method); + let height = get_random_block_height(block_count); + let view = database.latest_view().unwrap(); + group.bench_function(format!("{block_count}/{tx_count}"), |b| { + b.iter(|| { + let full_block = get_full_block(&view, &height); + assert!(full_block.is_ok()); + assert!(full_block.unwrap().is_some()); + }); + }); + } + + group.finish(); +} + +criterion_group! { + name = benches; + config = Criterion::default().sample_size(10).measurement_time(std::time::Duration::from_secs(10)); + targets = header_and_tx_lookup, multi_get_lookup, full_block_lookup +} +criterion_main!(benches); diff --git a/benches/benches/db_lookup_times_utils/matrix.rs b/benches/benches/db_lookup_times_utils/matrix.rs new file mode 100644 index 00000000000..b5edd481d1c --- /dev/null +++ b/benches/benches/db_lookup_times_utils/matrix.rs @@ -0,0 +1,13 @@ +pub const BLOCK_COUNT_MATRIX: [u32; 1] = [1]; +pub const TX_COUNT_MATRIX: [u32; 1] = [1]; + +// todo: we can make this lazy loaded +pub fn matrix() -> Box> { + let iter = BLOCK_COUNT_MATRIX.iter().flat_map(|&block_count| { + TX_COUNT_MATRIX + .iter() + .map(move |&tx_count| (block_count, tx_count)) + }); + + Box::new(iter) +} diff --git a/benches/benches/db_lookup_times_utils/mod.rs b/benches/benches/db_lookup_times_utils/mod.rs new file mode 100644 index 00000000000..2bb42a84cbd --- /dev/null +++ b/benches/benches/db_lookup_times_utils/mod.rs @@ -0,0 +1,3 @@ +pub mod matrix; +pub mod seed; +pub mod utils; diff --git a/benches/benches/db_lookup_times_utils/seed.rs b/benches/benches/db_lookup_times_utils/seed.rs new file mode 100644 index 00000000000..6595f7b2f97 --- /dev/null +++ b/benches/benches/db_lookup_times_utils/seed.rs @@ -0,0 +1,155 @@ +use crate::db_lookup_times_utils::{ + matrix::matrix, + utils::open_raw_rocksdb, +}; +use fuel_core::{ + database::database_description::on_chain::OnChain, + state::rocks_db::RocksDb, +}; +use fuel_core_storage::{ + column::Column, + kv_store::{ + KeyValueMutate, + Value, + }, +}; +use fuel_core_types::{ + blockchain::{ + block::{ + Block, + PartialFuelBlock, + }, + header::{ + ConsensusHeader, + PartialBlockHeader, + }, + primitives::Empty, + }, + fuel_tx::{ + Transaction, + UniqueIdentifier, + }, + fuel_types::{ + BlockHeight, + ChainId, + }, +}; + +fn generate_bench_block(height: u32, tx_count: u32) -> Block { + let header = PartialBlockHeader { + application: Default::default(), + consensus: ConsensusHeader:: { + height: BlockHeight::from(height), + ..Default::default() + }, + }; + let txes = generate_bench_transactions(tx_count); + let block = PartialFuelBlock::new(header, txes); + block.generate(&[], Default::default()).unwrap() +} + +fn generate_bench_transactions(tx_count: u32) -> Vec { + let mut txes = vec![]; + for _ in 0..tx_count { + txes.push(Transaction::default_test_tx()); + } + txes +} + +fn height_key(block_height: u32) -> Vec { + BlockHeight::from(block_height).to_bytes().to_vec() +} + +fn insert_compressed_block( + database: &mut RocksDb, + height: u32, + tx_count: u32, +) -> Block { + let block = generate_bench_block(height, tx_count); + + let compressed_block = block.compress(&ChainId::default()); + let height_key = height_key(height); + + let raw_compressed_block = postcard::to_allocvec(&compressed_block).unwrap().to_vec(); + let raw_transactions = block + .transactions() + .into_iter() + .map(|tx| { + ( + tx.id(&ChainId::default()), + postcard::to_allocvec(tx).unwrap().to_vec(), + ) + }) + .collect::>(); + + // 1. insert into CompressedBlocks table + database + .put( + height_key.as_slice(), + Column::FuelBlocks, + Value::new(raw_compressed_block), + ) + .unwrap(); + // 2. insert into Transactions table + for (tx_id, tx) in raw_transactions { + database + .put(tx_id.as_slice(), Column::Transactions, Value::new(tx)) + .unwrap(); + } + + block +} + +fn insert_full_block(database: &mut RocksDb, height: u32, tx_count: u32) { + let block = match insert_compressed_block(database, height, tx_count) { + Block::V1(b) => b, + }; + + let height_key = height_key(height); + let raw_full_block = postcard::to_allocvec(&block).unwrap().to_vec(); + + // 3. insert into FullFuelBlocks table + database + .put( + height_key.as_slice(), + Column::FullFuelBlocks, + Value::new(raw_full_block), + ) + .unwrap(); +} + +fn seed_compressed_blocks_and_transactions( + database: &mut RocksDb, + block_count: u32, + tx_count: u32, +) -> Vec { + let mut blocks = vec![]; + for block_number in 0..block_count { + blocks.push(insert_compressed_block(database, block_number, tx_count)); + } + blocks +} + +pub fn seed_compressed_blocks_and_transactions_matrix(method: &str) { + for (block_count, tx_count) in matrix() { + let mut database = open_raw_rocksdb(block_count, tx_count, method); + let _ = + seed_compressed_blocks_and_transactions(&mut database, block_count, tx_count); + } +} + +fn seed_full_blocks(database: &mut RocksDb, block_count: u32, tx_count: u32) { + // we seed compressed blocks and transactions to not affect individual + // lookup times + + for block_number in 0..block_count { + insert_full_block(database, block_number, tx_count); + } +} + +pub fn seed_full_block_matrix() { + for (block_count, tx_count) in matrix() { + let mut database = open_raw_rocksdb(block_count, tx_count, "full_block"); + seed_full_blocks(&mut database, block_count, tx_count); + } +} diff --git a/benches/benches/db_lookup_times_utils/utils.rs b/benches/benches/db_lookup_times_utils/utils.rs new file mode 100644 index 00000000000..673a21ce0fe --- /dev/null +++ b/benches/benches/db_lookup_times_utils/utils.rs @@ -0,0 +1,89 @@ +use fuel_core::{ + database::{ + database_description::on_chain::OnChain, + Database, + }, + state::{ + historical_rocksdb::StateRewindPolicy, + rocks_db::RocksDb, + IterableKeyValueView, + }, +}; +use fuel_core_storage::{ + column::Column, + kv_store::{ + KeyValueInspect, + StorageColumn, + }, + tables::FullFuelBlocks, + StorageAsRef, +}; +use fuel_core_types::{ + blockchain::block::{ + Block, + CompressedBlock, + }, + fuel_tx::Transaction, + fuel_types::BlockHeight, +}; +use rand::Rng; +use std::{ + borrow::Cow, + path::Path, +}; + +pub fn get_random_block_height(block_count: u32) -> BlockHeight { + BlockHeight::from(rand::thread_rng().gen_range(0..block_count)) +} + +pub fn open_db(block_count: u32, tx_count: u32, method: &str) -> Database { + Database::open_rocksdb( + Path::new(format!("./{block_count}/{method}/{tx_count}").as_str()), + None, // no caching + StateRewindPolicy::NoRewind, + ) + .unwrap() +} + +pub fn open_raw_rocksdb( + block_count: u32, + tx_count: u32, + method: &str, +) -> RocksDb { + RocksDb::default_open( + Path::new(format!("./{block_count}/{method}/{tx_count}").as_str()), + None, + ) + .unwrap() +} + +pub fn get_full_block( + view: &IterableKeyValueView, + height: &BlockHeight, +) -> Result, anyhow::Error> { + let db_block = view.storage::().get(height)?; + if let Some(block) = db_block { + Ok(Some(Block::V1(Cow::into_owned(block)))) + } else { + Ok(None) + } +} + +pub fn multi_get_block(database: &RocksDb, height: BlockHeight) { + let height_key = height.to_bytes(); + + let raw_block = database + .get(&height_key, Column::FuelBlocks) + .unwrap() + .unwrap(); + let block: CompressedBlock = postcard::from_bytes(raw_block.as_slice()).unwrap(); + let tx_ids = block.transactions().into_iter(); + let raw_txs = database + .multi_get(Column::Transactions.id(), tx_ids) + .unwrap(); + for raw_tx in raw_txs { + if let Some(tx) = raw_tx { + let _: Transaction = postcard::from_bytes(tx.as_slice()).unwrap(); + } + } +} diff --git a/crates/fuel-core/src/state/rocks_db.rs b/crates/fuel-core/src/state/rocks_db.rs index a638ed29f7a..c94eb3aaed8 100644 --- a/crates/fuel-core/src/state/rocks_db.rs +++ b/crates/fuel-core/src/state/rocks_db.rs @@ -7,6 +7,11 @@ use crate::{ }, state::IterDirection, }; + +use super::rocks_db_key_iterator::{ + ExtractItem, + RocksDBKeyIterator, +}; use fuel_core_metrics::core_metrics::DatabaseMetrics; use fuel_core_storage::{ iter::{ @@ -18,11 +23,15 @@ use fuel_core_storage::{ KVItem, KeyItem, KeyValueInspect, + KeyValueMutate, StorageColumn, Value, WriteOperation, }, - transactional::Changes, + transactional::{ + Changes, + ReadTransaction, + }, Result as StorageResult, }; use itertools::Itertools; @@ -58,11 +67,6 @@ use std::{ }; use tempfile::TempDir; -use super::rocks_db_key_iterator::{ - ExtractItem, - RocksDBKeyIterator, -}; - type DB = DBWithThreadMode; /// Reimplementation of `tempdir::TempDir` that allows creating a new @@ -863,6 +867,33 @@ fn next_prefix(mut prefix: Vec) -> Option> { None } +impl KeyValueMutate for RocksDb +where + Description: DatabaseDescription, +{ + fn write( + &mut self, + key: &[u8], + column: Self::Column, + buf: &[u8], + ) -> StorageResult { + let mut transaction = self.read_transaction(); + let len = transaction.write(key, column, buf)?; + let changes = transaction.into_changes(); + self.commit_changes(&changes)?; + + Ok(len) + } + + fn delete(&mut self, key: &[u8], column: Self::Column) -> StorageResult<()> { + let mut transaction = self.read_transaction(); + transaction.delete(key, column)?; + let changes = transaction.into_changes(); + self.commit_changes(&changes)?; + Ok(()) + } +} + #[allow(non_snake_case)] #[cfg(test)] mod tests { @@ -871,7 +902,6 @@ mod tests { use fuel_core_storage::{ column::Column, kv_store::KeyValueMutate, - transactional::ReadTransaction, }; use std::collections::{ BTreeMap, @@ -879,33 +909,6 @@ mod tests { }; use tempfile::TempDir; - impl KeyValueMutate for RocksDb - where - Description: DatabaseDescription, - { - fn write( - &mut self, - key: &[u8], - column: Self::Column, - buf: &[u8], - ) -> StorageResult { - let mut transaction = self.read_transaction(); - let len = transaction.write(key, column, buf)?; - let changes = transaction.into_changes(); - self.commit_changes(&changes)?; - - Ok(len) - } - - fn delete(&mut self, key: &[u8], column: Self::Column) -> StorageResult<()> { - let mut transaction = self.read_transaction(); - transaction.delete(key, column)?; - let changes = transaction.into_changes(); - self.commit_changes(&changes)?; - Ok(()) - } - } - fn create_db() -> (RocksDb, TempDir) { let tmp_dir = TempDir::new().unwrap(); ( diff --git a/crates/storage/src/column.rs b/crates/storage/src/column.rs index 6557dc35b4d..d9dcc7943ef 100644 --- a/crates/storage/src/column.rs +++ b/crates/storage/src/column.rs @@ -71,6 +71,10 @@ pub enum Column { // TODO: Remove this column and use `Metadata` column instead. /// Table for genesis state import progress tracking. GenesisMetadata = 21, + + /// See [`FullFuelBlocks`](crate::tables::FullFuelBlocks) + /// we don't use this table at the moment, only used for benchmarks + FullFuelBlocks = 22, } impl Column { diff --git a/crates/storage/src/structured_storage/blocks.rs b/crates/storage/src/structured_storage/blocks.rs index d5d57ed4c8a..3716c3629f7 100644 --- a/crates/storage/src/structured_storage/blocks.rs +++ b/crates/storage/src/structured_storage/blocks.rs @@ -15,9 +15,13 @@ use crate::{ FuelBlockMerkleMetadata, }, FuelBlocks, + FullFuelBlocks, }, }; -use fuel_core_types::blockchain::block::CompressedBlock; +use fuel_core_types::blockchain::block::{ + BlockV1, + CompressedBlock, +}; use fuel_vm_private::fuel_tx::Bytes32; /// The encoder of `CompressedBlock` for the `FuelBlocks` table. @@ -32,6 +36,15 @@ impl Encode for BlockEncoder { } } +impl Encode for BlockEncoder { + type Encoder<'a> = [u8; Bytes32::LEN]; + + fn encode(value: &BlockV1) -> Self::Encoder<'_> { + let bytes: Bytes32 = value.id().into(); + bytes.into() + } +} + impl TableWithBlueprint for FuelBlocks { type Blueprint = Merklized< Primitive<4>, @@ -47,6 +60,21 @@ impl TableWithBlueprint for FuelBlocks { } } +impl TableWithBlueprint for FullFuelBlocks { + type Blueprint = Merklized< + Primitive<4>, + Postcard, + FuelBlockMerkleMetadata, + FuelBlockMerkleData, + BlockEncoder, + >; + type Column = Column; + + fn column() -> Column { + Column::FullFuelBlocks + } +} + #[cfg(test)] mod tests { use crate::{ diff --git a/crates/storage/src/tables.rs b/crates/storage/src/tables.rs index 0eda7e5e645..be647bf156a 100644 --- a/crates/storage/src/tables.rs +++ b/crates/storage/src/tables.rs @@ -4,7 +4,10 @@ use crate::Mappable; use fuel_core_types::{ blockchain::{ - block::CompressedBlock, + block::{ + BlockV1, + CompressedBlock, + }, consensus::Consensus, header::{ ConsensusParametersVersion, @@ -41,6 +44,16 @@ pub use fuel_vm_private::storage::{ /// Right now, we have only that type of block, but we will support others in the future. pub struct FuelBlocks; +/// Full block table. Contains all the information about the block. +pub struct FullFuelBlocks; + +impl Mappable for FullFuelBlocks { + type Key = Self::OwnedKey; + type OwnedKey = BlockHeight; + type Value = Self::OwnedValue; + type OwnedValue = BlockV1; +} + impl Mappable for FuelBlocks { /// Unique identifier of the fuel block. type Key = Self::OwnedKey; diff --git a/crates/types/src/blockchain/block.rs b/crates/types/src/blockchain/block.rs index d89a5e03682..1020bd69187 100644 --- a/crates/types/src/blockchain/block.rs +++ b/crates/types/src/blockchain/block.rs @@ -59,6 +59,13 @@ pub struct BlockV1 { transactions: Vec, } +impl BlockV1 { + /// Get the block id + pub fn id(&self) -> BlockId { + self.header.id() + } +} + /// Compressed version of the fuel `Block`. pub type CompressedBlock = Block;