3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-12 02:05:50 +00:00

HSQLDB debugging & optimization for Transaction.isValidUnconfirmed

This commit is contained in:
catbref 2019-04-25 18:29:01 +01:00
parent 51fd029e22
commit 0296c4bbb1
5 changed files with 77 additions and 30 deletions

View File

@ -163,36 +163,18 @@ public class Account {
}
/**
* Fetch last reference for account, considering unconfirmed transactions.
* Fetch last reference for account, considering unconfirmed transactions only, or return null.
* <p>
* NOTE: a repository savepoint may be used during execution.
*
* @return byte[] reference, or null if no reference or account not found.
* @return byte[] reference, or null if no unconfirmed transactions for this account.
* @throws DataException
*/
public byte[] getUnconfirmedLastReference() throws DataException {
byte[] reference = getUnconfirmedLastReference(null);
if (reference == null)
// No unconfirmed transactions
reference = getLastReference();
return reference;
}
/**
* Fetch last reference for account, considering unconfirmed transactions only, or return defaultReference.
* <p>
* NOTE: a repository savepoint may be used during execution.
*
* @return byte[] reference, or defaultReference if no unconfirmed transactions for this account.
* @throws DataException
*/
public byte[] getUnconfirmedLastReference(byte[] defaultReference) throws DataException {
// Newest unconfirmed transaction takes priority
List<TransactionData> unconfirmedTransactions = Transaction.getUnconfirmedTransactions(repository);
byte[] reference = defaultReference;
byte[] reference = null;
for (TransactionData transactionData : unconfirmedTransactions) {
String address = PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey());

View File

@ -78,7 +78,7 @@ public class AddressesResource {
else {
// Unconfirmed transactions could update lastReference
Account account = new Account(repository, address);
byte[] unconfirmedLastReference = account.getUnconfirmedLastReference(null);
byte[] unconfirmedLastReference = account.getUnconfirmedLastReference();
if (unconfirmedLastReference != null)
accountData.setReference(unconfirmedLastReference);
}
@ -109,9 +109,12 @@ public class AddressesResource {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
byte[] lastReference = null;
try (final Repository repository = RepositoryManager.getRepository()) {
Account account = new Account(repository, address);
lastReference = account.getUnconfirmedLastReference();
if (lastReference == null)
lastReference = account.getLastReference();
} catch (ApiException e) {
throw e;
} catch (DataException e) {

View File

@ -11,8 +11,10 @@ import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.TimeZone;
import org.apache.logging.log4j.LogManager;
@ -29,12 +31,10 @@ import org.qora.repository.Repository;
import org.qora.repository.TransactionRepository;
import org.qora.repository.VotingRepository;
import org.qora.repository.hsqldb.transaction.HSQLDBTransactionRepository;
import org.qora.settings.Settings;
public class HSQLDBRepository implements Repository {
/** Queries that take longer than this (milliseconds) are logged */
private static final long MAX_QUERY_TIME = 1000L;
private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepository.class);
public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
@ -42,11 +42,17 @@ public class HSQLDBRepository implements Repository {
protected Connection connection;
protected Deque<Savepoint> savepoints;
protected boolean debugState = false;
protected Long slowQueryThreshold = null;
protected List<String> queries;
// NB: no visibility modifier so only callable from within same package
HSQLDBRepository(Connection connection) {
this.connection = connection;
this.savepoints = new ArrayDeque<>(3);
this.slowQueryThreshold = Settings.getInstance().getSlowQueryThreshold();
if (this.slowQueryThreshold != null)
this.queries = new ArrayList<String>();
}
@Override
@ -102,6 +108,9 @@ public class HSQLDBRepository implements Repository {
throw new DataException("commit error", e);
} finally {
this.savepoints.clear();
if (this.queries != null)
this.queries.clear();
}
}
@ -113,6 +122,9 @@ public class HSQLDBRepository implements Repository {
throw new DataException("rollback error", e);
} finally {
this.savepoints.clear();
if (this.queries != null)
this.queries.clear();
}
}
@ -121,6 +133,9 @@ public class HSQLDBRepository implements Repository {
try {
Savepoint savepoint = this.connection.setSavepoint();
this.savepoints.push(savepoint);
if (this.queries != null)
this.queries.add("SAVEPOINT");
} catch (SQLException e) {
throw new DataException("savepoint error", e);
}
@ -135,6 +150,9 @@ public class HSQLDBRepository implements Repository {
try {
this.connection.rollback(savepoint);
if (this.queries != null)
this.queries.add("ROLLBACK TO SAVEPOINT");
} catch (SQLException e) {
throw new DataException("savepoint rollback error", e);
}
@ -206,9 +224,17 @@ public class HSQLDBRepository implements Repository {
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects);
long queryTime = System.currentTimeMillis() - beforeQuery;
if (queryTime > MAX_QUERY_TIME)
if (this.slowQueryThreshold != null && queryTime > this.slowQueryThreshold) {
LOGGER.info(String.format("HSQLDB query took %d ms: %s", queryTime, sql));
if (this.queries != null)
for (String query : this.queries)
LOGGER.info(query);
}
if (this.queries != null)
this.queries.add(sql);
return resultSet;
}
@ -316,8 +342,13 @@ public class HSQLDBRepository implements Repository {
* @throws SQLException
*/
public boolean exists(String tableName, String whereClause, Object... objects) throws SQLException {
try (PreparedStatement preparedStatement = this.connection.prepareStatement("SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " LIMIT 1");
String sql = "SELECT TRUE FROM " + tableName + " WHERE " + whereClause + " LIMIT 1";
try (PreparedStatement preparedStatement = this.connection.prepareStatement(sql);
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects)) {
if (this.queries != null)
this.queries.add(sql);
if (resultSet == null)
return false;
@ -334,7 +365,12 @@ public class HSQLDBRepository implements Repository {
* @throws SQLException
*/
public int delete(String tableName, String whereClause, Object... objects) throws SQLException {
try (PreparedStatement preparedStatement = this.connection.prepareStatement("DELETE FROM " + tableName + " WHERE " + whereClause)) {
String sql = "DELETE FROM " + tableName + " WHERE " + whereClause;
try (PreparedStatement preparedStatement = this.connection.prepareStatement(sql)) {
if (this.queries != null)
this.queries.add(sql);
return this.checkedExecuteUpdateCount(preparedStatement, objects);
}
}
@ -346,7 +382,12 @@ public class HSQLDBRepository implements Repository {
* @throws SQLException
*/
public int delete(String tableName) throws SQLException {
try (PreparedStatement preparedStatement = this.connection.prepareStatement("DELETE FROM " + tableName)) {
String sql = "DELETE FROM " + tableName;
try (PreparedStatement preparedStatement = this.connection.prepareStatement(sql)) {
if (this.queries != null)
this.queries.add(sql);
return this.checkedExecuteUpdateCount(preparedStatement);
}
}
@ -376,6 +417,10 @@ public class HSQLDBRepository implements Repository {
public SQLException examineException(SQLException e) throws SQLException {
LOGGER.error("SQL error: " + e.getMessage());
if (this.queries != null)
for (String query : this.queries)
LOGGER.info(query);
// Serialization failure / potential deadlock - so list other sessions
try (ResultSet resultSet = this.checkedExecute(
"SELECT session_id, transaction, transaction_size, waiting_for_this, this_waiting_for, current_statement FROM Information_schema.system_sessions")) {

View File

@ -62,6 +62,12 @@ public class Settings {
private String blockchainConfig = "blockchain.json";
private boolean useBitcoinTestNet = false;
// Repository related
/** Queries that take longer than this are logged. (milliseconds) */
private Long slowQueryThreshold = null;
/** Repository storage path. */
private String repositoryPath = null;
// Constructors
private Settings() {
@ -228,4 +234,12 @@ public class Settings {
return this.useBitcoinTestNet;
}
public Long getSlowQueryThreshold() {
return this.slowQueryThreshold;
}
public String getRepositoryPath() {
return this.repositoryPath;
}
}

View File

@ -515,7 +515,10 @@ public abstract class Transaction {
if (!this.isValidTxGroupId())
return ValidationResult.INVALID_TX_GROUP_ID;
creator.setLastReference(creator.getUnconfirmedLastReference());
byte[] unconfirmedLastReference = creator.getUnconfirmedLastReference();
if (unconfirmedLastReference != null)
creator.setLastReference(unconfirmedLastReference);
ValidationResult result = this.isValid();
// Reject if unconfirmed pile already has X transactions from same creator