İlk temizlik tamamlandı bir önceki projeden
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Exception;
|
||||
|
||||
class TransactionHelper
|
||||
{
|
||||
/**
|
||||
* Execute a database transaction with exponential backoff retry mechanism
|
||||
*
|
||||
* @param callable $callback The callback function to execute
|
||||
* @param int $maxAttempts Maximum number of retry attempts
|
||||
* @param int $baseDelay Base delay in milliseconds for exponential backoff
|
||||
* @param int $maxDelay Maximum delay in milliseconds
|
||||
* @return mixed Result from the callback
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function retryTransaction(callable $callback, int $maxAttempts = 5, int $baseDelay = 100, int $maxDelay = 5000)
|
||||
{
|
||||
$attempt = 0;
|
||||
|
||||
while ($attempt < $maxAttempts) {
|
||||
try {
|
||||
return DB::transaction($callback);
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
$attempt++;
|
||||
|
||||
// Check if it's a lock wait timeout or deadlock error
|
||||
$isLockError = self::isLockRelatedError($e);
|
||||
|
||||
if (!$isLockError || $attempt >= $maxAttempts) {
|
||||
Log::error("Transaction failed after {$attempt} attempts", [
|
||||
'error' => $e->getMessage(),
|
||||
'sql_state' => $e->errorInfo[0] ?? 'unknown',
|
||||
'error_code' => $e->errorInfo[1] ?? 'unknown'
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Calculate exponential backoff delay with jitter
|
||||
$delay = min(
|
||||
$baseDelay * pow(2, $attempt - 1) + rand(0, 50),
|
||||
$maxDelay
|
||||
);
|
||||
|
||||
Log::warning("Transaction lock error, retrying attempt {$attempt}/{$maxAttempts}", [
|
||||
'error' => $e->getMessage(),
|
||||
'delay_ms' => $delay,
|
||||
'sql_state' => $e->errorInfo[0] ?? 'unknown'
|
||||
]);
|
||||
|
||||
// Wait before retrying (convert ms to microseconds)
|
||||
usleep($delay * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Transaction failed after {$maxAttempts} attempts");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the exception is related to lock/deadlock errors
|
||||
*
|
||||
* @param \Illuminate\Database\QueryException $e
|
||||
* @return bool
|
||||
*/
|
||||
private static function isLockRelatedError(\Illuminate\Database\QueryException $e): bool
|
||||
{
|
||||
$lockErrors = [
|
||||
'40001', // Serialization failure / Deadlock
|
||||
'HY000', // General error (includes lock wait timeout)
|
||||
'1205', // Lock wait timeout exceeded
|
||||
'1213', // Deadlock found when trying to get lock
|
||||
];
|
||||
|
||||
$errorCode = $e->errorInfo[1] ?? null;
|
||||
$sqlState = $e->errorInfo[0] ?? null;
|
||||
$message = strtolower($e->getMessage());
|
||||
|
||||
// Check error codes
|
||||
if (in_array($sqlState, $lockErrors) || in_array($errorCode, $lockErrors)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check error message for lock-related keywords
|
||||
$lockKeywords = ['lock wait timeout', 'deadlock', 'lock timeout'];
|
||||
foreach ($lockKeywords as $keyword) {
|
||||
if (strpos($message, $keyword) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a batch of operations in smaller chunks with transactions
|
||||
*
|
||||
* @param \Illuminate\Support\Collection|array $items Items to process
|
||||
* @param callable $callback Callback to execute for each chunk
|
||||
* @param int $chunkSize Size of each chunk
|
||||
* @param int $delayBetweenChunks Delay in microseconds between chunks
|
||||
* @return array Results from each chunk
|
||||
*/
|
||||
public static function chunkTransaction($items, callable $callback, int $chunkSize = 5, int $delayBetweenChunks = 100000)
|
||||
{
|
||||
$collection = collect($items);
|
||||
$chunks = $collection->chunk($chunkSize);
|
||||
$results = [];
|
||||
|
||||
foreach ($chunks as $index => $chunk) {
|
||||
try {
|
||||
$result = self::retryTransaction(function () use ($callback, $chunk) {
|
||||
return $callback($chunk);
|
||||
});
|
||||
|
||||
$results[] = $result;
|
||||
|
||||
// Add delay between chunks to prevent overwhelming the database
|
||||
if ($index < $chunks->count() - 1) {
|
||||
usleep($delayBetweenChunks);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Log::error("Chunk transaction failed", [
|
||||
'chunk_index' => $index,
|
||||
'chunk_size' => $chunk->count(),
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
// Decide whether to continue or stop
|
||||
// For now, we'll rethrow the exception
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database session timeout values
|
||||
*
|
||||
* @param int $lockWaitTimeout Lock wait timeout in seconds
|
||||
* @param int $waitTimeout General wait timeout in seconds
|
||||
* @param int $interactiveTimeout Interactive timeout in seconds
|
||||
*/
|
||||
public static function setDatabaseTimeouts(int $lockWaitTimeout = 300, int $waitTimeout = 600, int $interactiveTimeout = 600)
|
||||
{
|
||||
try {
|
||||
DB::statement("SET SESSION innodb_lock_wait_timeout = {$lockWaitTimeout}");
|
||||
DB::statement("SET SESSION lock_wait_timeout = {$lockWaitTimeout}");
|
||||
DB::statement("SET SESSION wait_timeout = {$waitTimeout}");
|
||||
DB::statement("SET SESSION interactive_timeout = {$interactiveTimeout}");
|
||||
|
||||
Log::info("Database timeouts set successfully", [
|
||||
'lock_wait_timeout' => $lockWaitTimeout,
|
||||
'wait_timeout' => $waitTimeout,
|
||||
'interactive_timeout' => $interactiveTimeout
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
Log::warning("Failed to set database timeouts", [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user