3
0
mirror of https://github.com/Qortal/qortal.git synced 2025-02-12 10:15:49 +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> * <p>
* NOTE: a repository savepoint may be used during execution. * 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 * @throws DataException
*/ */
public byte[] getUnconfirmedLastReference() 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 // Newest unconfirmed transaction takes priority
List<TransactionData> unconfirmedTransactions = Transaction.getUnconfirmedTransactions(repository); List<TransactionData> unconfirmedTransactions = Transaction.getUnconfirmedTransactions(repository);
byte[] reference = defaultReference; byte[] reference = null;
for (TransactionData transactionData : unconfirmedTransactions) { for (TransactionData transactionData : unconfirmedTransactions) {
String address = PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey()); String address = PublicKeyAccount.getAddress(transactionData.getCreatorPublicKey());

View File

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

View File

@ -11,8 +11,10 @@ import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Deque; import java.util.Deque;
import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -29,12 +31,10 @@ import org.qora.repository.Repository;
import org.qora.repository.TransactionRepository; import org.qora.repository.TransactionRepository;
import org.qora.repository.VotingRepository; import org.qora.repository.VotingRepository;
import org.qora.repository.hsqldb.transaction.HSQLDBTransactionRepository; import org.qora.repository.hsqldb.transaction.HSQLDBTransactionRepository;
import org.qora.settings.Settings;
public class HSQLDBRepository implements Repository { 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); private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepository.class);
public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
@ -42,11 +42,17 @@ public class HSQLDBRepository implements Repository {
protected Connection connection; protected Connection connection;
protected Deque<Savepoint> savepoints; protected Deque<Savepoint> savepoints;
protected boolean debugState = false; protected boolean debugState = false;
protected Long slowQueryThreshold = null;
protected List<String> queries;
// NB: no visibility modifier so only callable from within same package // NB: no visibility modifier so only callable from within same package
HSQLDBRepository(Connection connection) { HSQLDBRepository(Connection connection) {
this.connection = connection; this.connection = connection;
this.savepoints = new ArrayDeque<>(3); this.savepoints = new ArrayDeque<>(3);
this.slowQueryThreshold = Settings.getInstance().getSlowQueryThreshold();
if (this.slowQueryThreshold != null)
this.queries = new ArrayList<String>();
} }
@Override @Override
@ -102,6 +108,9 @@ public class HSQLDBRepository implements Repository {
throw new DataException("commit error", e); throw new DataException("commit error", e);
} finally { } finally {
this.savepoints.clear(); 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); throw new DataException("rollback error", e);
} finally { } finally {
this.savepoints.clear(); this.savepoints.clear();
if (this.queries != null)
this.queries.clear();
} }
} }
@ -121,6 +133,9 @@ public class HSQLDBRepository implements Repository {
try { try {
Savepoint savepoint = this.connection.setSavepoint(); Savepoint savepoint = this.connection.setSavepoint();
this.savepoints.push(savepoint); this.savepoints.push(savepoint);
if (this.queries != null)
this.queries.add("SAVEPOINT");
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("savepoint error", e); throw new DataException("savepoint error", e);
} }
@ -135,6 +150,9 @@ public class HSQLDBRepository implements Repository {
try { try {
this.connection.rollback(savepoint); this.connection.rollback(savepoint);
if (this.queries != null)
this.queries.add("ROLLBACK TO SAVEPOINT");
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("savepoint rollback error", e); throw new DataException("savepoint rollback error", e);
} }
@ -206,9 +224,17 @@ public class HSQLDBRepository implements Repository {
ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects); ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects);
long queryTime = System.currentTimeMillis() - beforeQuery; 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)); 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; return resultSet;
} }
@ -316,8 +342,13 @@ public class HSQLDBRepository implements Repository {
* @throws SQLException * @throws SQLException
*/ */
public boolean exists(String tableName, String whereClause, Object... objects) 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)) { ResultSet resultSet = this.checkedExecuteResultSet(preparedStatement, objects)) {
if (this.queries != null)
this.queries.add(sql);
if (resultSet == null) if (resultSet == null)
return false; return false;
@ -334,7 +365,12 @@ public class HSQLDBRepository implements Repository {
* @throws SQLException * @throws SQLException
*/ */
public int delete(String tableName, String whereClause, Object... objects) 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); return this.checkedExecuteUpdateCount(preparedStatement, objects);
} }
} }
@ -346,7 +382,12 @@ public class HSQLDBRepository implements Repository {
* @throws SQLException * @throws SQLException
*/ */
public int delete(String tableName) 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); return this.checkedExecuteUpdateCount(preparedStatement);
} }
} }
@ -376,6 +417,10 @@ public class HSQLDBRepository implements Repository {
public SQLException examineException(SQLException e) throws SQLException { public SQLException examineException(SQLException e) throws SQLException {
LOGGER.error("SQL error: " + e.getMessage()); 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 // Serialization failure / potential deadlock - so list other sessions
try (ResultSet resultSet = this.checkedExecute( try (ResultSet resultSet = this.checkedExecute(
"SELECT session_id, transaction, transaction_size, waiting_for_this, this_waiting_for, current_statement FROM Information_schema.system_sessions")) { "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 String blockchainConfig = "blockchain.json";
private boolean useBitcoinTestNet = false; 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 // Constructors
private Settings() { private Settings() {
@ -228,4 +234,12 @@ public class Settings {
return this.useBitcoinTestNet; 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()) if (!this.isValidTxGroupId())
return ValidationResult.INVALID_TX_GROUP_ID; return ValidationResult.INVALID_TX_GROUP_ID;
creator.setLastReference(creator.getUnconfirmedLastReference()); byte[] unconfirmedLastReference = creator.getUnconfirmedLastReference();
if (unconfirmedLastReference != null)
creator.setLastReference(unconfirmedLastReference);
ValidationResult result = this.isValid(); ValidationResult result = this.isValid();
// Reject if unconfirmed pile already has X transactions from same creator // Reject if unconfirmed pile already has X transactions from same creator