Compare commits

...

1 Commits

Author SHA1 Message Date
catbref
e93b262049 WIP: starting to split into two DBs 2020-10-06 16:38:22 +01:00
12 changed files with 99 additions and 56 deletions

View File

@ -6,7 +6,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.controller.Controller;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryFactory;
@ -42,7 +41,7 @@ public class RepositoryMaintenance {
LOGGER.info("Opening repository");
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
// If exception has no cause then repository is in use by some other process.

View File

@ -1,7 +1,6 @@
package org.qortal.controller;
import java.awt.TrayIcon.MessageType;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
@ -108,7 +107,7 @@ public class Controller extends Thread {
private static final long MISBEHAVIOUR_COOLOFF = 10 * 60 * 1000L; // ms
private static final int MAX_BLOCKCHAIN_TIP_AGE = 5; // blocks
private static final Object shutdownLock = new Object();
private static final String repositoryUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "blockchain;create=true;hsqldb.full_log_replay=true";
private static final long ARBITRARY_REQUEST_TIMEOUT = 5 * 1000L; // ms
private static final long NTP_PRE_SYNC_CHECK_PERIOD = 5 * 1000L; // ms
private static final long NTP_POST_SYNC_CHECK_PERIOD = 5 * 60 * 1000L; // ms
@ -224,10 +223,6 @@ public class Controller extends Thread {
// Getters / setters
public static String getRepositoryUrl() {
return String.format(repositoryUrlTemplate, Settings.getInstance().getRepositoryPath());
}
public long getBuildTimestamp() {
return this.buildTimestamp;
}
@ -315,7 +310,7 @@ public class Controller extends Thread {
LOGGER.info("Starting repository");
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(getRepositoryUrl());
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
// If exception has no cause then repository is in use by some other process.

View File

@ -76,9 +76,8 @@ public class HSQLDBRepository implements Repository {
// Constructors
// NB: no visibility modifier so only callable from within same package
/* package */ HSQLDBRepository(Connection connection) throws DataException {
this.connection = connection;
/*package*/ HSQLDBRepository(Connection blockchainConnection, Connection nodeLocalConnection) throws DataException {
this.connection = blockchainConnection;
this.slowQueryThreshold = Settings.getInstance().getSlowQueryThreshold();
if (this.slowQueryThreshold != null)

View File

@ -1,5 +1,6 @@
package org.qortal.repository.hsqldb;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
@ -20,25 +21,44 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
private static final Logger LOGGER = LogManager.getLogger(HSQLDBRepositoryFactory.class);
private static final int POOL_SIZE = 100;
private static final String blockchainFileUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "blockchain;create=true;hsqldb.full_log_replay=true";
private static final String blockchainMemoryUrl = "jdbc:hsqldb:mem:blockchain";
private static final String nodeLocalFileUrlTemplate = "jdbc:hsqldb:file:%s" + File.separator + "node-local;create=true;hsqldb.full_log_replay=true";
private static final String nodeLocalMemoryUrl = "jdbc:hsqldb:mem:node-local";
/** Log getConnection() calls that take longer than this. (ms) */
private static final long SLOW_CONNECTION_THRESHOLD = 1000L;
private String connectionUrl;
private HSQLDBPool connectionPool;
private final String repositoryPath;
private final String blockchainConnectionUrl;
private final String nodeLocalConnectionUrl;
private final HSQLDBPool blockchainConnectionPool;
private final HSQLDBPool nodeLocalConnectionPool;
/**
* Constructs new RepositoryFactory using passed <tt>connectionUrl</tt>.
* Constructs new RepositoryFactory using passed repository path, or null for in-memory.
*
* @param connectionUrl
* @param repositoryPath
* @throws DataException <i>without throwable</i> if repository in use by another process.
* @throws DataException <i>with throwable</i> if repository cannot be opened for some other reason.
*/
public HSQLDBRepositoryFactory(String connectionUrl) throws DataException {
public HSQLDBRepositoryFactory(String repositoryPath) throws DataException {
this.repositoryPath = repositoryPath;
// one-time initialization goes in here
this.connectionUrl = connectionUrl;
if (repositoryPath != null) {
this.blockchainConnectionUrl = String.format(blockchainFileUrlTemplate, repositoryPath);
this.nodeLocalConnectionUrl = String.format(nodeLocalFileUrlTemplate, repositoryPath);
} else {
this.blockchainConnectionUrl = blockchainMemoryUrl;
this.nodeLocalConnectionUrl = nodeLocalMemoryUrl;
}
// Check no-one else is accessing database
try (Connection connection = DriverManager.getConnection(this.connectionUrl)) {
try (Connection connection = DriverManager.getConnection(this.blockchainConnectionUrl)) {
// We only need to check we can obtain connection. It will be auto-closed.
} catch (SQLException e) {
Throwable cause = e.getCause();
@ -53,19 +73,27 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
throw new DataException("Unable to read repository: " + e.getMessage(), e);
// Attempt recovery?
HSQLDBRepository.attemptRecovery(connectionUrl);
HSQLDBRepository.attemptRecovery(repositoryPath);
}
this.connectionPool = new HSQLDBPool(POOL_SIZE);
this.connectionPool.setUrl(this.connectionUrl);
this.blockchainConnectionPool = new HSQLDBPool(POOL_SIZE);
this.blockchainConnectionPool.setUrl(this.blockchainConnectionUrl);
Properties properties = new Properties();
properties.setProperty("close_result", "true"); // Auto-close old ResultSet if Statement creates new ResultSet
this.connectionPool.setProperties(properties);
Properties blockchainProperties = new Properties();
blockchainProperties.setProperty("close_result", "true"); // Auto-close old ResultSet if Statement creates new ResultSet
this.blockchainConnectionPool.setProperties(blockchainProperties);
this.nodeLocalConnectionPool = new HSQLDBPool(POOL_SIZE);
this.nodeLocalConnectionPool.setUrl(this.nodeLocalConnectionUrl);
Properties nodeLocalProperties = new Properties();
nodeLocalProperties.setProperty("close_result", "true"); // Auto-close old ResultSet if Statement creates new ResultSet
this.nodeLocalConnectionPool.setProperties(nodeLocalProperties);
// Perform DB updates?
try (final Connection connection = this.connectionPool.getConnection()) {
HSQLDBDatabaseUpdates.updateDatabase(connection);
try (final Connection blockchainConnection = this.blockchainConnectionPool.getConnection();
final Connection nodeLocalConnection = this.nodeLocalConnectionPool.getConnection()) {
HSQLDBDatabaseUpdates.updateDatabase(blockchainConnection);
} catch (SQLException e) {
throw new DataException("Repository initialization error", e);
}
@ -73,13 +101,13 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
@Override
public RepositoryFactory reopen() throws DataException {
return new HSQLDBRepositoryFactory(this.connectionUrl);
return new HSQLDBRepositoryFactory(this.repositoryPath);
}
@Override
public Repository getRepository() throws DataException {
try {
return new HSQLDBRepository(this.getConnection());
return new HSQLDBRepository(this.getBlockchainConnection(), this.getNodeLocalConnection());
} catch (SQLException e) {
throw new DataException("Repository instantiation error", e);
}
@ -88,27 +116,54 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
@Override
public Repository tryRepository() throws DataException {
try {
return new HSQLDBRepository(this.tryConnection());
Connection blockchainConnection = this.tryBlockchainConnection();
if (blockchainConnection == null)
return null;
Connection nodeLocalConnection = this.tryNodeLocalConnection();
if (nodeLocalConnection == null)
return null;
return new HSQLDBRepository(blockchainConnection, nodeLocalConnection);
} catch (SQLException e) {
throw new DataException("Repository instantiation error", e);
}
}
private Connection getConnection() throws SQLException {
private Connection getBlockchainConnection() throws SQLException {
return getConnection(this.blockchainConnectionPool, "blockchain");
}
private Connection tryBlockchainConnection() throws SQLException {
return tryConnection(this.blockchainConnectionPool);
}
private Connection getNodeLocalConnection() throws SQLException {
return getConnection(this.nodeLocalConnectionPool, "node-local");
}
private Connection tryNodeLocalConnection() throws SQLException {
return tryConnection(this.nodeLocalConnectionPool);
}
private Connection getConnection(HSQLDBPool pool, String poolName) throws SQLException {
final long before = System.currentTimeMillis();
Connection connection = this.connectionPool.getConnection();
Connection connection = pool.getConnection();
final long delay = System.currentTimeMillis() - before;
if (delay > SLOW_CONNECTION_THRESHOLD)
// This could be an indication of excessive repository use, or insufficient pool size
LOGGER.warn(() -> String.format("Fetching repository connection from pool took %dms (threshold: %dms)", delay, SLOW_CONNECTION_THRESHOLD));
LOGGER.warn(() -> String.format("Fetching repository connection from %s pool took %dms (threshold: %dms)",
poolName,
delay,
SLOW_CONNECTION_THRESHOLD));
setupConnection(connection);
return connection;
}
private Connection tryConnection() throws SQLException {
Connection connection = this.connectionPool.tryConnection();
private Connection tryConnection(HSQLDBPool pool) throws SQLException {
Connection connection = pool.tryConnection();
if (connection == null)
return null;
@ -126,10 +181,16 @@ public class HSQLDBRepositoryFactory implements RepositoryFactory {
public void close() throws DataException {
try {
// Close all existing connections immediately
this.connectionPool.close(0);
this.blockchainConnectionPool.close(0);
this.nodeLocalConnectionPool.close(0);
// Now that all connections are closed, create a dedicated connection to shut down repository
try (Connection connection = DriverManager.getConnection(this.connectionUrl);
try (Connection connection = DriverManager.getConnection(this.blockchainConnectionUrl);
Statement stmt = connection.createStatement()) {
stmt.execute("SHUTDOWN");
}
try (Connection connection = DriverManager.getConnection(this.nodeLocalConnectionUrl);
Statement stmt = connection.createStatement()) {
stmt.execute("SHUTDOWN");
}

View File

@ -7,7 +7,6 @@ import org.bitcoinj.core.Base58;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.block.BlockChain;
import org.qortal.controller.Controller;
import org.qortal.data.account.RewardShareData;
import org.qortal.gui.Gui;
import org.qortal.repository.DataException;
@ -65,7 +64,7 @@ public class DecodeOnlineAccounts {
}
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
System.err.println("Couldn't connect to repository: " + e.getMessage());

View File

@ -3,7 +3,6 @@ import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.qortal.block.BlockChain;
import org.qortal.controller.Controller;
import org.qortal.repository.DataException;
import org.qortal.repository.RepositoryFactory;
import org.qortal.repository.RepositoryManager;
@ -32,7 +31,7 @@ public class orphan {
int targetHeight = Integer.parseInt(args[argIndex]);
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
System.err.println("Couldn't connect to repository: " + e.getMessage());

View File

@ -11,7 +11,6 @@ import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.qortal.controller.Controller;
import org.qortal.crosschain.BTC;
import org.qortal.crosschain.BTCP2SH;
import org.qortal.crypto.Crypto;
@ -85,7 +84,7 @@ public class BuildP2SH {
}
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
throw new RuntimeException("Repository startup issue: " + e.getMessage());

View File

@ -13,7 +13,6 @@ import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.qortal.controller.Controller;
import org.qortal.crosschain.BTC;
import org.qortal.crosschain.BTCP2SH;
import org.qortal.crosschain.BitcoinException;
@ -94,7 +93,7 @@ public class CheckP2SH {
}
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
throw new RuntimeException("Repository startup issue: " + e.getMessage());

View File

@ -5,7 +5,6 @@ import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.asset.Asset;
import org.qortal.controller.Controller;
import org.qortal.crosschain.BTCACCT;
import org.qortal.data.transaction.BaseTransactionData;
import org.qortal.data.transaction.DeployAtTransactionData;
@ -95,7 +94,7 @@ public class DeployAT {
}
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
throw new RuntimeException("Repository startup issue: " + e.getMessage());

View File

@ -16,7 +16,6 @@ import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.qortal.controller.Controller;
import org.qortal.crosschain.BTC;
import org.qortal.crosschain.BTCP2SH;
import org.qortal.crosschain.BitcoinException;
@ -98,7 +97,7 @@ public class Redeem {
}
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
throw new RuntimeException("Repository startup issue: " + e.getMessage());

View File

@ -16,7 +16,6 @@ import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.script.Script.ScriptType;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.qortal.controller.Controller;
import org.qortal.crosschain.BTC;
import org.qortal.crosschain.BTCP2SH;
import org.qortal.crosschain.BitcoinException;
@ -98,7 +97,7 @@ public class Refund {
}
try {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Controller.getRepositoryUrl());
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(Settings.getInstance().getRepositoryPath());
RepositoryManager.setRepositoryFactory(repositoryFactory);
} catch (DataException e) {
throw new RuntimeException("Repository startup issue: " + e.getMessage());

View File

@ -46,10 +46,6 @@ public class Common {
private static final Logger LOGGER = LogManager.getLogger(Common.class);
public static final String testConnectionUrl = "jdbc:hsqldb:mem:testdb";
// For debugging, use this instead to write DB to disk for examination:
// public static final String testConnectionUrl = "jdbc:hsqldb:file:testdb/blockchain;create=true";
public static final String testSettingsFilename = "test-settings-v2.json";
static {
@ -188,7 +184,7 @@ public class Common {
@BeforeClass
public static void setRepository() throws DataException {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(testConnectionUrl);
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(null);
RepositoryManager.setRepositoryFactory(repositoryFactory);
}