Skip to content

Commit

Permalink
feat: First attempt to track dirty tables after writes and switch bac…
Browse files Browse the repository at this point in the history
…k to replicas if reads go to other tables

Signed-off-by: Julius Härtl <[email protected]>
  • Loading branch information
juliusknorr committed Dec 18, 2023
1 parent 9a9b437 commit 08c3a4d
Showing 1 changed file with 27 additions and 0 deletions.
27 changes: 27 additions & 0 deletions lib/private/DB/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class Connection extends PrimaryReadReplicaConnection {

protected ?float $transactionActiveSince = null;

protected $tableDirtyWrites = [];

/**
* Initializes a new instance of the Connection class.
*
Expand Down Expand Up @@ -257,13 +259,35 @@ public function prepare($sql, $limit = null, $offset = null): Statement {
* @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
$this->ensureConnectedToReplica();
$this->logger->debug('no dirty table reads: ' . $sql, ['tables' => $this->tableDirtyWrites, 'reads' => $tables]);
} 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++;
$this->logQueryToFile($sql);
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\*[A-z0-9_-]+)/mi';
preg_match_all($re, $sql, $matches);
return array_map([$this, 'replaceTablePrefix'], $matches[0] ?? []);
}

/**
* @throws Exception
*/
Expand All @@ -290,6 +314,9 @@ public function executeUpdate(string $sql, array $params = [], array $types = []
* @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++;
Expand Down

0 comments on commit 08c3a4d

Please sign in to comment.