From 9bf1926d4f77eb6a80a99eb3ebdb0dcda84e9342 Mon Sep 17 00:00:00 2001 From: Paul Hachmang Date: Thu, 24 Sep 2026 18:29:18 +0200 Subject: [PATCH] Entries that Mage-OS compresses in Redis reach the local files The lifetime rule compared the packed record with the value the frontend answered, so every entry above the compression threshold, among them the translations, the EAV attributes and most config data, stayed a Redis read per request. --- CHANGELOG.md | 4 ++++ src/FastBootCache/Model/EntryLifetime.php | 21 ++++++++++++++++- .../Test/Unit/Model/EntryLifetimeTest.php | 23 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ccb47e..786c702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Promote entries that Mage-OS stores through its compression decorator to the local files; the lifetime rule reads the packed record as the frontend answers it. + ## 0.2.0-rc6 - Read the di.xml files through the runtime loader of Magento when the installation has no compiled metadata, so a developer-mode install serves requests. diff --git a/src/FastBootCache/Model/EntryLifetime.php b/src/FastBootCache/Model/EntryLifetime.php index e74965c..a4d03ab 100644 --- a/src/FastBootCache/Model/EntryLifetime.php +++ b/src/FastBootCache/Model/EntryLifetime.php @@ -17,7 +17,7 @@ public function remaining(FrontendInterface $frontend, string $id, mixed $value) $clean = preg_replace('/[^a-zA-Z0-9_]/', '_', str_replace('.', '__', strtoupper($id))); $item = $low->getItem($clean); $record = $item->isHit() ? $item->get() : null; - if (!is_array($record) || !array_key_exists('expire', $record) || ($record['data'] ?? null) !== $value) { + if (!is_array($record) || !array_key_exists('expire', $record) || self::stored($record['data'] ?? null) !== $value) { return false; } $expiry = $record['expire']; @@ -36,4 +36,23 @@ public function remaining(FrontendInterface $frontend, string $id, mixed $value) return false; } } + + /** + * The value as the compression decorator of Mage-OS hands it to the frontends above it: + * a string above its threshold is stored with the CACHE_COMPRESSION prefix and packed. + */ + private static function stored(mixed $data): mixed + { + if (!is_string($data) || !str_starts_with($data, 'CACHE_COMPRESSION')) { + return $data; + } + $packed = substr($data, strlen('CACHE_COMPRESSION')); + foreach (['gzuncompress', 'snappy_uncompress', 'lzf_decompress', 'lz4_uncompress', 'zstd_uncompress'] as $function) { + if (function_exists($function) && ($value = @$function($packed)) !== false) { + return $value; + } + } + + return $data; + } } diff --git a/src/FastBootCache/Test/Unit/Model/EntryLifetimeTest.php b/src/FastBootCache/Test/Unit/Model/EntryLifetimeTest.php index 58f33fb..172b7b2 100644 --- a/src/FastBootCache/Test/Unit/Model/EntryLifetimeTest.php +++ b/src/FastBootCache/Test/Unit/Model/EntryLifetimeTest.php @@ -6,6 +6,7 @@ use GraphCommerce\FastBootCache\Model\EntryLifetime; use Magento\Framework\Cache\Frontend\Adapter\Symfony; +use Magento\Framework\Cache\Frontend\Decorator\Compression; use PHPUnit\Framework\TestCase; class EntryLifetimeTest extends TestCase @@ -33,6 +34,28 @@ public function testPromotionUsesActualExpiryAndRejectsChangedPayload(): void $frontend->clean(); } } + public function testAnEntryStoredThroughTheCompressionDecoratorIsPromotedWithItsExpiry(): void + { + if (!class_exists(Symfony::class) || !class_exists(Compression::class)) { + self::markTestSkipped('The compression decorator is not present in this Magento version.'); + } + $dir = sys_get_temp_dir().'/fastboot-compressed-'.bin2hex(random_bytes(8)); + $frontend = new Compression(new Symfony(static fn () => new \Symfony\Component\Cache\Adapter\FilesystemAdapter('lifetime', 7200, $dir)), 8); + $reader = new EntryLifetime(); + $value = str_repeat('translated text ', 64); + try { + $frontend->save($value, 'big.id', ['TRANSLATE'], 30); + self::assertSame($value, $frontend->load('big.id')); + $ttl = $reader->remaining($frontend, 'big.id', $value); + self::assertIsInt($ttl); + self::assertGreaterThan(0, $ttl); + self::assertLessThanOrEqual(30, $ttl); + self::assertFalse($reader->remaining($frontend, 'big.id', $value.'changed')); + } finally { + $frontend->clean(); + } + } + public function testLegacyZendFrontendPreservesExpiryAndPermanentEntries(): void { $dir = sys_get_temp_dir().'/fastboot-zend-'.bin2hex(random_bytes(8));