Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -1,51 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Cache;
|
||||
|
||||
final readonly class MultiLevelCache implements Cache
|
||||
use App\Framework\Cache\Contracts\DriverAccessible;
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
|
||||
final readonly class MultiLevelCache implements Cache, DriverAccessible
|
||||
{
|
||||
private const int MAX_FAST_CACHE_SIZE = 1024; //1KB
|
||||
private const int FAST_CACHE_TTL = 300;
|
||||
private const int FAST_CACHE_TTL_SECONDS = 300;
|
||||
|
||||
public function __construct(
|
||||
private Cache $fastCache, // z.B. ArrayCache
|
||||
private Cache $slowCache // z.B. RedisCache, FileCache
|
||||
) {}
|
||||
|
||||
public function get(string $key): CacheItem
|
||||
{
|
||||
$item = $this->fastCache->get($key);
|
||||
if ($item->isHit) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
// Fallback auf "langsame" Ebene
|
||||
$item = $this->slowCache->get($key);
|
||||
if ($item->isHit) {
|
||||
// In schnellen Cache zurücklegen (optional mit TTL aus Slow-Item)
|
||||
if($this->shouldCacheInFast($item->value)) {
|
||||
$this->fastCache->set($key, $item->value, self::FAST_CACHE_TTL);
|
||||
}
|
||||
}
|
||||
return $item;
|
||||
) {
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value, ?int $ttl = null): bool
|
||||
/**
|
||||
* Returns the default TTL for fast cache as a Duration object
|
||||
*/
|
||||
private static function getDefaultFastCacheTTL(): Duration
|
||||
{
|
||||
$slowSuccess = $this->slowCache->set($key, $value, $ttl);
|
||||
return Duration::fromSeconds(self::FAST_CACHE_TTL_SECONDS);
|
||||
}
|
||||
|
||||
public function get(CacheIdentifier ...$identifiers): CacheResult
|
||||
{
|
||||
if (empty($identifiers)) {
|
||||
return CacheResult::empty();
|
||||
}
|
||||
|
||||
// Try fast cache first
|
||||
$fastResult = $this->fastCache->get(...$identifiers);
|
||||
$allItems = [];
|
||||
$missedIdentifiers = [];
|
||||
|
||||
foreach ($identifiers as $identifier) {
|
||||
$item = $fastResult->getItem($identifier instanceof CacheKey ? $identifier : CacheKey::fromString($identifier->toString()));
|
||||
|
||||
if ($item->isHit) {
|
||||
$allItems[] = $item;
|
||||
} else {
|
||||
$missedIdentifiers[] = $identifier;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to slow cache for missed items
|
||||
if (! empty($missedIdentifiers)) {
|
||||
$slowResult = $this->slowCache->get(...$missedIdentifiers);
|
||||
|
||||
foreach ($slowResult->getItems() as $item) {
|
||||
if ($item->isHit && $item->key instanceof CacheKey) {
|
||||
// Cache in fast cache if appropriate
|
||||
if ($this->shouldCacheInFast($item->value)) {
|
||||
$this->fastCache->set(CacheItem::forSet($item->key, $item->value, self::getDefaultFastCacheTTL()));
|
||||
}
|
||||
}
|
||||
$allItems[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return CacheResult::fromItems(...$allItems);
|
||||
}
|
||||
|
||||
public function set(CacheItem ...$items): bool
|
||||
{
|
||||
if (empty($items)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$slowSuccess = $this->slowCache->set(...$items);
|
||||
|
||||
// Also set in fast cache if appropriate
|
||||
$fastItems = [];
|
||||
foreach ($items as $item) {
|
||||
if ($this->shouldCacheInFast($item->value)) {
|
||||
$fastTtl = $item->ttl !== null ?
|
||||
Duration::fromSeconds(min($item->ttl->toSeconds(), self::FAST_CACHE_TTL_SECONDS)) :
|
||||
self::getDefaultFastCacheTTL();
|
||||
$fastItems[] = CacheItem::forSet($item->key, $item->value, $fastTtl);
|
||||
}
|
||||
}
|
||||
|
||||
$fastSuccess = true;
|
||||
if($this->shouldCacheInFast($value)) {
|
||||
$fastTtl = min($ttl ?? self::FAST_CACHE_TTL, self::FAST_CACHE_TTL);
|
||||
$fastSuccess = $this->fastCache->set($key, $value, $fastTtl);
|
||||
if (! empty($fastItems)) {
|
||||
$fastSuccess = $this->fastCache->set(...$fastItems);
|
||||
}
|
||||
|
||||
return $slowSuccess && $fastSuccess;
|
||||
}
|
||||
|
||||
public function forget(string $key): bool
|
||||
public function forget(CacheIdentifier ...$identifiers): bool
|
||||
{
|
||||
$s1 = $this->fastCache->forget($key);
|
||||
$s2 = $this->slowCache->forget($key);
|
||||
if (empty($identifiers)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$s1 = $this->fastCache->forget(...$identifiers);
|
||||
$s2 = $this->slowCache->forget(...$identifiers);
|
||||
|
||||
return $s1 && $s2;
|
||||
}
|
||||
|
||||
@@ -53,37 +108,54 @@ final readonly class MultiLevelCache implements Cache
|
||||
{
|
||||
$s1 = $this->fastCache->clear();
|
||||
$s2 = $this->slowCache->clear();
|
||||
|
||||
return $s1 && $s2;
|
||||
}
|
||||
|
||||
/*
|
||||
* - **has**: Versucht zuerst den schnelleren Layer. Im Falle eines Hits im "slowCache" wird der Wert aufgewärmt/gecached, damit zukünftige Zugriffe wieder schneller sind.
|
||||
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
public function has(CacheIdentifier ...$identifiers): array
|
||||
{
|
||||
// Schneller Check im Fast-Cache
|
||||
if ($this->fastCache->has($key)) {
|
||||
return true;
|
||||
if (empty($identifiers)) {
|
||||
return [];
|
||||
}
|
||||
// Ggf. im Slow-Cache prüfen (und dann in Fast-Cache "aufwärmen")
|
||||
$slowHit = $this->slowCache->get($key);
|
||||
if ($slowHit->isHit) {
|
||||
if($this->shouldCacheInFast($slowHit->value)) {
|
||||
$this->fastCache->set($key, $slowHit->value, self::FAST_CACHE_TTL);
|
||||
|
||||
$fastResults = $this->fastCache->has(...$identifiers);
|
||||
$results = [];
|
||||
$toCheckInSlow = [];
|
||||
|
||||
foreach ($identifiers as $identifier) {
|
||||
$keyString = $identifier->toString();
|
||||
if ($fastResults[$keyString] ?? false) {
|
||||
$results[$keyString] = true;
|
||||
} else {
|
||||
$toCheckInSlow[] = $identifier;
|
||||
$results[$keyString] = false; // Default to false, will be updated if found in slow
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
// Check missed items in slow cache and warm up fast cache
|
||||
if (! empty($toCheckInSlow)) {
|
||||
$slowResults = $this->slowCache->has(...$toCheckInSlow);
|
||||
foreach ($toCheckInSlow as $identifier) {
|
||||
$keyString = $identifier->toString();
|
||||
if ($slowResults[$keyString] ?? false) {
|
||||
$results[$keyString] = true;
|
||||
|
||||
// Warm up fast cache if it's a key identifier
|
||||
if ($identifier instanceof CacheKey) {
|
||||
$slowResult = $this->slowCache->get($identifier);
|
||||
$item = $slowResult->getItem($identifier);
|
||||
if ($item->isHit && $this->shouldCacheInFast($item->value)) {
|
||||
$this->fastCache->set(CacheItem::forSet($identifier, $item->value, self::getDefaultFastCacheTTL()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/*
|
||||
* - **remember**: Holt sich das Item per `get` (inkl. aller Multi-Level-Vorteile, wie bereits vorhanden). Wenn nicht im Cache, wird das Callback ausgeführt, gespeichert und als Treffer zurückgegeben.
|
||||
*/
|
||||
|
||||
public function remember(string $key, callable $callback, int $ttl = 3600): CacheItem
|
||||
public function remember(CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
|
||||
{
|
||||
$item = $this->get($key);
|
||||
if ($item->isHit) {
|
||||
@@ -93,9 +165,9 @@ final readonly class MultiLevelCache implements Cache
|
||||
// Wert generieren, speichern und zurückgeben
|
||||
$value = $callback();
|
||||
$this->set($key, $value, $ttl);
|
||||
|
||||
// Erstelle neuen CacheItem als Treffer
|
||||
return CacheItem::hit($key, $value);
|
||||
|
||||
}
|
||||
|
||||
private function shouldCacheInFast(mixed $value): bool
|
||||
@@ -110,6 +182,7 @@ final readonly class MultiLevelCache implements Cache
|
||||
$elementCount = count($value, COUNT_RECURSIVE);
|
||||
// Grobe Schätzung: 50 Bytes pro Element
|
||||
$estimatedSize = $elementCount * 50;
|
||||
|
||||
return $estimatedSize <= self::MAX_FAST_CACHE_SIZE;
|
||||
}
|
||||
|
||||
@@ -121,4 +194,57 @@ final readonly class MultiLevelCache implements Cache
|
||||
// Primitive Typen: Immer cachen
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying cache driver (uses slow cache as primary)
|
||||
*/
|
||||
public function getDriver(): ?CacheDriver
|
||||
{
|
||||
// Try slow cache first as it's typically the primary storage
|
||||
if ($this->slowCache instanceof DriverAccessible) {
|
||||
return $this->slowCache->getDriver();
|
||||
}
|
||||
|
||||
// If slow cache doesn't have driver access, try fast cache
|
||||
if ($this->fastCache instanceof DriverAccessible) {
|
||||
return $this->fastCache->getDriver();
|
||||
}
|
||||
|
||||
// Check if the cache layers are directly drivers
|
||||
if ($this->slowCache instanceof CacheDriver) {
|
||||
return $this->slowCache;
|
||||
}
|
||||
|
||||
if ($this->fastCache instanceof CacheDriver) {
|
||||
return $this->fastCache;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the underlying driver supports a specific interface
|
||||
*/
|
||||
public function driverSupports(string $interface): bool
|
||||
{
|
||||
$driver = $this->getDriver();
|
||||
|
||||
return $driver !== null && $driver instanceof $interface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the slow cache (primary storage layer)
|
||||
*/
|
||||
public function getSlowCache(): Cache
|
||||
{
|
||||
return $this->slowCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fast cache (quick access layer)
|
||||
*/
|
||||
public function getFastCache(): Cache
|
||||
{
|
||||
return $this->fastCache;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user