feat(DbTx): add get_by_encoded_key (#13171)

This commit is contained in:
Hai | RISE
2024-12-06 20:58:17 +07:00
committed by GitHub
parent 634db30b6b
commit fdff4f18f2
6 changed files with 88 additions and 7 deletions

View File

@ -7,7 +7,7 @@ use crate::{
ReverseWalker, Walker, ReverseWalker, Walker,
}, },
database::Database, database::Database,
table::{DupSort, Table, TableImporter}, table::{DupSort, Encode, Table, TableImporter},
transaction::{DbTx, DbTxMut}, transaction::{DbTx, DbTxMut},
DatabaseError, DatabaseError,
}; };
@ -49,6 +49,13 @@ impl DbTx for TxMock {
Ok(None) Ok(None)
} }
fn get_by_encoded_key<T: Table>(
&self,
_key: &<T::Key as Encode>::Encoded,
) -> Result<Option<T::Value>, DatabaseError> {
Ok(None)
}
fn commit(self) -> Result<bool, DatabaseError> { fn commit(self) -> Result<bool, DatabaseError> {
Ok(true) Ok(true)
} }

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW}, cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW},
table::{DupSort, Table}, table::{DupSort, Encode, Table},
DatabaseError, DatabaseError,
}; };
@ -11,8 +11,15 @@ pub trait DbTx: Send + Sync {
/// `DupCursor` type for this read-only transaction /// `DupCursor` type for this read-only transaction
type DupCursor<T: DupSort>: DbDupCursorRO<T> + DbCursorRO<T> + Send + Sync; type DupCursor<T: DupSort>: DbDupCursorRO<T> + DbCursorRO<T> + Send + Sync;
/// Get value /// Get value by an owned key
fn get<T: Table>(&self, key: T::Key) -> Result<Option<T::Value>, DatabaseError>; fn get<T: Table>(&self, key: T::Key) -> Result<Option<T::Value>, DatabaseError>;
/// Get value by a reference to the encoded key, especially useful for "raw" keys
/// that encode to themselves like Address and B256. Doesn't need to clone a
/// reference key like `get`.
fn get_by_encoded_key<T: Table>(
&self,
key: &<T::Key as Encode>::Encoded,
) -> Result<Option<T::Value>, DatabaseError>;
/// Commit for read only transaction will consume and free transaction and allows /// Commit for read only transaction will consume and free transaction and allows
/// freeing of memory pages /// freeing of memory pages
fn commit(self) -> Result<bool, DatabaseError>; fn commit(self) -> Result<bool, DatabaseError>;

View File

@ -129,3 +129,8 @@ harness = false
name = "iai" name = "iai"
required-features = ["test-utils"] required-features = ["test-utils"]
harness = false harness = false
[[bench]]
name = "get"
required-features = ["test-utils"]
harness = false

View File

@ -0,0 +1,52 @@
#![allow(missing_docs)]
use alloy_primitives::TxHash;
use criterion::{criterion_group, criterion_main, Criterion};
use pprof::criterion::{Output, PProfProfiler};
use reth_db::{test_utils::create_test_rw_db_with_path, Database, TransactionHashNumbers};
use reth_db_api::transaction::DbTx;
use std::{fs, sync::Arc};
mod utils;
use utils::BENCH_DB_PATH;
criterion_group! {
name = benches;
config = Criterion::default().with_profiler(PProfProfiler::new(1, Output::Flamegraph(None)));
targets = get
}
criterion_main!(benches);
// Small benchmark showing that [get_by_encoded_key] is slightly faster than [get]
// for a reference key, as [get] requires copying or cloning the key first.
fn get(c: &mut Criterion) {
let mut group = c.benchmark_group("Get");
// Random keys to get
let mut keys = Vec::new();
for _ in 0..10_000_000 {
let key = TxHash::random();
keys.push(key);
}
// We don't bother mock the DB to reduce noise from DB I/O, value decoding, etc.
let _ = fs::remove_dir_all(BENCH_DB_PATH);
let db = Arc::try_unwrap(create_test_rw_db_with_path(BENCH_DB_PATH)).unwrap();
let tx = db.tx().expect("tx");
group.bench_function("get", |b| {
b.iter(|| {
for key in &keys {
tx.get::<TransactionHashNumbers>(*key).unwrap();
}
})
});
group.bench_function("get_by_encoded_key", |b| {
b.iter(|| {
for key in &keys {
tx.get_by_encoded_key::<TransactionHashNumbers>(key).unwrap();
}
})
});
}

View File

@ -283,8 +283,15 @@ impl<K: TransactionKind> DbTx for Tx<K> {
type DupCursor<T: DupSort> = Cursor<K, T>; type DupCursor<T: DupSort> = Cursor<K, T>;
fn get<T: Table>(&self, key: T::Key) -> Result<Option<<T as Table>::Value>, DatabaseError> { fn get<T: Table>(&self, key: T::Key) -> Result<Option<<T as Table>::Value>, DatabaseError> {
self.get_by_encoded_key::<T>(&key.encode())
}
fn get_by_encoded_key<T: Table>(
&self,
key: &<T::Key as Encode>::Encoded,
) -> Result<Option<T::Value>, DatabaseError> {
self.execute_with_operation_metric::<T, _>(Operation::Get, None, |tx| { self.execute_with_operation_metric::<T, _>(Operation::Get, None, |tx| {
tx.get(self.get_dbi::<T>()?, key.encode().as_ref()) tx.get(self.get_dbi::<T>()?, key.as_ref())
.map_err(|e| DatabaseError::Read(e.into()))? .map_err(|e| DatabaseError::Read(e.into()))?
.map(decode_one::<T>) .map(decode_one::<T>)
.transpose() .transpose()

View File

@ -219,9 +219,12 @@ impl Account {
/// ///
/// In case of a mismatch, `Err(Error::Assertion)` is returned. /// In case of a mismatch, `Err(Error::Assertion)` is returned.
pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> { pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
let account = tx.get::<tables::PlainAccountState>(address)?.ok_or_else(|| { let account =
Error::Assertion(format!("Expected account ({address}) is missing from DB: {self:?}")) tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
})?; Error::Assertion(format!(
"Expected account ({address}) is missing from DB: {self:?}"
))
})?;
assert_equal(self.balance, account.balance, "Balance does not match")?; assert_equal(self.balance, account.balance, "Balance does not match")?;
assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?; assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;