| 1 | <?php |
||||
| 2 | |||||
| 3 | namespace Padosoft\SuperCacheInvalidate\Helpers; |
||||
| 4 | |||||
| 5 | use Illuminate\Support\Facades\DB; |
||||
| 6 | use Illuminate\Support\Facades\Log; |
||||
| 7 | use Illuminate\Support\Facades\Redis; |
||||
| 8 | use Illuminate\Support\Carbon; |
||||
| 9 | |||||
| 10 | class SuperCacheInvalidationHelper |
||||
| 11 | { |
||||
| 12 | /** |
||||
| 13 | * Insert a cache invalidation event into the database. |
||||
| 14 | * |
||||
| 15 | * @param string $type 'key' or 'tag' |
||||
| 16 | * @param string $identifier The cache key or tag to invalidate |
||||
| 17 | * @param string|null $connection_name The Redis Connection name (optional, 'default') |
||||
| 18 | * @param string|null $reason Reason for invalidation (optional) |
||||
| 19 | * @param int|null $totalShards Total number of shards (from config if null) |
||||
| 20 | * @param int|null $priority Priority of the event |
||||
| 21 | * @param int|null $processed Processed = 0 o 1 |
||||
| 22 | * @param Carbon|null $event_time Orario vero in cui si è richiesta l'invalidazione |
||||
| 23 | */ |
||||
| 24 | public function insertInvalidationEvent( |
||||
| 25 | string $type, |
||||
| 26 | string $identifier, |
||||
| 27 | ?string $connection_name = null, |
||||
| 28 | ?string $reason = null, |
||||
| 29 | ?int $totalShards = 0, |
||||
| 30 | ?int $priority = 0, |
||||
| 31 | ?int $processed = 0, |
||||
| 32 | ?Carbon $event_time = null, |
||||
| 33 | /* ?array $associatedIdentifiers = [], */ |
||||
| 34 | ?int $shard = -1, |
||||
| 35 | ): void { |
||||
| 36 | if ($shard < 0) { |
||||
| 37 | $shard = crc32($identifier) % ($totalShards > 0 ? $totalShards : config('super_cache_invalidate.total_shards', 10)); |
||||
| 38 | } |
||||
| 39 | $redisConnectionName = $connection_name ?? config('super_cache_invalidate.default_connection_name'); |
||||
| 40 | $data = [ |
||||
| 41 | 'type' => $type, |
||||
| 42 | 'identifier' => $identifier, |
||||
| 43 | 'connection_name' => $redisConnectionName, |
||||
| 44 | 'reason' => $reason, |
||||
| 45 | 'priority' => $priority, |
||||
| 46 | 'event_time' => $event_time ?? now(), |
||||
| 47 | 'processed' => $processed, // ATTENZIONE, poichè abbiamo solo 2 priorità, nel caso di priorità 1 verrà passato 1 perchè l'invalidazione la fa il progetto |
||||
| 48 | 'shard' => $shard, |
||||
| 49 | ]; |
||||
| 50 | |||||
| 51 | $maxAttempts = 5; |
||||
| 52 | $attempts = 0; |
||||
| 53 | $insertOk = false; |
||||
| 54 | |||||
| 55 | while ($attempts < $maxAttempts && !$insertOk) { |
||||
| 56 | // DB::beginTransaction(); |
||||
| 57 | |||||
| 58 | try { |
||||
| 59 | // Cerca di bloccare il record per l'inserimento |
||||
| 60 | switch ($processed) { |
||||
| 61 | case 0: |
||||
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||||
| 62 | $partitionCache_invalidation_events = $this->getCacheInvalidationEventsUnprocessedPartitionName($shard, $priority); |
||||
|
0 ignored issues
–
show
It seems like
$priority can also be of type null; however, parameter $priorityId of Padosoft\SuperCacheInval...rocessedPartitionName() does only seem to accept integer, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 63 | break; |
||||
| 64 | case 1: |
||||
| 65 | $partitionCache_invalidation_events = $this->getCacheInvalidationEventsProcessedPartitionName($shard, $priority, $event_time ?? now()); |
||||
|
0 ignored issues
–
show
It seems like
$priority can also be of type null; however, parameter $priorityId of Padosoft\SuperCacheInval...rocessedPartitionName() does only seem to accept integer, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 66 | break; |
||||
| 67 | default: |
||||
| 68 | $attempts = 5; // Mi fermo |
||||
| 69 | throw new \RuntimeException('Invalid value for processed'); |
||||
| 70 | } |
||||
| 71 | // $eventId = DB::table(DB::raw("`cache_invalidation_events` PARTITION ({$partitionCache_invalidation_events})"))->insertGetId($data); |
||||
| 72 | DB::table(DB::raw("`cache_invalidation_events` PARTITION ({$partitionCache_invalidation_events})"))->insert($data); |
||||
| 73 | // Insert associated identifiers |
||||
| 74 | // TODO JB 31/12/2024: per adesso commentato, da riattivare quando tutto funziona alla perfezione usando la partizione, |
||||
| 75 | // anche perchè la chiave primaria è data da id e partiton_key, per cui va capito cosa restituisce insertGetId e se è bloccante |
||||
| 76 | /* |
||||
| 77 | if (!empty($associatedIdentifiers)) { |
||||
| 78 | $associations = []; |
||||
| 79 | foreach ($associatedIdentifiers as $associated) { |
||||
| 80 | $associations[] = [ |
||||
| 81 | 'event_id' => $eventId, |
||||
| 82 | 'associated_type' => $associated['type'], // 'key' or 'tag' |
||||
| 83 | 'associated_identifier' => $associated['identifier'], |
||||
| 84 | 'connection_name' => $associated['connection_name'], |
||||
| 85 | 'created_at' => now(), |
||||
| 86 | ]; |
||||
| 87 | } |
||||
| 88 | DB::table('cache_invalidation_event_associations')->insert($associations); |
||||
| 89 | } |
||||
| 90 | */ |
||||
| 91 | $insertOk = true; |
||||
| 92 | // DB::commit(); // Completa la transazione |
||||
| 93 | } catch (\Throwable $e) { |
||||
| 94 | // DB::rollBack(); // Annulla la transazione in caso di errore |
||||
| 95 | $attempts++; |
||||
| 96 | Log::error("SuperCacheInvalidate: impossibile eseguire insert, tentativo $attempts di $maxAttempts: " . $e->getMessage()); |
||||
| 97 | // Logica per gestire i tentativi falliti |
||||
| 98 | if ($attempts >= $maxAttempts) { |
||||
| 99 | // Salta il record dopo il numero massimo di tentativi |
||||
| 100 | Log::error("SuperCacheInvalidate: impossibile eseguire insert dopo $maxAttempts tentativi: " . $e->getMessage()); |
||||
| 101 | } |
||||
| 102 | } |
||||
| 103 | } |
||||
| 104 | } |
||||
| 105 | |||||
| 106 | /** |
||||
| 107 | * Acquire a lock for processing a shard. |
||||
| 108 | * |
||||
| 109 | * @param int $shardId The shard number |
||||
| 110 | * @param int $lockTimeout Lock timeout in seconds |
||||
| 111 | * @param string $connection_name The Redis Connection name |
||||
| 112 | * @return string|false The lock value if acquired, false otherwise |
||||
| 113 | */ |
||||
| 114 | public function acquireShardLock(int $shardId, int $priority, int $lockTimeout, string $connection_name): bool|string |
||||
| 115 | { |
||||
| 116 | $lockKey = 'shard_lock:' . $shardId . '_' . $priority; |
||||
| 117 | // Il metodo has/exists occupa troppa memoria!!! |
||||
| 118 | $retrieveValue = Redis::connection($connection_name)->get($lockKey); |
||||
| 119 | if ($retrieveValue !== null) { |
||||
| 120 | // Lock già attivo |
||||
| 121 | return false; |
||||
| 122 | } |
||||
| 123 | $lockValue = uniqid('', true); |
||||
| 124 | $isLocked = Redis::connection($connection_name)->set($lockKey, $lockValue); |
||||
| 125 | |||||
| 126 | if ($lockTimeout > 0) { |
||||
| 127 | Redis::connection($connection_name)->expire($lockKey, $lockTimeout); |
||||
| 128 | } |
||||
| 129 | |||||
| 130 | return $isLocked ? $lockValue : false; |
||||
| 131 | } |
||||
| 132 | |||||
| 133 | /** |
||||
| 134 | * Release the lock for a shard. |
||||
| 135 | * |
||||
| 136 | * @param int $shardId The shard number |
||||
| 137 | * @param string $lockValue The lock value to validate ownership |
||||
| 138 | * @param string $connection_name The Redis Connection name |
||||
| 139 | */ |
||||
| 140 | public function releaseShardLock(int $shardId, int $priority, string $lockValue, string $connection_name): void |
||||
| 141 | { |
||||
| 142 | $lockKey = 'shard_lock:' . $shardId . '_' . $priority; |
||||
| 143 | $currentValue = Redis::connection($connection_name)->get($lockKey); |
||||
| 144 | if ($currentValue === $lockValue) { |
||||
| 145 | Redis::connection($connection_name)->del($lockKey); |
||||
| 146 | } |
||||
| 147 | } |
||||
| 148 | |||||
| 149 | public function getCacheInvalidationEventsUnprocessedPartitionName(int $shardId, int $priorityId): string |
||||
| 150 | { |
||||
| 151 | $partitionValue = ($priorityId * 10) + $shardId; |
||||
| 152 | |||||
| 153 | return "p_unprocessed_{$partitionValue}"; |
||||
| 154 | } |
||||
| 155 | |||||
| 156 | public function getCacheInvalidationEventsProcessedPartitionName(int $shardId, int $priorityId, Carbon $event_time): string |
||||
| 157 | { |
||||
| 158 | $partitionValue = ($event_time->year * 10000) + ($event_time->weekOfYear * 100) + ($priorityId * 10) + $shardId; |
||||
| 159 | |||||
| 160 | return "p_processed_{$partitionValue}"; |
||||
| 161 | } |
||||
| 162 | } |
||||
| 163 |