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
![]() |
|||||
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
![]() |
|||||
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
![]() |
|||||
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 |