mirror of
https://github.com/nextcloud/server.git
synced 2026-04-29 01:50:33 -04:00
feat: First attempt to track dirty tables after writes and switch back to replicas if reads go to other tables
Signed-off-by: Julius Härtl <jus@bitgrid.net> debug: error log Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
parent
3e60092b7d
commit
c17c42a0b2
1 changed files with 27 additions and 0 deletions
|
|
@ -81,6 +81,8 @@ class Connection extends PrimaryReadReplicaConnection {
|
|||
|
||||
protected ?float $transactionActiveSince = null;
|
||||
|
||||
protected $tableDirtyWrites = [];
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the Connection class.
|
||||
*
|
||||
|
|
@ -257,6 +259,18 @@ class Connection extends PrimaryReadReplicaConnection {
|
|||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function executeQuery(string $sql, array $params = [], $types = [], QueryCacheProfile $qcp = null): Result {
|
||||
$tables = $this->getQueriedTables($sql);
|
||||
if (count(array_intersect($this->tableDirtyWrites, $tables)) === 0 && !$this->isTransactionActive()) {
|
||||
// No tables read that could have been written already in the same request and no transaction active
|
||||
// so we can switch back to the replica for reading as long as no writes happen that switch back to the primary
|
||||
// We cannot log here as this would log too early in the server boot process
|
||||
$this->ensureConnectedToReplica();
|
||||
} else {
|
||||
// Read to a table that was previously written to
|
||||
// While this might not necessarily mean that we did a read after write it is an indication for a code path to check
|
||||
$this->logger->debug('dirty table reads: ' . $sql, ['tables' => $this->tableDirtyWrites, 'reads' => $tables, 'exception' => new \Exception()]);
|
||||
}
|
||||
|
||||
$sql = $this->replaceTablePrefix($sql);
|
||||
$sql = $this->adapter->fixupStatement($sql);
|
||||
$this->queriesExecuted++;
|
||||
|
|
@ -264,6 +278,16 @@ class Connection extends PrimaryReadReplicaConnection {
|
|||
return parent::executeQuery($sql, $params, $types, $qcp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the list of tables affected by a given query
|
||||
* used to track dirty tables that received a write with the current request
|
||||
*/
|
||||
private function getQueriedTables(string $sql): array {
|
||||
$re = '/(\*PREFIX\*\w+)/mi';
|
||||
preg_match_all($re, $sql, $matches);
|
||||
return array_map([$this, 'replaceTablePrefix'], $matches[0] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
|
|
@ -290,6 +314,9 @@ class Connection extends PrimaryReadReplicaConnection {
|
|||
* @throws \Doctrine\DBAL\Exception
|
||||
*/
|
||||
public function executeStatement($sql, array $params = [], array $types = []): int {
|
||||
$tables = $this->getQueriedTables($sql);
|
||||
$this->tableDirtyWrites = array_unique(array_merge($this->tableDirtyWrites, $tables));
|
||||
$this->logger->debug('dirty table writes: ' . $sql, ['tables' => $this->tableDirtyWrites]);
|
||||
$sql = $this->replaceTablePrefix($sql);
|
||||
$sql = $this->adapter->fixupStatement($sql);
|
||||
$this->queriesExecuted++;
|
||||
|
|
|
|||
Loading…
Reference in a new issue