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]>

debug: error log

Signed-off-by: Julius Härtl <[email protected]>
  • Loading branch information
juliusknorr committed Jan 12, 2024
1 parent 3e60092 commit c17c42a
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
// 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++;
$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\*\w+)/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 c17c42a

Please sign in to comment.