Skip to content

Commit

Permalink
enh: Implement PrimaryReadReplicaConnection
Browse files Browse the repository at this point in the history
Signed-off-by: Julius Härtl <jus@bitgrid.net>
  • Loading branch information
juliusknorr committed Dec 4, 2023
1 parent 1066f7e commit 65e3016
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 7 deletions.
5 changes: 5 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@
*/
'dbpersistent' => '',

'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
Expand Down
20 changes: 18 additions & 2 deletions lib/private/DB/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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;
Expand All @@ -54,8 +55,9 @@
use OCP\PreConditionNotMetException;
use OCP\Profiler\IProfiler;
use Psr\Log\LoggerInterface;
use SensitiveParameter;

class Connection extends \Doctrine\DBAL\Connection {
class Connection extends PrimaryReadReplicaConnection {
/** @var string */
protected $tablePrefix;

Expand Down Expand Up @@ -119,7 +121,7 @@ public function __construct(
/**
* @throws Exception
*/
public function connect() {
public function connect($connectionName = null) {
try {
if ($this->_conn) {
/** @psalm-suppress InternalMethod */
Expand Down Expand Up @@ -302,6 +304,10 @@ protected function logQueryToFile(string $sql): void {
$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",
Expand Down Expand Up @@ -603,4 +609,14 @@ private function getMigrator() {
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;
}
}
10 changes: 7 additions & 3 deletions lib/private/DB/ConnectionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ public function getConnection($type, $additionalConnectionParams) {
$eventManager->addEventSubscriber(new SetTransactionIsolationLevel());
switch ($normalizedType) {
case 'mysql':
$eventManager->addEventSubscriber(
new SQLSessionInit("SET SESSION AUTOCOMMIT=1"));
// $eventManager->addEventSubscriber(
// new SQLSessionInit("SET SESSION AUTOCOMMIT=1"));
break;
case 'oci':
$eventManager->addEventSubscriber(new OracleSessionInit);
Expand Down Expand Up @@ -237,7 +237,11 @@ public function createConnectionParams(string $configPrefix = '') {
$connectionParams['persistent'] = true;
}

return $connectionParams;
$replica = $this->config->getValue('dbreplica', []) ?: [$connectionParams];
return array_merge($connectionParams, [
'primary' => $connectionParams,
'replica' => $replica,
]);
}

/**
Expand Down
5 changes: 4 additions & 1 deletion lib/private/DB/SetTransactionIsolationLevel.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ class SetTransactionIsolationLevel implements EventSubscriber {
* @return void
*/
public function postConnect(ConnectionEventArgs $args) {
$args->getConnection()->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED);
// Maybe this has an effect on primary
if ($args->getConnection()->isConnectedToPrimary()) {

Check failure on line 40 in lib/private/DB/SetTransactionIsolationLevel.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

UndefinedMethod

lib/private/DB/SetTransactionIsolationLevel.php:40:31: UndefinedMethod: Method Doctrine\DBAL\Connection::isConnectedToPrimary does not exist (see https://psalm.dev/022)

Check failure

Code scanning / Psalm

UndefinedMethod Error

Method Doctrine\DBAL\Connection::isConnectedToPrimary does not exist
$args->getConnection()->setTransactionIsolation(TransactionIsolationLevel::READ_COMMITTED);
}
}

public function getSubscribedEvents() {
Expand Down
2 changes: 1 addition & 1 deletion lib/private/Setup/AbstractDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ protected function connect(array $configOverwrite = []): Connection {
$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);
}
Expand Down

0 comments on commit 65e3016

Please sign in to comment.