mirror of https://github.com/nextcloud/server
enh: Implement PrimaryReadReplicaConnection
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
parent
9c4467461c
commit
79c4986354
|
@ -151,6 +151,14 @@ $CONFIG = [
|
|||
*/
|
||||
'dbpersistent' => '',
|
||||
|
||||
/**
|
||||
* Specify read only replicas to be used by Nextcloud when querying the database
|
||||
*/
|
||||
'dbreplica' => [
|
||||
['user' => 'replica1', 'password', 'host' => '', 'dbname' => ''],
|
||||
['user' => 'replica1', 'password', 'host' => '', 'dbname' => ''],
|
||||
],
|
||||
|
||||
/**
|
||||
* Indicates whether the Nextcloud instance was installed successfully; ``true``
|
||||
* indicates a successful installation, and ``false`` indicates an unsuccessful
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace OC\DB;
|
|||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
use Doctrine\DBAL\Configuration;
|
||||
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
|
@ -55,7 +56,7 @@ use OCP\PreConditionNotMetException;
|
|||
use OCP\Profiler\IProfiler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Connection extends \Doctrine\DBAL\Connection {
|
||||
class Connection extends PrimaryReadReplicaConnection {
|
||||
/** @var string */
|
||||
protected $tablePrefix;
|
||||
|
||||
|
@ -119,7 +120,7 @@ class Connection extends \Doctrine\DBAL\Connection {
|
|||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function connect() {
|
||||
public function connect($connectionName = null) {
|
||||
try {
|
||||
if ($this->_conn) {
|
||||
/** @psalm-suppress InternalMethod */
|
||||
|
@ -302,6 +303,10 @@ class Connection extends \Doctrine\DBAL\Connection {
|
|||
$prefix .= \OC::$server->get(IRequestId::class)->getId() . "\t";
|
||||
}
|
||||
|
||||
// FIXME: Improve to log the actual target db host
|
||||
$isPrimary = $this->connections['primary'] === $this->_conn;
|
||||
$prefix .= ' ' . ($isPrimary === true ? 'primary' : 'replica') . ' ';
|
||||
|
||||
file_put_contents(
|
||||
$this->systemConfig->getValue('query_log_file', ''),
|
||||
$prefix . $sql . "\n",
|
||||
|
@ -603,4 +608,14 @@ class Connection extends \Doctrine\DBAL\Connection {
|
|||
return new Migrator($this, $config, $dispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
protected function performConnect(?string $connectionName = null): bool {
|
||||
$before = $this->isConnectedToPrimary();
|
||||
$result = parent::performConnect($connectionName);
|
||||
$after = $this->isConnectedToPrimary();
|
||||
if (!$before && $after) {
|
||||
$this->logger->debug('Switched to primary database', ['exception' => new \Exception()]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ use Doctrine\Common\EventManager;
|
|||
use Doctrine\DBAL\Configuration;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Event\Listeners\OracleSessionInit;
|
||||
use Doctrine\DBAL\Event\Listeners\SQLSessionInit;
|
||||
use OC\SystemConfig;
|
||||
|
||||
/**
|
||||
|
@ -127,11 +126,8 @@ class ConnectionFactory {
|
|||
$normalizedType = $this->normalizeType($type);
|
||||
$eventManager = new EventManager();
|
||||
$eventManager->addEventSubscriber(new SetTransactionIsolationLevel());
|
||||
$additionalConnectionParams = array_merge($this->createConnectionParams(), $additionalConnectionParams);
|
||||
switch ($normalizedType) {
|
||||
case 'mysql':
|
||||
$eventManager->addEventSubscriber(
|
||||
new SQLSessionInit("SET SESSION AUTOCOMMIT=1"));
|
||||
break;
|
||||
case 'oci':
|
||||
$eventManager->addEventSubscriber(new OracleSessionInit);
|
||||
// the driverOptions are unused in dbal and need to be mapped to the parameters
|
||||
|
@ -159,7 +155,7 @@ class ConnectionFactory {
|
|||
}
|
||||
/** @var Connection $connection */
|
||||
$connection = DriverManager::getConnection(
|
||||
array_merge($this->getDefaultConnectionParams($type), $additionalConnectionParams),
|
||||
$additionalConnectionParams,
|
||||
new Configuration(),
|
||||
$eventManager
|
||||
);
|
||||
|
@ -195,10 +191,10 @@ class ConnectionFactory {
|
|||
public function createConnectionParams(string $configPrefix = '') {
|
||||
$type = $this->config->getValue('dbtype', 'sqlite');
|
||||
|
||||
$connectionParams = [
|
||||
$connectionParams = array_merge($this->getDefaultConnectionParams($type), [
|
||||
'user' => $this->config->getValue($configPrefix . 'dbuser', $this->config->getValue('dbuser', '')),
|
||||
'password' => $this->config->getValue($configPrefix . 'dbpassword', $this->config->getValue('dbpassword', '')),
|
||||
];
|
||||
]);
|
||||
$name = $this->config->getValue($configPrefix . 'dbname', $this->config->getValue('dbname', self::DEFAULT_DBNAME));
|
||||
|
||||
if ($this->normalizeType($type) === 'sqlite3') {
|
||||
|
@ -237,7 +233,11 @@ class ConnectionFactory {
|
|||
$connectionParams['persistent'] = true;
|
||||
}
|
||||
|
||||
return $connectionParams;
|
||||
$replica = $this->config->getValue('dbreplica', []) ?: [$connectionParams];
|
||||
return array_merge($connectionParams, [
|
||||
'primary' => $connectionParams,
|
||||
'replica' => $replica,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,8 +26,10 @@ declare(strict_types=1);
|
|||
namespace OC\DB;
|
||||
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
|
||||
use Doctrine\DBAL\Event\ConnectionEventArgs;
|
||||
use Doctrine\DBAL\Events;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\TransactionIsolationLevel;
|
||||
|
||||
class SetTransactionIsolationLevel implements EventSubscriber {
|
||||
|
@ -36,7 +38,13 @@ class SetTransactionIsolationLevel implements EventSubscriber {
|
|||
* @return void
|
||||
*/
|
||||
public function postConnect(ConnectionEventArgs $args) {
|
||||
$args->getConnection()->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED);
|
||||
$connection = $args->getConnection();
|
||||
if ($connection instanceof PrimaryReadReplicaConnection && $connection->isConnectedToPrimary()) {
|
||||
$connection->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED);
|
||||
if ($connection->getDatabasePlatform() instanceof MySQLPlatform) {
|
||||
$connection->executeStatement('SET SESSION AUTOCOMMIT=1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getSubscribedEvents() {
|
||||
|
|
|
@ -842,8 +842,7 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
if (!$factory->isValidType($type)) {
|
||||
throw new \OC\DatabaseException('Invalid database type');
|
||||
}
|
||||
$connectionParams = $factory->createConnectionParams();
|
||||
$connection = $factory->getConnection($type, $connectionParams);
|
||||
$connection = $factory->getConnection($type, []);
|
||||
return $connection;
|
||||
});
|
||||
/** @deprecated 19.0.0 */
|
||||
|
|
|
@ -141,7 +141,7 @@ abstract class AbstractDatabase {
|
|||
$connectionParams['host'] = $host;
|
||||
}
|
||||
|
||||
$connectionParams = array_merge($connectionParams, $configOverwrite);
|
||||
$connectionParams = array_merge($connectionParams, ['primary' => $connectionParams, 'replica' => [$connectionParams]], $configOverwrite);
|
||||
$cf = new ConnectionFactory($this->config);
|
||||
return $cf->getConnection($this->config->getValue('dbtype', 'sqlite'), $connectionParams);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue