Merge pull request #273 from ethcore/tx-cache

Transaction & meta lru cache (WiP)
This commit is contained in:
Marek Kotewicz 2016-12-09 19:40:43 +01:00 committed by GitHub
commit 9a3c8de606
5 changed files with 82 additions and 12 deletions

16
Cargo.lock generated
View File

@ -180,6 +180,7 @@ dependencies = [
"elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "elastic-array 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.3.0", "ethcore-devtools 1.3.0",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"primitives 0.1.0", "primitives 0.1.0",
"rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)", "rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)",
@ -392,6 +393,11 @@ name = "libc"
version = "0.2.17" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "linked-hash-map"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.3.0" version = "0.3.0"
@ -412,6 +418,14 @@ dependencies = [
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "lru-cache"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "matches" name = "matches"
version = "0.1.4" version = "0.1.4"
@ -1241,8 +1255,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b" "checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b"
"checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b"
"checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8" "checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8"
"checksum linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bda158e0dabeb97ee8a401f4d17e479d6b891a14de0bba79d5cc2d4d325b5e48"
"checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd"
"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054"
"checksum lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "656fa4dfcb02bcf1063c592ba3ff6a5303ee1f2afe98c8a889e8b1a77c6dfdb7"
"checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1" "checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1"
"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
"checksum mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5c93a4bd787ddc6e7833c519b73a50883deb5863d76d9b71eb8216fb7f94e66" "checksum mime 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5c93a4bd787ddc6e7833c519b73a50883deb5863d76d9b71eb8216fb7f94e66"

View File

@ -15,6 +15,7 @@ parking_lot = "0.3"
test-data = { path = "../test-data" } test-data = { path = "../test-data" }
bit-vec = "0.4" bit-vec = "0.4"
log = "0.3" log = "0.3"
lru-cache = "0.1.0"
[features] [features]
dev = [] dev = []

View File

@ -9,6 +9,7 @@ extern crate chain;
extern crate serialization; extern crate serialization;
extern crate bit_vec; extern crate bit_vec;
#[macro_use] extern crate log; #[macro_use] extern crate log;
extern crate lru_cache;
#[cfg(test)] #[cfg(test)]
extern crate ethcore_devtools as devtools; extern crate ethcore_devtools as devtools;

View File

@ -10,8 +10,9 @@ use super::{BlockRef, BestBlock, BlockLocation, IndexedBlock, IndexedTransaction
use serialization::{serialize, deserialize}; use serialization::{serialize, deserialize};
use chain; use chain;
use parking_lot::RwLock; use parking_lot::RwLock;
use transaction_meta::TransactionMeta; use lru_cache::LruCache;
use transaction_meta::TransactionMeta;
use error::{Error, ConsistencyError, MetaError}; use error::{Error, ConsistencyError, MetaError};
use update_context::UpdateContext; use update_context::UpdateContext;
use block_provider::{BlockProvider, BlockHeaderProvider, AsBlockHeaderProvider}; use block_provider::{BlockProvider, BlockHeaderProvider, AsBlockHeaderProvider};
@ -36,6 +37,7 @@ const DB_VERSION: u32 = 1;
// TODO: check how bitcoin core deals with long forks // TODO: check how bitcoin core deals with long forks
const MAX_FORK_ROUTE_PRESET: usize = 2048; const MAX_FORK_ROUTE_PRESET: usize = 2048;
const TRANSACTION_CACHE_SIZE: usize = 524288;
/// Blockchain storage interface /// Blockchain storage interface
pub trait Store : BlockProvider + BlockStapler + TransactionProvider + TransactionMetaProvider + AsBlockHeaderProvider { pub trait Store : BlockProvider + BlockStapler + TransactionProvider + TransactionMetaProvider + AsBlockHeaderProvider {
@ -50,6 +52,8 @@ pub trait Store : BlockProvider + BlockStapler + TransactionProvider + Transacti
pub struct Storage { pub struct Storage {
database: Database, database: Database,
best_block: RwLock<Option<BestBlock>>, best_block: RwLock<Option<BestBlock>>,
transaction_cache: RwLock<LruCache<H256, chain::Transaction>>,
meta_cache: RwLock<LruCache<H256, TransactionMeta>>,
} }
const KEY_VERSION: &'static[u8] = b"version"; const KEY_VERSION: &'static[u8] = b"version";
@ -83,6 +87,8 @@ impl Storage {
let storage = Storage { let storage = Storage {
database: db, database: db,
best_block: RwLock::default(), best_block: RwLock::default(),
transaction_cache: RwLock::new(LruCache::new(TRANSACTION_CACHE_SIZE)),
meta_cache: RwLock::new(LruCache::new(TRANSACTION_CACHE_SIZE)),
}; };
match storage.read_meta_u32(KEY_VERSION) { match storage.read_meta_u32(KEY_VERSION) {
@ -185,6 +191,7 @@ impl Storage {
); );
} }
// here the iteration continues from 1th element (0th consumed above ^^^)
for (accepted_hash, accepted_tx) in accepted_txs { for (accepted_hash, accepted_tx) in accepted_txs {
context.meta.insert( context.meta.insert(
accepted_hash.clone(), accepted_hash.clone(),
@ -239,8 +246,9 @@ impl Storage {
let tx = self.transaction(tx_hash) let tx = self.transaction(tx_hash)
.expect("Transaction in the saved block should exist as a separate entity indefinitely"); .expect("Transaction in the saved block should exist as a separate entity indefinitely");
// remove meta // remove meta & meta cache
context.db_transaction.delete(Some(COL_TRANSACTIONS_META), &**tx_hash); context.db_transaction.delete(Some(COL_TRANSACTIONS_META), &**tx_hash);
self.meta_cache.write().remove(&tx_hash);
// coinbase transaction does not have inputs // coinbase transaction does not have inputs
if tx_hash_num == 0 { if tx_hash_num == 0 {
@ -585,7 +593,11 @@ impl BlockStapler for Storage {
// we always update best hash even if it is not changed // we always update best hash even if it is not changed
context.db_transaction.put(Some(COL_META), KEY_BEST_BLOCK_HASH, &*new_best_hash); context.db_transaction.put(Some(COL_META), KEY_BEST_BLOCK_HASH, &*new_best_hash);
// write accumulated transactions meta // write accumulated transactions meta and update cache
{
let mut cache = self.meta_cache.write();
for (hash, meta) in context.meta.iter() { cache.insert(hash.clone(), meta.clone()); }
}
try!(context.apply(&self.database)); try!(context.apply(&self.database));
trace!(target: "db", "Best block now ({}, {})", &new_best_hash.to_reversed_str(), &new_best_number); trace!(target: "db", "Best block now ({}, {})", &new_best_hash.to_reversed_str(), &new_best_number);
@ -625,18 +637,57 @@ impl TransactionProvider for Storage {
} }
fn transaction(&self, hash: &H256) -> Option<chain::Transaction> { fn transaction(&self, hash: &H256) -> Option<chain::Transaction> {
self.transaction_bytes(hash).map(|tx_bytes| { let mut cache = self.transaction_cache.write();
deserialize(tx_bytes.as_ref()).expect("Failed to deserialize transaction: db corrupted?")
}) let (tx, is_cached) = {
let cached_transaction = cache.get_mut(hash);
match cached_transaction {
None => {
(
self.transaction_bytes(hash).map(|tx_bytes| {
let tx: chain::Transaction = deserialize(tx_bytes.as_ref())
.expect("Failed to deserialize transaction: db corrupted?");
tx
}),
false
)
},
Some(tx) => (Some(tx.clone()), true)
}
};
match tx {
Some(ref tx) => { if !is_cached { cache.insert(hash.clone(), tx.clone()); } }
None => {}
};
tx
} }
} }
impl TransactionMetaProvider for Storage { impl TransactionMetaProvider for Storage {
fn transaction_meta(&self, hash: &H256) -> Option<TransactionMeta> { fn transaction_meta(&self, hash: &H256) -> Option<TransactionMeta> {
self.get(COL_TRANSACTIONS_META, &**hash).map(|val| let mut cache = self.meta_cache.write();
TransactionMeta::from_bytes(&val).expect("Invalid transaction metadata: db corrupted?")
) let (meta, is_cached) = {
let cached_meta = cache.get_mut(hash);
match cached_meta {
None => {
(self.get(COL_TRANSACTIONS_META, &**hash).map(|val|
TransactionMeta::from_bytes(&val).expect("Invalid transaction metadata: db corrupted?")
), false)
},
Some(meta) => (Some(meta.clone()), true)
}
};
match meta {
Some(ref meta) => { if !is_cached { cache.insert(hash.clone(), meta.clone()); } }
None => {}
};
meta
} }
} }
@ -1195,6 +1246,7 @@ mod tests {
store.decanonize_block(&mut update_context, &block_hash) store.decanonize_block(&mut update_context, &block_hash)
.expect("Decanonizing block #1 which was just inserted should not fail"); .expect("Decanonizing block #1 which was just inserted should not fail");
update_context.apply(&store.database).unwrap(); update_context.apply(&store.database).unwrap();
store.meta_cache.write().clear();
let genesis_meta = store.transaction_meta(&genesis_coinbase) let genesis_meta = store.transaction_meta(&genesis_coinbase)
.expect("Transaction meta for the genesis coinbase transaction should exist"); .expect("Transaction meta for the genesis coinbase transaction should exist");

View File

@ -14,8 +14,8 @@ const COINBASE_MATURITY: u32 = 100; // 2 hours
pub const MAX_BLOCK_SIZE: usize = 1_000_000; pub const MAX_BLOCK_SIZE: usize = 1_000_000;
pub const MAX_BLOCK_SIGOPS: usize = 20_000; pub const MAX_BLOCK_SIGOPS: usize = 20_000;
const TRANSACTIONS_VERIFY_THREADS: usize = 4; const TRANSACTIONS_VERIFY_THREADS: usize = 8;
const TRANSACTIONS_VERIFY_PARALLEL_THRESHOLD: usize = 16; const TRANSACTIONS_VERIFY_PARALLEL_THRESHOLD: usize = 32;
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
/// Block verification chain /// Block verification chain
@ -306,7 +306,7 @@ impl ChainVerifier {
let mut last = 0; let mut last = 0;
for num_task in 0..TRANSACTIONS_VERIFY_THREADS { for num_task in 0..TRANSACTIONS_VERIFY_THREADS {
let from = last; let from = last;
last = ::std::cmp::max(1, block.transaction_count() / TRANSACTIONS_VERIFY_THREADS); last = from + ::std::cmp::max(1, block.transaction_count() / TRANSACTIONS_VERIFY_THREADS);
if num_task == TRANSACTIONS_VERIFY_THREADS - 1 { last = block.transaction_count(); }; if num_task == TRANSACTIONS_VERIFY_THREADS - 1 { last = block.transaction_count(); };
transaction_tasks.push(Task::new(block, location.height(), from, last)); transaction_tasks.push(Task::new(block, location.height(), from, last));
} }