Skip to content

Commit d0c5913

Browse files
smarcetCopilot
andauthored
Feature/l1 memcached cache middleware (#412)
* chore: adds APC as L1 cache at cache middleware * chore: add memcached to local docker * chore: Update app/Services/Model/Imp/ProcessScheduleEntityLifeCycleEventService.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent abbde7c commit d0c5913

File tree

9 files changed

+511
-348
lines changed

9 files changed

+511
-348
lines changed

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,6 @@ OTEL_AUDIT_ELASTICSEARCH_INDEX=logs-audit
251251

252252
L5_SWAGGER_CONST_HOST=${APP_URL}
253253
L5_SWAGGER_CONST_AUTH_URL=${IDP_AUTHORIZATION_ENDPOINT}
254-
L5_SWAGGER_CONST_TOKEN_URL=${IDP_TOKEN_ENDPOINT}
254+
L5_SWAGGER_CONST_TOKEN_URL=${IDP_TOKEN_ENDPOINT}
255+
MEMCACHED_SERVER_HOST=127.0.0.1
256+
MEMCACHED_SERVER_PORT=11211

.github/workflows/push.yml

Lines changed: 165 additions & 154 deletions
Large diffs are not rendered by default.

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ ENV PATH=$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
4848

4949
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
5050

51-
RUN install-php-extensions bcmath exif gettext gd imagick mbstring openssl pcntl pdo pdo_mysql sockets ${XDEBUG_VERSION} zip apcu redis igbinary
51+
RUN install-php-extensions bcmath exif gettext gd imagick mbstring openssl pcntl pdo pdo_mysql sockets ${XDEBUG_VERSION} zip apcu redis igbinary memcached
5252

5353
# XDEBUG
5454
COPY docker-compose/php/docker-php-ext-xdebug.ini $PHP_DIR/conf.d/docker-php-ext-xdebug.ini
@@ -69,4 +69,4 @@ RUN chmod 777 -R storage
6969

7070
# access to http://localhost:8002/apc.php to see APC statistics
7171

72-
RUN cd /var/www/public && curl -LO https://raw.githubusercontent.com/krakjoe/apcu/master/apc.php
72+
RUN cd /var/www/public && curl -LO https://raw.githubusercontent.com/krakjoe/apcu/master/apc.php

app/Http/Middleware/CacheMiddleware.php

Lines changed: 67 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php namespace App\Http\Middleware;
22

3+
use App\Utils\Cache\MemCache;
34
use Closure;
45
use Illuminate\Http\JsonResponse;
56
use Illuminate\Support\Facades\Cache;
@@ -87,49 +88,82 @@ public function handle($request, Closure $next, $cache_lifetime, $cache_region =
8788
}
8889
}
8990
$status = 200;
91+
$wasMemCacheHit = false;
9092
$wasHit = false;
93+
$data = null;
94+
9195
if ($regionTag) {
9296
Log::debug("CacheMiddleware: using region tag {$regionTag} ip {$ip} agent {$agent}");
93-
$wasHit = Cache::tags($regionTag)->has($key);
94-
Log::debug($wasHit ? "CacheMiddleware: cache HIT (tagged)" : "CacheMiddleware: cache MISS (tagged)", [
95-
'tag' => $regionTag,
96-
'ip' => $ip,
97-
'agent' => $agent,
98-
'key' => $key,
99-
]);
100-
101-
$encoded = Cache::tags($regionTag)
102-
->remember($key, $cache_lifetime, function() use ($next, $request, $regionTag, $key, $cache_lifetime, &$status,$ip, $agent) {
97+
// try L1 APC
98+
$encoded = MemCache::get($key);
99+
$wasMemCacheHit = $encoded !== null;
100+
if($wasMemCacheHit){
101+
Log::debug("CacheMiddleware:: MemcCache Hit");
102+
}
103+
if(!$wasMemCacheHit) {
104+
// then L2 Redis
105+
$wasHit = Cache::tags($regionTag)->has($key);
106+
Log::debug($wasHit ? "CacheMiddleware: cache HIT Redis (tagged)" : "CacheMiddleware: cache MISS (tagged)", [
107+
'tag' => $regionTag,
108+
'ip' => $ip,
109+
'agent' => $agent,
110+
'key' => $key,
111+
]);
112+
113+
$encoded = Cache::tags($regionTag)
114+
->remember($key, $cache_lifetime, function () use ($next, $request, $regionTag, $key, $cache_lifetime, &$status, $ip, $agent) {
115+
$resp = $next($request);
116+
if ($resp instanceof JsonResponse) {
117+
$status = $resp->getStatusCode();
118+
if ($status === 200) {
119+
return $this->encode($resp->getData(true));
120+
}
121+
}
122+
// don’t cache non-200 or non-JSON
123+
return Cache::get($key);
124+
});
125+
126+
127+
// backfill APC only if we actually have a value
128+
if ($encoded !== null) { // avoid null writes
129+
MemCache::put($key, $encoded, $cache_lifetime, $regionTag);
130+
}
131+
}
132+
$data = $this->decode($encoded);
133+
} else {
134+
// try L1 APC
135+
$encoded = MemCache::get($key);
136+
$wasMemCacheHit = !is_null($encoded);
137+
if($wasMemCacheHit){
138+
Log::debug("CacheMiddleware:: MemcCache Hit");
139+
}
140+
if(!$wasMemCacheHit) {
141+
// then L2 Redis
142+
143+
$wasHit = Cache::has($key);
144+
145+
Log::debug($wasHit ? "CacheMiddleware: cache HIT" : "CacheMiddleware: cache MISS", [
146+
'ip' => $ip,
147+
'agent' => $agent,
148+
'key' => $key,
149+
]);
150+
151+
$encoded = Cache::remember($key, $cache_lifetime, function () use ($next, $request, $key, &$status, $ip, $agent) {
103152
$resp = $next($request);
104153
if ($resp instanceof JsonResponse) {
105154
$status = $resp->getStatusCode();
106-
if($status === 200) {
155+
if ($status === 200)
107156
return $this->encode($resp->getData(true));
108-
}
109157
}
110-
// don’t cache non-200 or non-JSON
111158
return Cache::get($key);
112159
});
113-
$data = $this->decode($encoded);
114-
} else {
115-
$wasHit = Cache::has($key);
116-
117-
Log::debug($wasHit ? "CacheMiddleware: cache HIT" : "CacheMiddleware: cache MISS", [
118-
'ip' => $ip,
119-
'agent' => $agent,
120-
'key' => $key,
121-
]);
122-
123-
$encoded = Cache::remember($key, $cache_lifetime, function() use ($next, $request, $key, &$status, $ip, $agent) {
124-
$resp = $next($request);
125-
if ($resp instanceof JsonResponse) {
126-
$status = $resp->getStatusCode();
127-
if($status === 200)
128-
return $this->encode($resp->getData(true));
160+
// store at APC
161+
if ($encoded !== null) { // avoid null writes
162+
MemCache::put($key, $encoded, $cache_lifetime);
129163
}
130-
return Cache::get($key);
131-
});
132-
$data = $this->decode($encoded);
164+
165+
$data = $this->decode($encoded);
166+
}
133167
}
134168
// safe guard
135169
if ($data === null) $data = is_array($encoded) ? $encoded : [];
@@ -143,7 +177,7 @@ public function handle($request, Closure $next, $cache_lifetime, $cache_region =
143177
$response->headers->addCacheControlDirective('must-revalidate', true);
144178
$response->headers->addCacheControlDirective('proxy-revalidate', true);
145179
$response->headers->add([
146-
'X-Cache-Result' => $wasHit ? 'HIT':'MISS',
180+
'X-Cache-Result' => $wasMemCacheHit ? 'HIT MemcCache' : ($wasHit ? 'HIT REDIS' : 'MISS'),
147181
]);
148182
Log::debug( "CacheMiddleware: returning response", [
149183
'ip' => $ip,

app/Services/Model/Imp/ProcessScheduleEntityLifeCycleEventService.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ public function process(string $entity_operator, int $summit_id, int $entity_id,
151151
}
152152

153153
if (!empty($cache_region_key)) {
154+
Log::debug("ProcessScheduleEntityLifeCycleEventService::process", ['cache_region_key' => $cache_region_key]);
154155
$this->cache_service->clearCacheRegion($cache_region_key);
155156
}
156157

app/Services/Utils/RedisCacheService.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* limitations under the License.
1313
**/
1414

15+
use App\Utils\Cache\MemCache;
1516
use Illuminate\Support\Facades\Cache;
1617
use Illuminate\Support\Facades\Log;
1718
use libs\utils\ICacheService;
@@ -331,15 +332,17 @@ public function ttl($key)
331332
return (int)$conn->ttl($key);
332333
}, 0);
333334
}
334-
335+
335336
/**
336337
* @param string $cache_region_key
337338
* @return void
338339
*/
339340
public function clearCacheRegion(string $cache_region_key): void
340341
{
342+
Log::debug("RedisCacheService::clearCacheRegion", ["key" => $cache_region_key]);
341343
if (!empty($cache_region_key)) {
342344
Cache::tags($cache_region_key)->flush();
345+
MemCache::apcClearRegion($cache_region_key);
343346
if($this->exists($cache_region_key)){
344347
Log::debug(sprintf("RedisCacheService::clearCacheRegion will clear cache region %s", $cache_region_key));
345348
$region_data = $this->getSingleValue($cache_region_key);

app/Utils/Cache/MemCache.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php namespace App\Utils\Cache;
2+
3+
use Illuminate\Support\Facades\Cache;
4+
use Illuminate\Support\Facades\Log;
5+
6+
final class MemCache
7+
{
8+
private const CM_REGION = 'mem:region:';
9+
10+
private static function store(): \Illuminate\Contracts\Cache\Repository
11+
{
12+
return Cache::store('memcached');
13+
}
14+
15+
public static function get(string $key)
16+
{
17+
try {
18+
Log::debug("MemCacheService::get", ["key" => $key]);
19+
return self::store()->get($key, null);
20+
} catch (\Throwable $e) {
21+
Log::warning($e);
22+
return null;
23+
}
24+
}
25+
26+
27+
private static function trackKey(string $regionTag, string $key, int $ttl): void
28+
{
29+
try {
30+
$store = self::store();
31+
Log::debug("MemCache::trackKey", ["regionTag" => $regionTag, "key" => $key, "ttl" => $ttl]);
32+
$setKey = self::CM_REGION.$regionTag;
33+
$list = $store->get($setKey, []);
34+
if (!\is_array($list)) $list = [];
35+
$list[$key] = \time() + $ttl;
36+
if (\count($list) > 10000) { array_shift($list); }
37+
$store->put($setKey, $list, $ttl);
38+
} catch (\Throwable $e) {
39+
Log::warning($e);
40+
}
41+
}
42+
43+
public static function put(string $key, $value, int $ttl, ?string $regionTag = null): void
44+
{
45+
try {
46+
Log::debug("MemCache::put", ["key" => $key, "value" => $value, "ttl" => $ttl]);
47+
self::store()->put($key, $value, $ttl);
48+
if ($regionTag) self::trackKey($regionTag, $key, $ttl);
49+
} catch (\Throwable $e) {
50+
Log::warning($e);
51+
}
52+
}
53+
54+
public static function apcClearRegion(string $regionTag): int
55+
{
56+
try {
57+
$store = self::store();
58+
$setKey = self::CM_REGION.$regionTag;
59+
$list = $store->get($setKey, []);
60+
$n = 0;
61+
Log::debug("MemCache::apcClearRegion", ["regionTag" => $regionTag, "list" => $list]);
62+
if (\is_array($list)) {
63+
foreach (array_keys($list) as $k) {
64+
if ($store->forget($k)) $n++;
65+
}
66+
}
67+
$store->forget($setKey);
68+
return $n;
69+
} catch (\Throwable $e) {
70+
Log::warning($e);
71+
return 0;
72+
}
73+
}
74+
75+
}

config/cache.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,17 @@
4949

5050
'memcached' => [
5151
'driver' => 'memcached',
52-
'servers' => [
52+
//'persistent_id' => 'host-cache',
53+
'sasl' => [null, null],
54+
'servers' => [
55+
// UNIX socket (fastest)
5356
[
54-
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
55-
'port' => env('MEMCACHED_PORT', 11211),
56-
'weight' => 100,
57+
'host' => env('MEMCACHED_SERVER_HOST', '/var/run/memcached/memcached.sock'),
58+
'port' => env('MEMCACHED_SERVER_PORT',0),
59+
'weight' => env('MEMCACHED_SERVER_WEIGHT',100)
5760
],
61+
// or TCP if you prefer:
62+
// ['host' => '127.0.0.1', 'port' => 11211, 'weight' => 100],
5863
],
5964
],
6065

0 commit comments

Comments
 (0)