Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,6 @@ OTEL_AUDIT_ELASTICSEARCH_INDEX=logs-audit

L5_SWAGGER_CONST_HOST=${APP_URL}
L5_SWAGGER_CONST_AUTH_URL=${IDP_AUTHORIZATION_ENDPOINT}
L5_SWAGGER_CONST_TOKEN_URL=${IDP_TOKEN_ENDPOINT}
L5_SWAGGER_CONST_TOKEN_URL=${IDP_TOKEN_ENDPOINT}
MEMCACHED_SERVER_HOST=127.0.0.1
MEMCACHED_SERVER_PORT=11211
319 changes: 165 additions & 154 deletions .github/workflows/push.yml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ ENV PATH=$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH

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

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

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

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

RUN cd /var/www/public && curl -LO https://raw.githubusercontent.com/krakjoe/apcu/master/apc.php
RUN cd /var/www/public && curl -LO https://raw.githubusercontent.com/krakjoe/apcu/master/apc.php
100 changes: 67 additions & 33 deletions app/Http/Middleware/CacheMiddleware.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace App\Http\Middleware;

use App\Utils\Cache\MemCache;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
Expand Down Expand Up @@ -87,49 +88,82 @@ public function handle($request, Closure $next, $cache_lifetime, $cache_region =
}
}
$status = 200;
$wasMemCacheHit = false;
$wasHit = false;
$data = null;

