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:
@@ -15,11 +15,12 @@ final readonly class CacheMiddleware implements QueryMiddleware
|
||||
private int $defaultTtlSeconds = 300, // 5 Minuten
|
||||
private bool $enabled = true,
|
||||
private array $cacheableOperations = ['query', 'queryOne', 'queryColumn', 'queryScalar']
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(QueryContext $context, callable $next): mixed
|
||||
{
|
||||
if (!$this->enabled || !$this->isCacheable($context)) {
|
||||
if (! $this->enabled || ! $this->isCacheable($context)) {
|
||||
return $next($context);
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ final readonly class CacheMiddleware implements QueryMiddleware
|
||||
private function isCacheable(QueryContext $context): bool
|
||||
{
|
||||
// Nur bestimmte Operationen sind cacheable
|
||||
if (!in_array($context->operation, $this->cacheableOperations, true)) {
|
||||
if (! in_array($context->operation, $this->cacheableOperations, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ final readonly class CacheMiddleware implements QueryMiddleware
|
||||
$sql = strtoupper(trim($context->sql));
|
||||
|
||||
// SELECT-Statements sind normalerweise cacheable
|
||||
if (!str_starts_with($sql, 'SELECT')) {
|
||||
if (! str_starts_with($sql, 'SELECT')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ final readonly class HealthCheckMiddleware implements QueryMiddleware
|
||||
private int $healthCheckInterval = 30, // Sekunden
|
||||
private bool $enabled = true
|
||||
) {
|
||||
$this->healthChecker = new ConnectionHealthChecker();
|
||||
$this->healthChecker = ConnectionHealthChecker::quick();
|
||||
}
|
||||
|
||||
public function process(QueryContext $context, callable $next): mixed
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
if (! $this->enabled) {
|
||||
return $next($context);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ final readonly class HealthCheckMiddleware implements QueryMiddleware
|
||||
{
|
||||
$result = $this->healthChecker->checkHealth($context->connection);
|
||||
|
||||
if (!$result->isHealthy) {
|
||||
if (! $result->isHealthy) {
|
||||
throw new DatabaseException(
|
||||
"Health check failed: {$result->message}",
|
||||
0,
|
||||
|
||||
@@ -12,6 +12,7 @@ final class MiddlewarePipeline
|
||||
public function add(QueryMiddleware $middleware): self
|
||||
{
|
||||
$this->middleware[] = $middleware;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -20,6 +21,7 @@ final class MiddlewarePipeline
|
||||
foreach ($middleware as $m) {
|
||||
$this->add($m);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -27,14 +29,14 @@ final class MiddlewarePipeline
|
||||
{
|
||||
// Sortiere Middleware nach Priorität (höchste zuerst)
|
||||
$sortedMiddleware = $this->middleware;
|
||||
usort($sortedMiddleware, fn($a, $b) => $b->getPriority() <=> $a->getPriority());
|
||||
usort($sortedMiddleware, fn ($a, $b) => $b->getPriority() <=> $a->getPriority());
|
||||
|
||||
// Baue die Pipeline von hinten nach vorne auf
|
||||
$pipeline = $finalHandler;
|
||||
|
||||
for ($i = count($sortedMiddleware) - 1; $i >= 0; $i--) {
|
||||
$middleware = $sortedMiddleware[$i];
|
||||
$pipeline = fn(QueryContext $ctx) => $middleware->process($ctx, $pipeline);
|
||||
$pipeline = fn (QueryContext $ctx) => $middleware->process($ctx, $pipeline);
|
||||
}
|
||||
|
||||
return $pipeline($context);
|
||||
@@ -52,6 +54,7 @@ final class MiddlewarePipeline
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -62,6 +65,7 @@ final class MiddlewarePipeline
|
||||
return $middleware;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@ final class QueryContext
|
||||
public readonly array $parameters,
|
||||
public readonly ConnectionInterface $connection,
|
||||
public array $metadata = []
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
public function withMetadata(string $key, mixed $value): self
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->metadata[$key] = $value;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Database\Middleware;
|
||||
|
||||
use App\Framework\Database\ConnectionInterface;
|
||||
use App\Framework\Database\ResultInterface;
|
||||
|
||||
interface QueryMiddleware
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -4,14 +4,17 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Database\Middleware;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\Database\Exception\DatabaseException;
|
||||
use App\Framework\Database\HealthCheck\ConnectionHealthChecker;
|
||||
use App\Framework\DateTime\Timer;
|
||||
|
||||
final readonly class RetryMiddleware implements QueryMiddleware
|
||||
{
|
||||
private ConnectionHealthChecker $healthChecker;
|
||||
|
||||
public function __construct(
|
||||
private Timer $timer,
|
||||
private int $maxRetries = 3,
|
||||
private int $retryDelayMs = 100,
|
||||
private array $retryableExceptions = [
|
||||
@@ -19,7 +22,7 @@ final readonly class RetryMiddleware implements QueryMiddleware
|
||||
DatabaseException::class,
|
||||
]
|
||||
) {
|
||||
$this->healthChecker = new ConnectionHealthChecker();
|
||||
$this->healthChecker = new ConnectionHealthChecker($timer);
|
||||
}
|
||||
|
||||
public function process(QueryContext $context, callable $next): mixed
|
||||
@@ -49,7 +52,7 @@ final readonly class RetryMiddleware implements QueryMiddleware
|
||||
$lastException = $e;
|
||||
|
||||
// Prüfe ob Exception retry-bar ist
|
||||
if (!$this->isRetryableException($e)) {
|
||||
if (! $this->isRetryableException($e)) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -61,14 +64,14 @@ final readonly class RetryMiddleware implements QueryMiddleware
|
||||
$attempt++;
|
||||
|
||||
// Health Check vor Retry
|
||||
if (!$this->healthChecker->checkConnectionAlive($context->connection)) {
|
||||
if (! $this->healthChecker->checkConnectionAlive($context->connection)) {
|
||||
// Für LazyGhost: Connection ist automatisch lazy und wird bei nächstem Zugriff neu initialisiert
|
||||
// Keine explizite Aktion nötig - LazyGhost handled das automatisch
|
||||
}
|
||||
|
||||
// Exponential Backoff
|
||||
$delay = $this->retryDelayMs * (2 ** ($attempt - 1));
|
||||
usleep($delay * 1000); // usleep benötigt Mikrosekunden
|
||||
$this->timer->sleep(Duration::fromMilliseconds($delay));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,9 +90,11 @@ final readonly class RetryMiddleware implements QueryMiddleware
|
||||
if ($exception instanceof \PDOException) {
|
||||
return $this->isPdoExceptionRetryable($exception);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -104,6 +109,7 @@ final readonly class RetryMiddleware implements QueryMiddleware
|
||||
];
|
||||
|
||||
$sqlState = $exception->getCode();
|
||||
return !in_array($sqlState, $nonRetryableCodes, true);
|
||||
|
||||
return ! in_array($sqlState, $nonRetryableCodes, true);
|
||||
}
|
||||
}
|
||||
|
||||
93
src/Framework/Database/Middleware/UnifiedRetryMiddleware.php
Normal file
93
src/Framework/Database/Middleware/UnifiedRetryMiddleware.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Database\Middleware;
|
||||
|
||||
use App\Framework\Core\Events\EventDispatcherInterface;
|
||||
use App\Framework\Database\Exception\DatabaseException;
|
||||
use App\Framework\Database\HealthCheck\ConnectionHealthChecker;
|
||||
use App\Framework\DateTime\Clock;
|
||||
use App\Framework\Retry\RetryManager;
|
||||
use App\Framework\Retry\Strategies\ExponentialBackoffStrategy;
|
||||
|
||||
/**
|
||||
* Database Retry-Middleware basierend auf dem neuen Retry-System
|
||||
*
|
||||
* Ersetzt die alte RetryMiddleware mit einheitlicher Retry-Logik
|
||||
*/
|
||||
final readonly class UnifiedRetryMiddleware implements QueryMiddleware
|
||||
{
|
||||
private RetryManager $retryManager;
|
||||
|
||||
public function __construct(
|
||||
Clock $clock,
|
||||
?EventDispatcherInterface $eventDispatcher = null,
|
||||
private ConnectionHealthChecker $healthChecker = new ConnectionHealthChecker()
|
||||
) {
|
||||
$this->retryManager = RetryManager::create($clock)
|
||||
->withStrategy(ExponentialBackoffStrategy::forDatabase())
|
||||
->withContext(['middleware' => 'database']);
|
||||
|
||||
if ($eventDispatcher !== null) {
|
||||
$this->retryManager = $this->retryManager->withEventDispatcher($eventDispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
public function process(QueryContext $context, callable $next): mixed
|
||||
{
|
||||
// Transaktions-Commits und Rollbacks nicht wiederholen
|
||||
if ($this->isTransactionCommand($context)) {
|
||||
return $next($context);
|
||||
}
|
||||
|
||||
// Health-Check vor Retry
|
||||
if (! $this->healthChecker->isHealthy($context->connection)) {
|
||||
throw new DatabaseException('Database connection is unhealthy');
|
||||
}
|
||||
|
||||
// Retry-Kontext erweitern
|
||||
$retryManager = $this->retryManager->withContext([
|
||||
'query_type' => $this->getQueryType($context),
|
||||
'connection_name' => $context->connection->getName() ?? 'default',
|
||||
]);
|
||||
|
||||
// Retry-Operation ausführen
|
||||
$result = $retryManager->execute(function () use ($context, $next) {
|
||||
return $next($context);
|
||||
});
|
||||
|
||||
return $result->getResult();
|
||||
}
|
||||
|
||||
private function isTransactionCommand(QueryContext $context): bool
|
||||
{
|
||||
$sql = strtoupper(trim($context->sql));
|
||||
|
||||
return str_starts_with($sql, 'COMMIT') ||
|
||||
str_starts_with($sql, 'ROLLBACK') ||
|
||||
str_starts_with($sql, 'START TRANSACTION') ||
|
||||
str_starts_with($sql, 'BEGIN');
|
||||
}
|
||||
|
||||
private function getQueryType(QueryContext $context): string
|
||||
{
|
||||
$sql = strtoupper(trim($context->sql));
|
||||
|
||||
return match (true) {
|
||||
str_starts_with($sql, 'SELECT') => 'select',
|
||||
str_starts_with($sql, 'INSERT') => 'insert',
|
||||
str_starts_with($sql, 'UPDATE') => 'update',
|
||||
str_starts_with($sql, 'DELETE') => 'delete',
|
||||
str_starts_with($sql, 'CREATE') => 'ddl',
|
||||
str_starts_with($sql, 'ALTER') => 'ddl',
|
||||
str_starts_with($sql, 'DROP') => 'ddl',
|
||||
default => 'other'
|
||||
};
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user