Skip to content

Commit b65ff72

Browse files
andrey-legayevxmav
authored andcommitted
Fix layout xml and page layout caching issue on redis cluster under high load
Bugs which were fixed: - $this->pageLayout was not checked after reading from cache, but was used as is - two cache items were used in once place instead of one (performance impact) Changes: - replace 2 cache items by 1 - it should improve performance - add "_MERGED" to cache key suffix to have compatibility with old cache keys during deployment of new version
1 parent ce9b748 commit b65ff72

File tree

3 files changed

+87
-13
lines changed

3 files changed

+87
-13
lines changed

dev/tests/integration/testsuite/Magento/Framework/View/Layout/MergeTest.php

+19-3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class MergeTest extends \PHPUnit\Framework\TestCase
4141
*/
4242
protected $_cache;
4343

44+
/**
45+
* @var \Magento\Framework\Serialize\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject
46+
*/
47+
protected $_serializer;
48+
4449
/**
4550
* @var \PHPUnit_Framework_MockObject_MockObject
4651
*/
@@ -100,6 +105,8 @@ protected function setUp()
100105

101106
$this->_cache = $this->getMockForAbstractClass(\Magento\Framework\Cache\FrontendInterface::class);
102107

108+
$this->_serializer = $this->getMockForAbstractClass(\Magento\Framework\Serialize\SerializerInterface::class);
109+
103110
$this->_theme = $this->createMock(\Magento\Theme\Model\Theme::class);
104111
$this->_theme->expects($this->any())->method('isPhysical')->will($this->returnValue(true));
105112
$this->_theme->expects($this->any())->method('getArea')->will($this->returnValue('area'));
@@ -140,6 +147,7 @@ function ($filename) use ($fileDriver) {
140147
'resource' => $this->_resource,
141148
'appState' => $this->_appState,
142149
'cache' => $this->_cache,
150+
'serializer' => $this->_serializer,
143151
'theme' => $this->_theme,
144152
'validator' => $this->_layoutValidator,
145153
'logger' => $this->_logger,
@@ -276,9 +284,16 @@ public function testLoadFileSystemWithPageLayout()
276284

277285
public function testLoadCache()
278286
{
287+
$cacheValue = [
288+
"pageLayout" => "1column",
289+
"layout" => self::FIXTURE_LAYOUT_XML
290+
];
291+
279292
$this->_cache->expects($this->at(0))->method('load')
280-
->with('LAYOUT_area_STORE20_100c6a4ccd050e33acef0553f24ef399961')
281-
->will($this->returnValue(self::FIXTURE_LAYOUT_XML));
293+
->with('LAYOUT_area_STORE20_100c6a4ccd050e33acef0553f24ef399961_page_layout_merged')
294+
->will($this->returnValue(json_encode($cacheValue)));
295+
296+
$this->_serializer->expects($this->once())->method('unserialize')->willReturn($cacheValue);
282297

283298
$this->assertEmpty($this->_model->getHandles());
284299
$this->assertEmpty($this->_model->asString());
@@ -425,7 +440,8 @@ public function testLoadWithInvalidLayout()
425440
->willThrowException(new \Exception('Layout is invalid.'));
426441

427442
$suffix = md5(implode('|', $this->_model->getHandles()));
428-
$cacheId = "LAYOUT_{$this->_theme->getArea()}_STORE{$this->scope->getId()}_{$this->_theme->getId()}{$suffix}";
443+
$cacheId = "LAYOUT_{$this->_theme->getArea()}_STORE{$this->scope->getId()}"
444+
. "_{$this->_theme->getId()}{$suffix}_page_layout_merged";
429445
$messages = $this->_layoutValidator->getMessages();
430446

431447
// Testing error message is logged with logger

lib/internal/Magento/Framework/View/Model/Layout/Merge.php

+25-10
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
*/
66
namespace Magento\Framework\View\Model\Layout;
77

8+
use Magento\Framework\App\ObjectManager;
89
use Magento\Framework\App\State;
910
use Magento\Framework\Config\Dom\ValidationException;
1011
use Magento\Framework\Filesystem\DriverPool;
1112
use Magento\Framework\Filesystem\File\ReadFactory;
13+
use Magento\Framework\Serialize\SerializerInterface;
1214
use Magento\Framework\View\Layout\LayoutCacheKeyInterface;
1315
use Magento\Framework\View\Model\Layout\Update\Validator;
1416

@@ -42,7 +44,7 @@ class Merge implements \Magento\Framework\View\Layout\ProcessorInterface
4244
/**
4345
* Cache id suffix for page layout
4446
*/
45-
const PAGE_LAYOUT_CACHE_SUFFIX = 'page_layout';
47+
const PAGE_LAYOUT_CACHE_SUFFIX = 'page_layout_merged';
4648

4749
/**
4850
* @var \Magento\Framework\View\Design\ThemeInterface
@@ -54,6 +56,11 @@ class Merge implements \Magento\Framework\View\Layout\ProcessorInterface
5456
*/
5557
private $scope;
5658

59+
/**
60+
* @var SerializerInterface
61+
*/
62+
private $serializer;
63+
5764
/**
5865
* In-memory cache for loaded layout updates
5966
*
@@ -173,10 +180,11 @@ class Merge implements \Magento\Framework\View\Layout\ProcessorInterface
173180
* @param \Magento\Framework\Cache\FrontendInterface $cache
174181
* @param \Magento\Framework\View\Model\Layout\Update\Validator $validator
175182
* @param \Psr\Log\LoggerInterface $logger
176-
* @param ReadFactory $readFactory ,
183+
* @param ReadFactory $readFactory
177184
* @param \Magento\Framework\View\Design\ThemeInterface $theme Non-injectable theme instance
178185
* @param string $cacheSuffix
179186
* @param LayoutCacheKeyInterface $layoutCacheKey
187+
* @param SerializerInterface|null $serializer
180188
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
181189
*/
182190
public function __construct(
@@ -191,7 +199,8 @@ public function __construct(
191199
ReadFactory $readFactory,
192200
\Magento\Framework\View\Design\ThemeInterface $theme = null,
193201
$cacheSuffix = '',
194-
LayoutCacheKeyInterface $layoutCacheKey = null
202+
LayoutCacheKeyInterface $layoutCacheKey = null,
203+
SerializerInterface $serializer = null
195204
) {
196205
$this->theme = $theme ?: $design->getDesignTheme();
197206
$this->scope = $scopeResolver->getScope();
@@ -205,6 +214,7 @@ public function __construct(
205214
$this->cacheSuffix = $cacheSuffix;
206215
$this->layoutCacheKey = $layoutCacheKey
207216
?: \Magento\Framework\App\ObjectManager::getInstance()->get(LayoutCacheKeyInterface::class);
217+
$this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class);
208218
}
209219

210220
/**
@@ -437,12 +447,12 @@ public function load($handles = [])
437447

438448
$this->addHandle($handles);
439449

440-
$cacheId = $this->getCacheId();
441-
$cacheIdPageLayout = $cacheId . '_' . self::PAGE_LAYOUT_CACHE_SUFFIX;
450+
$cacheId = $this->getCacheId() . '_' . self::PAGE_LAYOUT_CACHE_SUFFIX;
442451
$result = $this->_loadCache($cacheId);
443-
if ($result) {
444-
$this->addUpdate($result);
445-
$this->pageLayout = $this->_loadCache($cacheIdPageLayout);
452+
if ($result !== false && $result !== null) {
453+
$data = $this->serializer->unserialize($result);
454+
$this->pageLayout = $data["pageLayout"];
455+
$this->addUpdate($data["layout"]);
446456
foreach ($this->getHandles() as $handle) {
447457
$this->allHandles[$handle] = $this->handleProcessed;
448458
}
@@ -455,8 +465,13 @@ public function load($handles = [])
455465

456466
$layout = $this->asString();
457467
$this->_validateMergedLayout($cacheId, $layout);
458-
$this->_saveCache($layout, $cacheId, $this->getHandles());
459-
$this->_saveCache((string)$this->pageLayout, $cacheIdPageLayout, $this->getHandles());
468+
469+
$data = [
470+
"pageLayout" => (string)$this->pageLayout,
471+
"layout" => $layout
472+
];
473+
$this->_saveCache($this->serializer->serialize($data), $cacheId, $this->getHandles());
474+
460475
return $this;
461476
}
462477

lib/internal/Magento/Framework/View/Test/Unit/Model/Layout/MergeTest.php

+43
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,21 @@ class MergeTest extends \PHPUnit\Framework\TestCase
2828
*/
2929
private $scope;
3030

31+
/**
32+
* @var \Magento\Framework\Cache\FrontendInterface|\PHPUnit_Framework_MockObject_MockObject
33+
*/
34+
private $cache;
35+
3136
/**
3237
* @var \Magento\Framework\View\Model\Layout\Update\Validator|\PHPUnit_Framework_MockObject_MockObject
3338
*/
3439
private $layoutValidator;
3540

41+
/**
42+
* @var \Magento\Framework\Serialize\SerializerInterface|\PHPUnit_Framework_MockObject_MockObject
43+
*/
44+
private $serializer;
45+
3646
/**
3747
* @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
3848
*/
@@ -53,10 +63,12 @@ protected function setUp()
5363
$this->objectManagerHelper = new ObjectManager($this);
5464

5565
$this->scope = $this->getMockForAbstractClass(\Magento\Framework\Url\ScopeInterface::class);
66+
$this->cache = $this->getMockForAbstractClass(\Magento\Framework\Cache\FrontendInterface::class);
5667
$this->layoutValidator = $this->getMockBuilder(\Magento\Framework\View\Model\Layout\Update\Validator::class)
5768
->disableOriginalConstructor()
5869
->getMock();
5970
$this->logger = $this->getMockForAbstractClass(\Psr\Log\LoggerInterface::class);
71+
$this->serializer = $this->getMockForAbstractClass(\Magento\Framework\Serialize\SerializerInterface::class);
6072
$this->appState = $this->getMockBuilder(\Magento\Framework\App\State::class)
6173
->disableOriginalConstructor()
6274
->getMock();
@@ -70,10 +82,12 @@ protected function setUp()
7082
\Magento\Framework\View\Model\Layout\Merge::class,
7183
[
7284
'scope' => $this->scope,
85+
'cache' => $this->cache,
7386
'layoutValidator' => $this->layoutValidator,
7487
'logger' => $this->logger,
7588
'appState' => $this->appState,
7689
'layoutCacheKey' => $this->layoutCacheKeyMock,
90+
'serializer' => $this->serializer,
7791
]
7892
);
7993
}
@@ -104,4 +118,33 @@ public function testValidateMergedLayoutThrowsException()
104118

105119
$this->model->load();
106120
}
121+
122+
/**
123+
* Test that merged layout is saved to cache if it wasn't cached before.
124+
*/
125+
public function testSaveToCache()
126+
{
127+
$this->scope->expects($this->once())->method('getId')->willReturn(1);
128+
$this->cache->expects($this->once())->method('save');
129+
130+
$this->model->load();
131+
}
132+
133+
/**
134+
* Test that merged layout is not re-saved to cache when it was loaded from cache.
135+
*/
136+
public function testNoSaveToCacheWhenCachePresent()
137+
{
138+
$cacheValue = [
139+
"pageLayout" => "1column",
140+
"layout" => "<body></body>"
141+
];
142+
143+
$this->scope->expects($this->once())->method('getId')->willReturn(1);
144+
$this->cache->expects($this->once())->method('load')->willReturn(json_encode($cacheValue));
145+
$this->serializer->expects($this->once())->method('unserialize')->willReturn($cacheValue);
146+
$this->cache->expects($this->never())->method('save');
147+
148+
$this->model->load();
149+
}
107150
}

0 commit comments

Comments
 (0)