if ($regionTag) {
Log::debug("CacheMiddleware: using region tag {$regionTag} ip {$ip} agent {$agent}");
$wasHit = Cache::tags($regionTag)->has($key);
Log::debug($wasHit ? "CacheMiddleware: cache HIT (tagged)" : "CacheMiddleware: cache MISS (tagged)", [
'tag' => $regionTag,
'ip' => $ip,
'agent' => $agent,
'key' => $key,
]);

$encoded = Cache::tags($regionTag)
->remember($key, $cache_lifetime, function() use ($next, $request, $regionTag, $key, $cache_lifetime, &$status,$ip, $agent) {
// try L1 APC
$encoded = MemCache::get($key);
$wasMemCacheHit = $encoded !== null;
if($wasMemCacheHit){
Log::debug("CacheMiddleware:: MemcCache Hit");
}
if(!$wasMemCacheHit) {
// then L2 Redis
$wasHit = Cache::tags($regionTag)->has($key);
Log::debug($wasHit ? "CacheMiddleware: cache HIT Redis (tagged)" : "CacheMiddleware: cache MISS (tagged)", [
'tag' => $regionTag,
'ip' => $ip,
'agent' => $agent,
'key' => $key,
]);

$encoded = Cache::tags($regionTag)
->remember($key, $cache_lifetime, function () use ($next, $request, $regionTag, $key, $cache_lifetime, &$status, $ip, $agent) {
$resp = $next($request);
if ($resp instanceof JsonResponse) {
$status = $resp->getStatusCode();
if ($status === 200) {
return $this->encode($resp->getData(true));
}
}
// don’t cache non-200 or non-JSON
return Cache::get($key);
});


// backfill APC only if we actually have a value
if ($encoded !== null) { // avoid null writes
MemCache::put($key, $encoded, $cache_lifetime, $regionTag);
}
}
$data = $this->decode($encoded);
} else {
// try L1 APC
$encoded = MemCache::get($key);
$wasMemCacheHit = !is_null($encoded);
if($wasMemCacheHit){
Log::debug("CacheMiddleware:: MemcCache Hit");
}
if(!$wasMemCacheHit) {
// then L2 Redis

$wasHit = Cache::has($key);

Log::debug($wasHit ? "CacheMiddleware: cache HIT" : "CacheMiddleware: cache MISS", [
'ip' => $ip,
'agent' => $agent,
'key' => $key,
]);

$encoded = Cache::remember($key, $cache_lifetime, function () use ($next, $request, $key, &$status, $ip, $agent) {
$resp = $next($request);
if ($resp instanceof JsonResponse) {
$status = $resp->getStatusCode();
if($status === 200) {
if ($status === 200)
return $this->encode($resp->getData(true));
}
}
// don’t cache non-200 or non-JSON
return Cache::get($key);
});
$data = $this->decode($encoded);
} else {
$wasHit = Cache::has($key);

Log::debug($wasHit ? "CacheMiddleware: cache HIT" : "CacheMiddleware: cache MISS", [
'ip' => $ip,
'agent' => $agent,
'key' => $key,
]);

$encoded = Cache::remember($key, $cache_lifetime, function() use ($next, $request, $key, &$status, $ip, $agent) {
$resp = $next($request);
if ($resp instanceof JsonResponse) {
$status = $resp->getStatusCode();
if($status === 200)
return $this->encode($resp->getData(true));
// store at APC
if ($encoded !== null) { // avoid null writes
MemCache::put($key, $encoded, $cache_lifetime);
}
return Cache::get($key);
});
$data = $this->decode($encoded);

$data = $this->decode($encoded);
}
}
// safe guard
if ($data === null) $data = is_array($encoded) ? $encoded : [];
Expand All @@ -143,7 +177,7 @@ public function handle($request, Closure $next, $cache_lifetime, $cache_region =
$response->headers->addCacheControlDirective('must-revalidate', true);
$response->headers->addCacheControlDirective('proxy-revalidate', true);
$response->headers->add([
'X-Cache-Result' => $wasHit ? 'HIT':'MISS',
'X-Cache-Result' => $wasMemCacheHit ? 'HIT MemcCache' : ($wasHit ? 'HIT REDIS' : 'MISS'),
]);
Log::debug( "CacheMiddleware: returning response", [
'ip' => $ip,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public function process(string $entity_operator, int $summit_id, int $entity_id,
}

if (!empty($cache_region_key)) {
Log::debug(sprintf("ProcessScheduleEntityLifeCycleEventService::process", ['cache_region_key' => $cache_region_key]));
$this->cache_service->clearCacheRegion($cache_region_key);
}

Expand Down
5 changes: 4 additions & 1 deletion app/Services/Utils/RedisCacheService.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* limitations under the License.
**/

use App\Utils\Cache\MemCache;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use libs\utils\ICacheService;
Expand Down Expand Up @@ -331,15 +332,17 @@ public function ttl($key)
return (int)$conn->ttl($key);
}, 0);
}

/**
* @param string $cache_region_key
* @return void
*/
public function clearCacheRegion(string $cache_region_key): void
{
Log::debug("RedisCacheService::clearCacheRegion", ["key" => $cache_region_key]);
if (!empty($cache_region_key)) {
Cache::tags($cache_region_key)->flush();
MemCache::apcClearRegion($cache_region_key);
if($this->exists($cache_region_key)){
Log::debug(sprintf("RedisCacheService::clearCacheRegion will clear cache region %s", $cache_region_key));
$region_data = $this->getSingleValue($cache_region_key);
Expand Down
75 changes: 75 additions & 0 deletions app/Utils/Cache/MemCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php namespace App\Utils\Cache;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

final class MemCache
{
private const CM_REGION = 'mem:region:';

private static function store(): \Illuminate\Contracts\Cache\Repository
{
return Cache::store('memcached');
}

public static function get(string $key)
{
try {
Log::debug("MemCacheService::get", ["key" => $key]);
return self::store()->get($key, null);
} catch (\Throwable $e) {
Log::warning($e);
return null;
}
}


private static function trackKey(string $regionTag, string $key, int $ttl): void
{
try {
$store = self::store();
Log::debug("MemCache::trackKey", ["regionTag" => $regionTag, "key" => $key, "ttl" => $ttl]);
$setKey = self::CM_REGION.$regionTag;
$list = $store->get($setKey, []);
if (!\is_array($list)) $list = [];
$list[$key] = \time() + $ttl;
if (\count($list) > 10000) { array_shift($list); }
$store->put($setKey, $list, $ttl);
} catch (\Throwable $e) {
Log::warning($e);
}
}
Comment on lines +27 to +41
Copy link

Copilot AI Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The hard-coded 10000 cap is a magic number. Consider extracting it to a class constant (e.g., MAX_KEYS_PER_REGION) or a config value so it can be tuned without code changes.

Copilot uses AI. Check for mistakes.

public static function put(string $key, $value, int $ttl, ?string $regionTag = null): void
{
try {
Log::debug("MemCache::put", ["key" => $key, "value" => $value, "ttl" => $ttl]);
self::store()->put($key, $value, $ttl);
if ($regionTag) self::trackKey($regionTag, $key, $ttl);
} catch (\Throwable $e) {
Log::warning($e);
}
}

public static function apcClearRegion(string $regionTag): int
{
try {
$store = self::store();
$setKey = self::CM_REGION.$regionTag;
$list = $store->get($setKey, []);
$n = 0;
Log::debug("MemCache::apcClearRegion", ["regionTag" => $regionTag, "list" => $list]);
if (\is_array($list)) {
foreach (array_keys($list) as $k) {
if ($store->forget($k)) $n++;
}
}
$store->forget($setKey);
return $n;
} catch (\Throwable $e) {
Log::warning($e);
return 0;
}
}

}
13 changes: 9 additions & 4 deletions config/cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,17 @@

'memcached' => [
'driver' => 'memcached',
'servers' => [
//'persistent_id' => 'host-cache',
'sasl' => [null, null],
'servers' => [
// UNIX socket (fastest)
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
'host' => env('MEMCACHED_SERVER_HOST', '/var/run/memcached/memcached.sock'),
'port' => env('MEMCACHED_SERVER_PORT',0),
'weight' => env('MEMCACHED_SERVER_WEIGHT',100)
],
// or TCP if you prefer:
// ['host' => '127.0.0.1', 'port' => 11211, 'weight' => 100],
],
],

Expand Down
Loading
Loading