Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -4,70 +4,88 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Database;
|
||||
|
||||
use App\Framework\Database\Config\ReadWriteConfig;
|
||||
use App\Framework\Database\Exception\DatabaseException;
|
||||
use App\Framework\Database\ReadWrite\MasterSlaveRouter;
|
||||
use App\Framework\Database\ReadWrite\ReplicationLagDetector;
|
||||
use App\Framework\DateTime\Clock;
|
||||
|
||||
/**
|
||||
* Class ReadWriteConnection provides a mechanism to handle separate writing and reading database connections.
|
||||
* It ensures that write-heavy operations are directed to a designated write connection,
|
||||
* while read operations are distributed among a pool of read connections.
|
||||
*
|
||||
* Enhanced with load balancing, health checks, and lag detection.
|
||||
*/
|
||||
final class ReadWriteConnection implements ConnectionInterface
|
||||
{
|
||||
private ConnectionInterface $writeConnection;
|
||||
|
||||
private array $readConnections;
|
||||
private int $currentReadIndex = 0;
|
||||
|
||||
private bool $forceWrite = false;
|
||||
|
||||
public function __construct(ConnectionInterface $writeConnection, array $readConnections)
|
||||
{
|
||||
private ?MasterSlaveRouter $router = null;
|
||||
|
||||
public function __construct(
|
||||
ConnectionInterface $writeConnection,
|
||||
array $readConnections,
|
||||
private readonly ?ReadWriteConfig $config = null,
|
||||
private readonly ?Clock $clock = null,
|
||||
private readonly ?ReplicationLagDetector $lagDetector = null
|
||||
) {
|
||||
$this->writeConnection = $writeConnection;
|
||||
$this->readConnections = $readConnections;
|
||||
|
||||
if (empty($this->readConnections)) {
|
||||
throw new DatabaseException('At least one read connection is required');
|
||||
}
|
||||
|
||||
// Initialize advanced router if dependencies are provided
|
||||
if ($this->config && $this->clock && $this->lagDetector) {
|
||||
$this->router = new MasterSlaveRouter(
|
||||
$writeConnection,
|
||||
$readConnections,
|
||||
$this->config,
|
||||
$this->clock,
|
||||
$this->lagDetector
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function execute(string $sql, array $parameters = []): int
|
||||
{
|
||||
$this->forceWrite = true;
|
||||
|
||||
return $this->writeConnection->execute($sql, $parameters);
|
||||
}
|
||||
|
||||
public function query(string $sql, array $parameters = []): ResultInterface
|
||||
{
|
||||
if ($this->shouldUseWriteConnection($sql)) {
|
||||
return $this->writeConnection->query($sql, $parameters);
|
||||
}
|
||||
$connection = $this->getConnection($sql);
|
||||
|
||||
return $this->getReadConnection()->query($sql, $parameters);
|
||||
return $connection->query($sql, $parameters);
|
||||
}
|
||||
|
||||
public function queryOne(string $sql, array $parameters = []): ?array
|
||||
{
|
||||
if ($this->shouldUseWriteConnection($sql)) {
|
||||
return $this->writeConnection->queryOne($sql, $parameters);
|
||||
}
|
||||
$connection = $this->getConnection($sql);
|
||||
|
||||
return $this->getReadConnection()->queryOne($sql, $parameters);
|
||||
return $connection->queryOne($sql, $parameters);
|
||||
}
|
||||
|
||||
public function queryColumn(string $sql, array $parameters = []): array
|
||||
{
|
||||
if ($this->shouldUseWriteConnection($sql)) {
|
||||
return $this->writeConnection->queryColumn($sql, $parameters);
|
||||
}
|
||||
$connection = $this->getConnection($sql);
|
||||
|
||||
return $this->getReadConnection()->queryColumn($sql, $parameters);
|
||||
return $connection->queryColumn($sql, $parameters);
|
||||
}
|
||||
|
||||
public function queryScalar(string $sql, array $parameters = []): mixed
|
||||
{
|
||||
if ($this->shouldUseWriteConnection($sql)) {
|
||||
return $this->writeConnection->queryScalar($sql, $parameters);
|
||||
}
|
||||
$connection = $this->getConnection($sql);
|
||||
|
||||
return $this->getReadConnection()->queryScalar($sql, $parameters);
|
||||
return $connection->queryScalar($sql, $parameters);
|
||||
}
|
||||
|
||||
public function beginTransaction(): void
|
||||
@@ -111,6 +129,7 @@ final class ReadWriteConnection implements ConnectionInterface
|
||||
public function resetConnectionMode(): void
|
||||
{
|
||||
$this->forceWrite = false;
|
||||
$this->router?->resetStickyConnection();
|
||||
}
|
||||
|
||||
private function shouldUseWriteConnection(string $sql): bool
|
||||
@@ -122,15 +141,78 @@ final class ReadWriteConnection implements ConnectionInterface
|
||||
$sql = trim(strtoupper($sql));
|
||||
$writeOperations = ['INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP', 'TRUNCATE'];
|
||||
|
||||
return array_any($writeOperations, fn($operation) => str_starts_with($sql, $operation));
|
||||
return array_any($writeOperations, fn ($operation) => str_starts_with($sql, $operation));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get appropriate connection for SQL query
|
||||
*/
|
||||
private function getConnection(string $sql): ConnectionInterface
|
||||
{
|
||||
if ($this->router) {
|
||||
return $this->router->route($sql, $this->forceWrite);
|
||||
}
|
||||
|
||||
// Fallback to simple routing
|
||||
if ($this->shouldUseWriteConnection($sql)) {
|
||||
return $this->writeConnection;
|
||||
}
|
||||
|
||||
return $this->getReadConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple round-robin read connection selection (fallback)
|
||||
*/
|
||||
private function getReadConnection(): ConnectionInterface
|
||||
{
|
||||
$connection = $this->readConnections[$this->currentReadIndex];
|
||||
$this->currentReadIndex = ($this->currentReadIndex + 1) % count($this->readConnections);
|
||||
static $currentReadIndex = 0;
|
||||
|
||||
$connection = $this->readConnections[$currentReadIndex];
|
||||
$currentReadIndex = ($currentReadIndex + 1) % count($this->readConnections);
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get read/write statistics
|
||||
*/
|
||||
public function getStatistics(): array
|
||||
{
|
||||
if ($this->router) {
|
||||
return $this->router->getStatistics();
|
||||
}
|
||||
|
||||
return [
|
||||
'router' => 'simple',
|
||||
'master_connection' => 'active',
|
||||
'replica_count' => count($this->readConnections),
|
||||
'load_balancing_strategy' => 'round_robin',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get write connection (for direct access if needed)
|
||||
*/
|
||||
public function getWriteConnection(): ConnectionInterface
|
||||
{
|
||||
return $this->writeConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get read connections
|
||||
*/
|
||||
public function getReadConnections(): array
|
||||
{
|
||||
return $this->readConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if advanced routing is enabled
|
||||
*/
|
||||
public function hasAdvancedRouting(): bool
|
||||
{
|
||||
return $this->router !== null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user