Add transient tx map to DaoState to speed up getTx queries

Build a HashMap of all BSQ transactions found, when loading the DaoState
from disc, and store it in a transient field which is always kept in
sync with the associated list of blocks. (The latter is only modified in
a couple of places in DaoStateService, making this straightforward.)

This is to speed up daoStateService.getTx(id), which is called from many
places and appears to be a significant bottleneck. In particular, the
initial load of the results in VoteResultView.doFillCycleList was very
slow (taking nearly a minute on a Core i3 machine) and likely to suffer
a quadratic slowdown (#cycles * #tx's) over time.
This commit is contained in:
Steven Barclay 2019-12-09 23:54:26 +00:00
parent 30f96643b2
commit 0fa21b5f1a
No known key found for this signature in database
GPG Key ID: 9FED6BF1176D500B
3 changed files with 36 additions and 10 deletions

View File

@ -22,7 +22,6 @@ import bisq.core.dao.node.parser.exceptions.BlockHashNotConnectingException;
import bisq.core.dao.node.parser.exceptions.BlockHeightNotConnectingException;
import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.common.app.DevEnv;
@ -31,7 +30,6 @@ import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import java.util.LinkedList;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
@ -106,14 +104,13 @@ public class BlockParser {
// one get resolved.
// Lately there is a patter with 24 iterations observed
long startTs = System.currentTimeMillis();
List<Tx> txList = block.getTxs();
rawBlock.getRawTxs().forEach(rawTx ->
txParser.findTx(rawTx,
genesisTxId,
genesisBlockHeight,
genesisTotalSupply)
.ifPresent(txList::add));
.ifPresent(tx -> daoStateService.onNewTxForLastBlock(block, tx)));
log.info("Parsing {} transactions at block height {} took {} ms", rawBlock.getRawTxs().size(),
blockHeight, System.currentTimeMillis() - startTs);

View File

@ -35,18 +35,21 @@ import bisq.core.dao.state.model.governance.EvaluatedProposal;
import bisq.core.dao.state.model.governance.Issuance;
import bisq.core.dao.state.model.governance.IssuanceType;
import bisq.core.dao.state.model.governance.ParamChange;
import bisq.core.util.coin.BsqFormatter;
import bisq.core.util.ParsingUtils;
import bisq.core.util.coin.BsqFormatter;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
@ -115,6 +118,9 @@ public class DaoStateService implements DaoSetupService {
daoState.setChainHeight(snapshot.getChainHeight());
daoState.getTxMap().clear();
daoState.getTxMap().putAll(snapshot.getTxMap());
daoState.getBlocks().clear();
daoState.getBlocks().addAll(snapshot.getBlocks());
@ -226,7 +232,16 @@ public class DaoStateService implements DaoSetupService {
}
}
// Third we get the onParseBlockComplete called after all rawTxs of blocks have been parsed
// Third we add each successfully parsed BSQ tx to the last block
public void onNewTxForLastBlock(Block block, Tx tx) {
// At least one block must be present else no rawTx would have been recognised as a BSQ tx.
Preconditions.checkArgument(block == getLastBlock().orElseThrow());
block.getTxs().add(tx);
daoState.getTxMap().put(tx.getId(), tx);
}
// Fourth we get the onParseBlockComplete called after all rawTxs of blocks have been parsed
public void onParseBlockComplete(Block block) {
if (parseBlockChainComplete)
log.info("Parse block completed: Block height {}, {} BSQ transactions.", block.getHeight(), block.getTxs().size());
@ -348,16 +363,16 @@ public class DaoStateService implements DaoSetupService {
.flatMap(block -> block.getTxs().stream());
}
public TreeMap<String, Tx> getTxMap() {
return new TreeMap<>(getTxStream().collect(Collectors.toMap(Tx::getId, tx -> tx)));
public Map<String, Tx> getTxMap() {
return daoState.getTxMap();
}
public Set<Tx> getTxs() {
return getTxStream().collect(Collectors.toSet());
return new HashSet<>(getTxMap().values());
}
public Optional<Tx> getTx(String txId) {
return getTxStream().filter(tx -> tx.getId().equals(txId)).findAny();
return Optional.ofNullable(getTxMap().get(txId));
}
public List<Tx> getInvalidTxs() {

View File

@ -19,6 +19,7 @@ package bisq.core.dao.state.model;
import bisq.core.dao.state.model.blockchain.Block;
import bisq.core.dao.state.model.blockchain.SpentInfo;
import bisq.core.dao.state.model.blockchain.Tx;
import bisq.core.dao.state.model.blockchain.TxOutput;
import bisq.core.dao.state.model.blockchain.TxOutputKey;
import bisq.core.dao.state.model.governance.Cycle;
@ -28,16 +29,19 @@ import bisq.core.dao.state.model.governance.Issuance;
import bisq.core.dao.state.model.governance.ParamChange;
import bisq.common.proto.persistable.PersistablePayload;
import bisq.common.util.JsonExclude;
import com.google.protobuf.Message;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.Getter;
@ -98,6 +102,11 @@ public class DaoState implements PersistablePayload {
@Getter
private final List<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsList;
// Transient data used only as an index - must be kept in sync with the block list
@Getter
@JsonExclude
private transient final Map<String, Tx> txMap; // key is txId
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -145,6 +154,10 @@ public class DaoState implements PersistablePayload {
this.paramChangeList = paramChangeList;
this.evaluatedProposalList = evaluatedProposalList;
this.decryptedBallotsWithMeritsList = decryptedBallotsWithMeritsList;
txMap = blocks.stream()
.flatMap(block -> block.getTxs().stream())
.collect(Collectors.toMap(Tx::getId, Function.identity(), (x, y) -> y, HashMap::new));
}
@Override
@ -237,6 +250,7 @@ public class DaoState implements PersistablePayload {
",\n paramChangeList=" + paramChangeList +
",\n evaluatedProposalList=" + evaluatedProposalList +
",\n decryptedBallotsWithMeritsList=" + decryptedBallotsWithMeritsList +
",\n txMap=" + txMap +
"\n}";
}
}