Passed
Pull Request — main (#3)
by
unknown
09:23 queued 08:34
created

SuperCacheInvalidationHelper   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 191
Duplicated Lines 0 %

Importance

Changes 7
Bugs 1 Features 0
Metric Value
eloc 73
dl 0
loc 191
rs 10
c 7
b 1
f 0
wmc 26

6 Methods

Rating   Name   Duplication   Size   Complexity  
B insertInvalidationEvent() 0 64 6
A getCacheInvalidationEventsProcessedPartitionName() 0 24 6
A getCacheInvalidationTimestampsPartitionName() 0 18 4
A releaseShardLock() 0 6 2
A getCacheInvalidationEventsPartitionName() 0 20 4
A acquireShardLock() 0 17 4
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 array|null  $associatedIdentifiers Optional array of associated tags or keys
22
     */
23
    public function insertInvalidationEvent(
24
        string $type,
25
        string $identifier,
26
        ?string $connection_name = null,
27
        ?string $reason = null,
28
        ?int $totalShards = 0,
29
        ?int $priority = 0,
30
        ?array $associatedIdentifiers = [],
0 ignored issues
show
Unused Code introduced by
The parameter $associatedIdentifiers is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

30
        /** @scrutinizer ignore-unused */ ?array $associatedIdentifiers = [],

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
31
    ): void {
32
        $shard = crc32($identifier) % ($totalShards > 0 ? $totalShards : config('super_cache_invalidate.total_shards', 10));
33
34
        $redisConnectionName = $connection_name ?? config('super_cache_invalidate.default_connection_name');
35
        $data = [
36
            'type' => $type,
37
            'identifier' => $identifier,
38
            'connection_name' => $redisConnectionName,
39
            'reason' => $reason,
40
            'priority' => $priority,
41
            'event_time' => now(),
42
            'processed' => 0,
43
            'shard' => $shard,
44
        ];
45
46
        $maxAttempts = 5;
47
        $attempts = 0;
48
        $insertOk = false;
49
50
        while ($attempts < $maxAttempts && !$insertOk) {
51
            //DB::beginTransaction();
52
53
            try {
54
                // Cerca di bloccare il record per l'inserimento
55
                $partitionCache_invalidation_events = $this->getCacheInvalidationEventsPartitionName($shard, $priority);
0 ignored issues
show
Bug introduced by
It seems like $priority can also be of type null; however, parameter $priorityId of Padosoft\SuperCacheInval...onEventsPartitionName() 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 ignore-type  annotation

55
                $partitionCache_invalidation_events = $this->getCacheInvalidationEventsPartitionName($shard, /** @scrutinizer ignore-type */ $priority);
Loading history...
56
57
                //$eventId = DB::table(DB::raw("`cache_invalidation_events` PARTITION ({$partitionCache_invalidation_events})"))->insertGetId($data);
58
                DB::table(DB::raw("`cache_invalidation_events` PARTITION ({$partitionCache_invalidation_events})"))->insert($data);
59
                // Insert associated identifiers
60
                // TODO JB 31/12/2024: per adesso commentato, da riattivare quando tutto funziona alla perfezione usando la partizione,
61
                // anche perchè la chiave primaria è data da id e partiton_key, per cui va capito cosa restituisce insertGetId e se è bloccante
62
                /*
63
                if (!empty($associatedIdentifiers)) {
64
                    $associations = [];
65
                    foreach ($associatedIdentifiers as $associated) {
66
                        $associations[] = [
67
                            'event_id' => $eventId,
68
                            'associated_type' => $associated['type'], // 'key' or 'tag'
69
                            'associated_identifier' => $associated['identifier'],
70
                            'connection_name' => $associated['connection_name'],
71
                            'created_at' => now(),
72
                        ];
73
                    }
74
                    DB::table('cache_invalidation_event_associations')->insert($associations);
75
                }
76
                */
77
                $insertOk = true;
78
                //DB::commit(); // Completa la transazione
79
            } catch (\Throwable $e) {
80
                //DB::rollBack(); // Annulla la transazione in caso di errore
81
                $attempts++;
82
                Log::error("SuperCacheInvalidate: impossibile eseguire insert, tentativo $attempts di $maxAttempts: " . $e->getMessage());
83
                // Logica per gestire i tentativi falliti
84
                if ($attempts >= $maxAttempts) {
85
                    // Salta il record dopo il numero massimo di tentativi
86
                    Log::error("SuperCacheInvalidate: impossibile eseguire insert dopo $maxAttempts tentativi: " . $e->getMessage());
87
                }
88
            }
89
        }
90
    }
91
92
    /**
93
     * Acquire a lock for processing a shard.
94
     *
95
     * @param  int          $shardId         The shard number
96
     * @param  int          $lockTimeout     Lock timeout in seconds
97
     * @param  string       $connection_name The Redis Connection name
98
     * @return string|false The lock value if acquired, false otherwise
99
     */
100
    public function acquireShardLock(int $shardId, int $priority, int $lockTimeout, string $connection_name): bool|string
101
    {
102
        $lockKey = 'shard_lock:' . $shardId . '_' . $priority;
103
        // Il metodo has/exists occupa troppa memoria!!!
104
        $retrieveValue = Redis::connection($connection_name)->get($lockKey);
105
        if ($retrieveValue !== null) {
106
            // Lock già attivo
107
            return false;
108
        }
109
        $lockValue = uniqid('', true);
110
        $isLocked = Redis::connection($connection_name)->set($lockKey, $lockValue);
111
112
        if ($lockTimeout > 0) {
113
            Redis::connection($connection_name)->expire($lockKey, $lockTimeout);
114
        }
115
116
        return $isLocked ? $lockValue : false;
117
    }
118
119
    /**
120
     * Release the lock for a shard.
121
     *
122
     * @param int    $shardId         The shard number
123
     * @param string $lockValue       The lock value to validate ownership
124
     * @param string $connection_name The Redis Connection name
125
     */
126
    public function releaseShardLock(int $shardId, int $priority, string $lockValue, string $connection_name): void
127
    {
128
        $lockKey = 'shard_lock:' . $shardId . '_' . $priority;
129
        $currentValue = Redis::connection($connection_name)->get($lockKey);
130
        if ($currentValue === $lockValue) {
131
            Redis::connection($connection_name)->del($lockKey);
132
        }
133
    }
134
135
    public function getCacheInvalidationEventsPartitionName(int $shardId, int $priorityId): string
136
    {
137
        // Calcola il valore della partizione
138
        $shards = config('super_cache_invalidate.total_shards', 10);
139
        $priorities = [0, 1];
140
141
        $partitionValueId = ($priorityId * $shards) + $shardId + 1;
142
143
        // Partitions for unprocessed events
144
        foreach ($priorities as $priority) {
145
            for ($shard = 0; $shard < $shards; $shard++) {
146
                $partitionName = "p_unprocessed_s{$shard}_p{$priority}";
147
                $partitionValue = ($priority * $shards) + $shard + 1;
148
                if ($partitionValueId < $partitionValue) {
149
                    return $partitionName;
150
                }
151
            }
152
        }
153
154
        return '';
155
    }
156
157
    public function getCacheInvalidationTimestampsPartitionName(Carbon $event_time): string
0 ignored issues
show
Unused Code introduced by
The parameter $event_time is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

157
    public function getCacheInvalidationTimestampsPartitionName(/** @scrutinizer ignore-unused */ Carbon $event_time): string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
158
    {
159
        $now = now();
160
        $partitionValueId = ($now->year * 100 + $now->weekOfYear) + 1;
161
        $startYear = 2024;
162
        $endYear = 2030;
163
164
        for ($year = $startYear; $year <= $endYear; $year++) {
165
            for ($week = 1; $week <= 53; $week++) {
166
                $partitionName = "p_{$year}w{$week}";
167
                $partitionValue = ($year * 100 + $week) + 1;
168
                if ($partitionValueId < $partitionValue) {
169
                    return $partitionName;
170
                }
171
            }
172
        }
173
174
        return '';
175
    }
176
177
    public function getCacheInvalidationEventsProcessedPartitionName(int $shardId, int $priorityId, string $event_time): string
178
    {
179
        // Calcola il valore della partizione
180
        $shards = config('super_cache_invalidate.total_shards', 10);
181
        $priorities = [0, 1];
182
        $eventTime = Carbon::parse($event_time);
183
        $partitionValueId = ($eventTime->year * 10000) + ($eventTime->weekOfYear * 100) + ($priorityId * $shards) + $shardId;
184
        // Partitions for processed events
185
        for ($year = $eventTime->year; $year <= ($eventTime->year + 1); $year++) {
186
            for ($week = $eventTime->weekOfYear; $week <= $eventTime->weekOfYear + 1; $week++) {
187
                foreach ($priorities as $priority) {
188
                    for ($shard = 0; $shard < $shards; $shard++) {
189
                        $partitionKey = ($year * 10000) + ($week * 100) + ($priority * $shards) + $shard;
190
                        $partitionValue = $partitionKey + 1;
191
                        $partitionName = "p_s{$shard}_p{$priority}_{$year}w{$week}";
192
                        if ($partitionValueId < $partitionValue) {
193
                            return $partitionName;
194
                        }
195
                    }
196
                }
197
            }
198
        }
199
200
        return '';
201
    }
202
}
203