mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: speed improvement part2
This commit is contained in:
@@ -3464,7 +3464,6 @@ impl SeriesTree_Transactions {
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Transactions_Raw {
|
||||
pub first_tx_index: SeriesPattern18<TxIndex>,
|
||||
pub height: SeriesPattern19<Height>,
|
||||
pub txid: SeriesPattern19<Txid>,
|
||||
pub tx_version: SeriesPattern19<TxVersion>,
|
||||
pub raw_locktime: SeriesPattern19<RawLockTime>,
|
||||
@@ -3479,7 +3478,6 @@ impl SeriesTree_Transactions_Raw {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
first_tx_index: SeriesPattern18::new(client.clone(), "first_tx_index".to_string()),
|
||||
height: SeriesPattern19::new(client.clone(), "height".to_string()),
|
||||
txid: SeriesPattern19::new(client.clone(), "txid".to_string()),
|
||||
tx_version: SeriesPattern19::new(client.clone(), "tx_version".to_string()),
|
||||
raw_locktime: SeriesPattern19::new(client.clone(), "raw_locktime".to_string()),
|
||||
@@ -3495,14 +3493,12 @@ impl SeriesTree_Transactions_Raw {
|
||||
/// Series tree node.
|
||||
pub struct SeriesTree_Transactions_Count {
|
||||
pub total: AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern,
|
||||
pub is_coinbase: SeriesPattern19<StoredBool>,
|
||||
}
|
||||
|
||||
impl SeriesTree_Transactions_Count {
|
||||
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
|
||||
Self {
|
||||
total: AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern::new(client.clone(), "tx_count".to_string()),
|
||||
is_coinbase: SeriesPattern19::new(client.clone(), "is_coinbase".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ impl Vecs {
|
||||
indexer: &Indexer<M>,
|
||||
range: Range<usize>,
|
||||
) -> Vec<f64> {
|
||||
let total_txs = indexer.vecs.transactions.height.len();
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
|
||||
// Pre-collect height-indexed data for the range (plus one extra for next-block lookups)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_types::{StoredBool, TxIndex, Version};
|
||||
use vecdb::{Database, LazyVecFrom2, ReadableCloneableVec};
|
||||
use brk_types::Version;
|
||||
use vecdb::Database;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{
|
||||
@@ -13,25 +12,11 @@ impl Vecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexer: &Indexer,
|
||||
indexes: &indexes::Vecs,
|
||||
cached_starts: &CachedWindowStarts,
|
||||
) -> Result<Self> {
|
||||
let tx_index_to_is_coinbase = LazyVecFrom2::init(
|
||||
"is_coinbase",
|
||||
version,
|
||||
indexer.vecs.transactions.height.read_only_boxed_clone(),
|
||||
indexer
|
||||
.vecs
|
||||
.transactions
|
||||
.first_tx_index
|
||||
.read_only_boxed_clone(),
|
||||
|index: TxIndex, _height, first_tx_index| StoredBool::from(index == first_tx_index),
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
total: PerBlockFull::forced_import(db, "tx_count", version, indexes, cached_starts)?,
|
||||
is_coinbase: tx_index_to_is_coinbase,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, StoredBool, StoredU64, TxIndex};
|
||||
use vecdb::{LazyVecFrom2, Rw, StorageMode};
|
||||
use brk_types::StoredU64;
|
||||
use vecdb::{Rw, StorageMode};
|
||||
|
||||
use crate::internal::PerBlockFull;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub total: PerBlockFull<StoredU64, M>,
|
||||
pub is_coinbase: LazyVecFrom2<TxIndex, StoredBool, TxIndex, Height, Height, TxIndex>,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_types::{FeeRate, Indexes, OutPoint, Sats, TxInIndex, VSize};
|
||||
use brk_types::{FeeRate, Indexes, OutPoint, Sats, TxInIndex, TxIndex, VSize};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecIndex, WritableVec, unlikely};
|
||||
|
||||
use super::super::size;
|
||||
@@ -125,11 +125,9 @@ impl Vecs {
|
||||
let start_height = if start_tx == 0 {
|
||||
0
|
||||
} else {
|
||||
indexer
|
||||
.vecs
|
||||
.transactions
|
||||
.height
|
||||
.collect_one_at(start_tx)
|
||||
indexes
|
||||
.tx_heights
|
||||
.get_shared(TxIndex::from(start_tx))
|
||||
.unwrap()
|
||||
.to_usize()
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ impl Vecs {
|
||||
let db = open_db(parent_path, super::DB_NAME, 10_000_000)?;
|
||||
let version = parent_version;
|
||||
|
||||
let count = CountVecs::forced_import(&db, version, indexer, indexes, cached_starts)?;
|
||||
let count = CountVecs::forced_import(&db, version, indexes, cached_starts)?;
|
||||
let size = SizeVecs::forced_import(&db, version, indexer, indexes)?;
|
||||
let fees = FeesVecs::forced_import(&db, version, indexes)?;
|
||||
let versions = VersionsVecs::forced_import(&db, version, indexes, cached_starts)?;
|
||||
|
||||
@@ -43,7 +43,6 @@ impl BlockProcessor<'_> {
|
||||
already_added: &mut ByAddrType<FxHashMap<AddrHash, TypeIndex>>,
|
||||
same_block_info: &mut FxHashMap<OutPoint, SameBlockOutputInfo>,
|
||||
) -> Result<()> {
|
||||
let height = self.height;
|
||||
let indexes = &mut *self.indexes;
|
||||
|
||||
// Split transactions vecs: finalize needs first_txout_index/first_txin_index, metadata needs the rest
|
||||
@@ -85,7 +84,7 @@ impl BlockProcessor<'_> {
|
||||
same_block_info,
|
||||
)
|
||||
},
|
||||
|| tx::store_tx_metadata(height, txs, txid_prefix_store, &mut tx_metadata),
|
||||
|| tx::store_tx_metadata(txs, txid_prefix_store, &mut tx_metadata),
|
||||
);
|
||||
|
||||
finalize_result?;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::{Error, Result};
|
||||
use brk_store::Store;
|
||||
use brk_types::{Height, StoredBool, TxIndex, Txid, TxidPrefix};
|
||||
use brk_types::{StoredBool, TxIndex, Txid, TxidPrefix};
|
||||
use rayon::prelude::*;
|
||||
use tracing::error;
|
||||
use vecdb::{AnyVec, WritableVec, likely};
|
||||
@@ -89,7 +89,6 @@ impl<'a> BlockProcessor<'a> {
|
||||
}
|
||||
|
||||
pub(super) fn store_tx_metadata(
|
||||
height: Height,
|
||||
txs: Vec<ComputedTx>,
|
||||
store: &mut Store<TxidPrefix, TxIndex>,
|
||||
md: &mut TxMetadataVecs<'_>,
|
||||
@@ -98,7 +97,6 @@ pub(super) fn store_tx_metadata(
|
||||
if ct.prev_tx_index_opt.is_none() {
|
||||
store.insert(ct.txid_prefix, ct.tx_index);
|
||||
}
|
||||
md.height.checked_push(ct.tx_index, height)?;
|
||||
md.tx_version
|
||||
.checked_push(ct.tx_index, ct.tx.version.into())?;
|
||||
md.txid.checked_push(ct.tx_index, ct.txid)?;
|
||||
|
||||
@@ -6,7 +6,8 @@ use brk_types::{
|
||||
};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{
|
||||
AnyStoredVec, BytesVec, Database, ImportableVec, PcoVec, Rw, Stamp, StorageMode, WritableVec,
|
||||
AnyStoredVec, BytesVec, CachedVec, Database, ImportableVec, PcoVec, Rw, Stamp, StorageMode,
|
||||
WritableVec,
|
||||
};
|
||||
|
||||
use crate::parallel_import;
|
||||
@@ -14,12 +15,16 @@ use crate::parallel_import;
|
||||
#[derive(Traversable)]
|
||||
pub struct BlocksVecs<M: StorageMode = Rw> {
|
||||
pub blockhash: M::Stored<BytesVec<Height, BlockHash>>,
|
||||
#[traversable(skip)]
|
||||
pub cached_blockhash: CachedVec<Height, BlockHash>,
|
||||
pub coinbase_tag: M::Stored<BytesVec<Height, CoinbaseTag>>,
|
||||
#[traversable(wrap = "difficulty", rename = "value")]
|
||||
pub difficulty: M::Stored<PcoVec<Height, StoredF64>>,
|
||||
/// Doesn't guarantee continuity due to possible reorgs and more generally the nature of mining
|
||||
#[traversable(wrap = "time")]
|
||||
pub timestamp: M::Stored<PcoVec<Height, Timestamp>>,
|
||||
#[traversable(skip)]
|
||||
pub cached_timestamp: CachedVec<Height, Timestamp>,
|
||||
#[traversable(wrap = "size", rename = "base")]
|
||||
pub total: M::Stored<PcoVec<Height, StoredU64>>,
|
||||
#[traversable(wrap = "weight", rename = "base")]
|
||||
@@ -56,11 +61,16 @@ impl BlocksVecs {
|
||||
segwit_size = PcoVec::forced_import(db, "segwit_size", version),
|
||||
segwit_weight = PcoVec::forced_import(db, "segwit_weight", version),
|
||||
};
|
||||
let cached_blockhash = CachedVec::new(&blockhash);
|
||||
let cached_timestamp = CachedVec::new(×tamp);
|
||||
|
||||
Ok(Self {
|
||||
blockhash,
|
||||
cached_blockhash,
|
||||
coinbase_tag,
|
||||
difficulty,
|
||||
timestamp,
|
||||
cached_timestamp,
|
||||
total,
|
||||
weight,
|
||||
position,
|
||||
|
||||
@@ -14,7 +14,6 @@ use crate::parallel_import;
|
||||
#[derive(Traversable)]
|
||||
pub struct TransactionsVecs<M: StorageMode = Rw> {
|
||||
pub first_tx_index: M::Stored<PcoVec<Height, TxIndex>>,
|
||||
pub height: M::Stored<PcoVec<TxIndex, Height>>,
|
||||
pub txid: M::Stored<BytesVec<TxIndex, Txid>>,
|
||||
pub tx_version: M::Stored<PcoVec<TxIndex, TxVersion>>,
|
||||
pub raw_locktime: M::Stored<PcoVec<TxIndex, RawLockTime>>,
|
||||
@@ -28,7 +27,6 @@ pub struct TransactionsVecs<M: StorageMode = Rw> {
|
||||
}
|
||||
|
||||
pub struct TxMetadataVecs<'a> {
|
||||
pub height: &'a mut PcoVec<TxIndex, Height>,
|
||||
pub tx_version: &'a mut PcoVec<TxIndex, TxVersion>,
|
||||
pub txid: &'a mut BytesVec<TxIndex, Txid>,
|
||||
pub raw_locktime: &'a mut PcoVec<TxIndex, RawLockTime>,
|
||||
@@ -49,7 +47,6 @@ impl TransactionsVecs {
|
||||
&mut self.first_txout_index,
|
||||
&mut self.first_txin_index,
|
||||
TxMetadataVecs {
|
||||
height: &mut self.height,
|
||||
tx_version: &mut self.tx_version,
|
||||
txid: &mut self.txid,
|
||||
raw_locktime: &mut self.raw_locktime,
|
||||
@@ -63,7 +60,6 @@ impl TransactionsVecs {
|
||||
pub fn forced_import(db: &Database, version: Version) -> Result<Self> {
|
||||
let (
|
||||
first_tx_index,
|
||||
height,
|
||||
txid,
|
||||
tx_version,
|
||||
raw_locktime,
|
||||
@@ -75,7 +71,6 @@ impl TransactionsVecs {
|
||||
position,
|
||||
) = parallel_import! {
|
||||
first_tx_index = PcoVec::forced_import(db, "first_tx_index", version),
|
||||
height = PcoVec::forced_import(db, "height", version),
|
||||
txid = BytesVec::forced_import(db, "txid", version),
|
||||
tx_version = PcoVec::forced_import(db, "tx_version", version),
|
||||
raw_locktime = PcoVec::forced_import(db, "raw_locktime", version),
|
||||
@@ -88,7 +83,6 @@ impl TransactionsVecs {
|
||||
};
|
||||
Ok(Self {
|
||||
first_tx_index,
|
||||
height,
|
||||
txid,
|
||||
tx_version,
|
||||
raw_locktime,
|
||||
@@ -104,7 +98,6 @@ impl TransactionsVecs {
|
||||
pub fn truncate(&mut self, height: Height, tx_index: TxIndex, stamp: Stamp) -> Result<()> {
|
||||
self.first_tx_index
|
||||
.truncate_if_needed_with_stamp(height, stamp)?;
|
||||
self.height.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
||||
self.txid.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
||||
self.tx_version
|
||||
.truncate_if_needed_with_stamp(tx_index, stamp)?;
|
||||
@@ -128,7 +121,6 @@ impl TransactionsVecs {
|
||||
pub fn par_iter_mut_any(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
[
|
||||
&mut self.first_tx_index as &mut dyn AnyStoredVec,
|
||||
&mut self.height,
|
||||
&mut self.txid,
|
||||
&mut self.tx_version,
|
||||
&mut self.raw_locktime,
|
||||
|
||||
@@ -150,7 +150,7 @@ fn main() {
|
||||
|
||||
let idx = |m: usize, s: usize| -> usize { m * start_heights.len() + s };
|
||||
|
||||
let total_txs = indexer.vecs.transactions.height.len();
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
|
||||
let first_tx_index: Vec<TxIndex> = indexer.vecs.transactions.first_tx_index.collect();
|
||||
|
||||
@@ -46,7 +46,7 @@ fn main() {
|
||||
let config = Config::default();
|
||||
let window_size = config.window_size;
|
||||
|
||||
let total_txs = indexer.vecs.transactions.height.len();
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
|
||||
let first_tx_index: Vec<TxIndex> = indexer.vecs.transactions.first_tx_index.collect();
|
||||
|
||||
@@ -91,7 +91,7 @@ fn main() {
|
||||
// Build per-block RAW histograms from the lowest start height.
|
||||
eprintln!("Building histograms from height {}...", lowest);
|
||||
|
||||
let total_txs = indexer.vecs.transactions.height.len();
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
|
||||
let first_txout_index_reader = indexer.vecs.transactions.first_txout_index.reader();
|
||||
|
||||
@@ -181,7 +181,7 @@ fn main() {
|
||||
let config = Config::default();
|
||||
let mut oracle = Oracle::new(cents_to_bin(start_price * 100.0), config);
|
||||
|
||||
let total_txs = indexer.vecs.transactions.height.len();
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
|
||||
// Pre-collect height-indexed vecs (small). Transaction-indexed vecs are too large.
|
||||
|
||||
@@ -163,7 +163,7 @@ fn main() {
|
||||
// Phase 1: precompute per-block data in a single pass over the indexer.
|
||||
eprintln!("Phase 1: precomputing block data...");
|
||||
|
||||
let total_txs = indexer.vecs.transactions.height.len();
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
|
||||
let first_tx_index: Vec<TxIndex> = indexer.vecs.transactions.first_tx_index.collect();
|
||||
|
||||
@@ -161,7 +161,7 @@ fn main() {
|
||||
// filter at different tolerance thresholds in Phase 2.
|
||||
eprintln!("Phase 1: precomputing block data...");
|
||||
|
||||
let total_txs = indexer.vecs.transactions.height.len();
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
|
||||
let first_tx_index: Vec<TxIndex> = indexer.vecs.transactions.first_tx_index.collect();
|
||||
|
||||
@@ -148,7 +148,7 @@ fn main() {
|
||||
];
|
||||
|
||||
// Build per-block filtered histograms from the indexer, feeding all oracles in one pass.
|
||||
let total_txs = indexer.vecs.transactions.height.len();
|
||||
let total_txs = indexer.vecs.transactions.txid.len();
|
||||
let total_outputs = indexer.vecs.outputs.value.len();
|
||||
|
||||
// Pre-collect height-indexed vecs (small). Transaction-indexed vecs are too large.
|
||||
|
||||
@@ -4,7 +4,7 @@ use bitcoin::{Network, PublicKey, ScriptBuf};
|
||||
use brk_error::{Error, OptionData, Result};
|
||||
use brk_types::{
|
||||
Addr, AddrBytes, AddrChainStats, AddrHash, AddrIndexOutPoint, AddrIndexTxIndex, AddrStats,
|
||||
AnyAddrDataIndexEnum, BlockHash, Dollars, Height, OutputType, Timestamp, Transaction, TxIndex,
|
||||
AnyAddrDataIndexEnum, Dollars, Height, OutputType, Transaction, TxIndex,
|
||||
TxStatus, Txid, TypeIndex, Unit, Utxo, Vout,
|
||||
};
|
||||
use vecdb::{ReadableVec, VecIndex};
|
||||
@@ -189,11 +189,8 @@ impl Query {
|
||||
let txid_reader = vecs.transactions.txid.reader();
|
||||
let first_txout_index_reader = vecs.transactions.first_txout_index.reader();
|
||||
let value_reader = vecs.outputs.value.reader();
|
||||
let blockhash_reader = vecs.blocks.blockhash.reader();
|
||||
let tx_heights = &self.computer().indexes.tx_heights;
|
||||
let mut block_ts_cursor = vecs.blocks.timestamp.cursor();
|
||||
|
||||
let mut cached_block: Option<(Height, BlockHash, Timestamp)> = None;
|
||||
let mut utxos = Vec::with_capacity(outpoints.len());
|
||||
|
||||
for (tx_index, vout) in outpoints {
|
||||
@@ -202,16 +199,8 @@ impl Query {
|
||||
let first_txout_index = first_txout_index_reader.get(tx_index.to_usize());
|
||||
let value = value_reader.get(usize::from(first_txout_index + vout));
|
||||
|
||||
let (block_hash, block_time) = if let Some((h, ref bh, bt)) = cached_block
|
||||
&& h == height
|
||||
{
|
||||
(bh.clone(), bt)
|
||||
} else {
|
||||
let bh = blockhash_reader.get(height.to_usize());
|
||||
let bt = block_ts_cursor.get(height.to_usize()).data()?;
|
||||
cached_block = Some((height, bh.clone(), bt));
|
||||
(bh, bt)
|
||||
};
|
||||
let block_hash = vecs.blocks.cached_blockhash.collect_one(height).data()?;
|
||||
let block_time = vecs.blocks.cached_timestamp.collect_one(height).data()?;
|
||||
|
||||
utxos.push(Utxo {
|
||||
txid,
|
||||
@@ -268,11 +257,10 @@ impl Query {
|
||||
.next_back()
|
||||
.map(|(key, _): (AddrIndexTxIndex, Unit)| key.tx_index())
|
||||
.ok_or(Error::UnknownAddr)?;
|
||||
self.indexer()
|
||||
.vecs
|
||||
.transactions
|
||||
.height
|
||||
.collect_one(last_tx_index)
|
||||
self.computer()
|
||||
.indexes
|
||||
.tx_heights
|
||||
.get_shared(last_tx_index)
|
||||
.ok_or(Error::UnknownAddr)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,9 @@ impl Query {
|
||||
let blockhash = indexer
|
||||
.vecs
|
||||
.blocks
|
||||
.blockhash
|
||||
.reader()
|
||||
.get(usize::from(height));
|
||||
.cached_blockhash
|
||||
.collect_one(height)
|
||||
.data()?;
|
||||
|
||||
// Convert timestamp to ISO 8601 format
|
||||
let ts_secs: i64 = (*best_ts).into();
|
||||
|
||||
@@ -4,7 +4,7 @@ use bitcoin::{consensus::Decodable, hex::DisplayHex};
|
||||
use brk_error::{Error, OptionData, Result};
|
||||
use brk_types::{
|
||||
BlkPosition, BlockHash, Height, OutPoint, OutputType, RawLockTime, Sats, StoredU32, Timestamp,
|
||||
Transaction, TxIn, TxInIndex, TxIndex, TxOut, TxOutIndex, TxStatus, Txid, Vout, Weight,
|
||||
Transaction, TxIn, TxInIndex, TxIndex, TxOut, TxStatus, Txid, TypeIndex, Vout, Weight,
|
||||
};
|
||||
use rustc_hash::FxHashMap;
|
||||
use vecdb::{AnyVec, ReadableVec, VecIndex};
|
||||
@@ -75,6 +75,7 @@ impl Query {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let t0 = std::time::Instant::now();
|
||||
let len = indices.len();
|
||||
|
||||
// Sort positions ascending for sequential I/O (O(n) when already sorted)
|
||||
@@ -92,9 +93,6 @@ impl Query {
|
||||
let mut total_size_cursor = indexer.vecs.transactions.total_size.cursor();
|
||||
let mut first_txin_cursor = indexer.vecs.transactions.first_txin_index.cursor();
|
||||
let mut position_cursor = indexer.vecs.transactions.position.cursor();
|
||||
let blockhash_reader = indexer.vecs.blocks.blockhash.reader();
|
||||
let mut block_ts_cursor = indexer.vecs.blocks.timestamp.cursor();
|
||||
|
||||
struct DecodedTx {
|
||||
pos: usize,
|
||||
tx_index: TxIndex,
|
||||
@@ -109,7 +107,6 @@ impl Query {
|
||||
outpoints: Vec<OutPoint>,
|
||||
}
|
||||
|
||||
let mut cached_block: Option<(Height, BlockHash, Timestamp)> = None;
|
||||
let mut decoded_txs: Vec<DecodedTx> = Vec::with_capacity(len);
|
||||
let mut total_inputs: usize = 0;
|
||||
|
||||
@@ -125,16 +122,18 @@ impl Query {
|
||||
let first_txin_index: TxInIndex = first_txin_cursor.get(idx).data()?;
|
||||
let position: BlkPosition = position_cursor.get(idx).data()?;
|
||||
|
||||
let (block_hash, block_time) = if let Some((h, ref bh, bt)) = cached_block
|
||||
&& h == height
|
||||
{
|
||||
(bh.clone(), bt)
|
||||
} else {
|
||||
let bh = blockhash_reader.get(height.to_usize());
|
||||
let bt = block_ts_cursor.get(height.to_usize()).data()?;
|
||||
cached_block = Some((height, bh.clone(), bt));
|
||||
(bh, bt)
|
||||
};
|
||||
let block_hash = indexer
|
||||
.vecs
|
||||
.blocks
|
||||
.cached_blockhash
|
||||
.collect_one(height)
|
||||
.data()?;
|
||||
let block_time = indexer
|
||||
.vecs
|
||||
.blocks
|
||||
.cached_timestamp
|
||||
.collect_one(height)
|
||||
.data()?;
|
||||
|
||||
let buffer = reader.read_raw_bytes(position, *total_size as usize)?;
|
||||
let decoded = bitcoin::Transaction::consensus_decode(&mut Cursor::new(buffer))
|
||||
@@ -157,19 +156,39 @@ impl Query {
|
||||
});
|
||||
}
|
||||
|
||||
// Phase 1b: Batch-read outpoints via cursor (PcoVec — sequential
|
||||
// cursor avoids re-decompressing the same pages)
|
||||
let t_phase1a = t0.elapsed();
|
||||
|
||||
// Phase 1b: Batch-read outpoints + prevout data via cursors (PcoVec —
|
||||
// sequential cursor avoids re-decompressing the same pages).
|
||||
// Reading output_type/type_index/value HERE from inputs vecs (sequential)
|
||||
// avoids random-reading them from outputs vecs in Phase 2.
|
||||
let mut outpoint_cursor = indexer.vecs.inputs.outpoint.cursor();
|
||||
let mut input_output_type_cursor = indexer.vecs.inputs.output_type.cursor();
|
||||
let mut input_type_index_cursor = indexer.vecs.inputs.type_index.cursor();
|
||||
let mut input_value_cursor = self.computer().inputs.spent.value.cursor();
|
||||
|
||||
let mut prevout_input_data: FxHashMap<OutPoint, (OutputType, TypeIndex, Sats)> =
|
||||
FxHashMap::with_capacity_and_hasher(total_inputs, Default::default());
|
||||
|
||||
for dtx in &mut decoded_txs {
|
||||
let start = usize::from(dtx.first_txin_index);
|
||||
let count = dtx.decoded.input.len();
|
||||
let mut outpoints = Vec::with_capacity(count);
|
||||
for i in 0..count {
|
||||
outpoints.push(outpoint_cursor.get(start + i).data()?);
|
||||
let op: OutPoint = outpoint_cursor.get(start + i).data()?;
|
||||
if op.is_not_coinbase() {
|
||||
let ot: OutputType = input_output_type_cursor.get(start + i).data()?;
|
||||
let ti: TypeIndex = input_type_index_cursor.get(start + i).data()?;
|
||||
let val: Sats = input_value_cursor.get(start + i).data()?;
|
||||
prevout_input_data.insert(op, (ot, ti, val));
|
||||
}
|
||||
outpoints.push(op);
|
||||
}
|
||||
dtx.outpoints = outpoints;
|
||||
}
|
||||
|
||||
let t_phase1b = t0.elapsed();
|
||||
|
||||
// ── Phase 2: Batch-read prevout data in sorted order ────────────
|
||||
|
||||
// Collect all non-coinbase outpoints, deduplicate, sort by tx_index
|
||||
@@ -184,53 +203,25 @@ impl Query {
|
||||
prevout_keys.sort_unstable();
|
||||
prevout_keys.dedup();
|
||||
|
||||
// Batch-read txid + first_txout_index sorted by prev_tx_index
|
||||
// Batch-read txid sorted by prev_tx_index (only remaining random read)
|
||||
let txid_reader = indexer.vecs.transactions.txid.reader();
|
||||
let first_txout_index_reader = indexer.vecs.transactions.first_txout_index.reader();
|
||||
|
||||
struct PrevoutIntermediate {
|
||||
outpoint: OutPoint,
|
||||
txid: Txid,
|
||||
txout_index: TxOutIndex,
|
||||
}
|
||||
|
||||
let mut intermediates: Vec<PrevoutIntermediate> = Vec::with_capacity(prevout_keys.len());
|
||||
|
||||
for &op in &prevout_keys {
|
||||
let prev_tx_idx = op.tx_index().to_usize();
|
||||
let txid = txid_reader.get(prev_tx_idx);
|
||||
let first_txout = first_txout_index_reader.get(prev_tx_idx);
|
||||
let txout_index = first_txout + op.vout();
|
||||
intermediates.push(PrevoutIntermediate {
|
||||
outpoint: op,
|
||||
txid,
|
||||
txout_index,
|
||||
});
|
||||
}
|
||||
|
||||
// Re-sort by txout_index for sequential output data reads
|
||||
intermediates.sort_unstable_by_key(|i| i.txout_index);
|
||||
|
||||
let value_reader = indexer.vecs.outputs.value.reader();
|
||||
let output_type_reader = indexer.vecs.outputs.output_type.reader();
|
||||
let type_index_reader = indexer.vecs.outputs.type_index.reader();
|
||||
let addr_readers = indexer.vecs.addrs.addr_readers();
|
||||
|
||||
let mut prevout_map: FxHashMap<OutPoint, (Txid, TxOut)> =
|
||||
FxHashMap::with_capacity_and_hasher(intermediates.len(), Default::default());
|
||||
FxHashMap::with_capacity_and_hasher(prevout_keys.len(), Default::default());
|
||||
|
||||
for inter in &intermediates {
|
||||
let txout_idx = usize::from(inter.txout_index);
|
||||
let value: Sats = value_reader.get(txout_idx);
|
||||
let output_type: OutputType = output_type_reader.get(txout_idx);
|
||||
let type_index = type_index_reader.get(txout_idx);
|
||||
for &op in &prevout_keys {
|
||||
let txid = txid_reader.get(op.tx_index().to_usize());
|
||||
// output_type, type_index, value pre-read from inputs vecs (sequential)
|
||||
let &(output_type, type_index, value) =
|
||||
prevout_input_data.get(&op).unwrap();
|
||||
let script_pubkey = addr_readers.script_pubkey(output_type, type_index);
|
||||
prevout_map.insert(
|
||||
inter.outpoint,
|
||||
(inter.txid.clone(), TxOut::from((script_pubkey, value))),
|
||||
);
|
||||
prevout_map.insert(op, (txid, TxOut::from((script_pubkey, value))));
|
||||
}
|
||||
|
||||
let t_phase2 = t0.elapsed();
|
||||
|
||||
// ── Phase 3: Assemble Transaction objects ───────────────────────
|
||||
|
||||
let mut txs: Vec<Option<Transaction>> = (0..len).map(|_| None).collect();
|
||||
@@ -320,6 +311,22 @@ impl Query {
|
||||
txs[dtx.pos] = Some(transaction);
|
||||
}
|
||||
|
||||
let t_phase3 = t0.elapsed();
|
||||
|
||||
if t_phase3.as_millis() > 50 {
|
||||
eprintln!(
|
||||
"[perf:txs] n={} vin={} prevouts={} | 1a={:.1?} 1b={:.1?} | 2={:.1?} | 3={:.1?} | total={:.1?}",
|
||||
len,
|
||||
total_inputs,
|
||||
prevout_keys.len(),
|
||||
t_phase1a,
|
||||
t_phase1b - t_phase1a,
|
||||
t_phase2 - t_phase1b,
|
||||
t_phase3 - t_phase2,
|
||||
t_phase3,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(txs.into_iter().map(Option::unwrap).collect())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use bitcoin::hex::{DisplayHex, FromHex};
|
||||
use brk_error::{Error, OptionData, Result};
|
||||
use brk_types::{
|
||||
BlockHash, Height, MerkleProof, Timestamp, Transaction, TxInIndex, TxIndex, TxOutIndex,
|
||||
Height, MerkleProof, Transaction, TxInIndex, TxIndex, TxOutIndex,
|
||||
TxOutspend, TxStatus, Txid, TxidPrefix, Vin, Vout,
|
||||
};
|
||||
use vecdb::{ReadableVec, VecIndex};
|
||||
@@ -59,13 +59,8 @@ impl Query {
|
||||
.tx_heights
|
||||
.get_shared(tx_index)
|
||||
.data()?;
|
||||
let block_hash = indexer
|
||||
.vecs
|
||||
.blocks
|
||||
.blockhash
|
||||
.reader()
|
||||
.get(height.to_usize());
|
||||
let block_time = indexer.vecs.blocks.timestamp.collect_one(height).data()?;
|
||||
let block_hash = indexer.vecs.blocks.cached_blockhash.collect_one(height).data()?;
|
||||
let block_time = indexer.vecs.blocks.cached_timestamp.collect_one(height).data()?;
|
||||
|
||||
Ok(TxStatus {
|
||||
confirmed: true,
|
||||
@@ -144,14 +139,10 @@ impl Query {
|
||||
let indexer = self.indexer();
|
||||
let txin_index_reader = self.computer().outputs.spent.txin_index.reader();
|
||||
let txid_reader = indexer.vecs.transactions.txid.reader();
|
||||
let blockhash_reader = indexer.vecs.blocks.blockhash.reader();
|
||||
|
||||
let tx_heights = &self.computer().indexes.tx_heights;
|
||||
let mut input_tx_cursor = indexer.vecs.inputs.tx_index.cursor();
|
||||
let mut first_txin_cursor = indexer.vecs.transactions.first_txin_index.cursor();
|
||||
let mut block_ts_cursor = indexer.vecs.blocks.timestamp.cursor();
|
||||
|
||||
let mut cached_block: Option<(Height, BlockHash, Timestamp)> = None;
|
||||
|
||||
let mut outspends = Vec::with_capacity(output_count);
|
||||
for i in 0..output_count {
|
||||
@@ -168,16 +159,18 @@ impl Query {
|
||||
let spending_txid = txid_reader.get(spending_tx_index.to_usize());
|
||||
let spending_height = tx_heights.get_shared(spending_tx_index).data()?;
|
||||
|
||||
let (block_hash, block_time) = if let Some((h, ref bh, bt)) = cached_block
|
||||
&& h == spending_height
|
||||
{
|
||||
(bh.clone(), bt)
|
||||
} else {
|
||||
let bh = blockhash_reader.get(spending_height.to_usize());
|
||||
let bt = block_ts_cursor.get(spending_height.to_usize()).data()?;
|
||||
cached_block = Some((spending_height, bh.clone(), bt));
|
||||
(bh, bt)
|
||||
};
|
||||
let block_hash = indexer
|
||||
.vecs
|
||||
.blocks
|
||||
.cached_blockhash
|
||||
.collect_one(spending_height)
|
||||
.data()?;
|
||||
let block_time = indexer
|
||||
.vecs
|
||||
.blocks
|
||||
.cached_timestamp
|
||||
.collect_one(spending_height)
|
||||
.data()?;
|
||||
|
||||
outspends.push(TxOutspend {
|
||||
spent: true,
|
||||
|
||||
@@ -189,8 +189,26 @@ impl AppState {
|
||||
F: FnOnce(&brk_query::Query) -> brk_error::Result<T> + Send + 'static,
|
||||
{
|
||||
self.cached(headers, strategy, uri, "application/json", move |q, enc| {
|
||||
let t0 = std::time::Instant::now();
|
||||
let value = f(q)?;
|
||||
Ok(enc.compress(Bytes::from(serde_json::to_vec(&value).unwrap())))
|
||||
let t_query = t0.elapsed();
|
||||
let json = serde_json::to_vec(&value).unwrap();
|
||||
let t_json = t0.elapsed();
|
||||
let json_len = json.len();
|
||||
let compressed = enc.compress(Bytes::from(json));
|
||||
let t_total = t0.elapsed();
|
||||
if t_total.as_millis() > 100 {
|
||||
eprintln!(
|
||||
"[perf] query={:.1?} json={:.1?}({:.1}MB) compress={:.1?}({}) total={:.1?}",
|
||||
t_query,
|
||||
t_json - t_query,
|
||||
json_len as f64 / 1_048_576.0,
|
||||
t_total - t_json,
|
||||
enc.as_str(),
|
||||
t_total,
|
||||
);
|
||||
}
|
||||
Ok(compressed)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user