Skip to content

Commit f442fa9

Browse files
authored
Merge pull request #5605 from magento-performance/MC-33363
[2.4] Second part delivery of Redis improvement delivery - MC-33363
2 parents bda0636 + 98743b5 commit f442fa9

File tree

11 files changed

+161
-34
lines changed

11 files changed

+161
-34
lines changed

app/code/Magento/Catalog/Model/Product.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements
7373
const STORE_ID = 'store_id';
7474

7575
/**
76-
* @var string
76+
* @var string|bool
7777
*/
78-
protected $_cacheTag = self::CACHE_TAG;
78+
protected $_cacheTag = false;
7979

8080
/**
8181
* @var string
@@ -878,7 +878,6 @@ public function getAttributes($groupId = null, $skipSuper = false)
878878
*/
879879
public function beforeSave()
880880
{
881-
$this->cleanCache();
882881
$this->setTypeHasOptions(false);
883882
$this->setTypeHasRequiredOptions(false);
884883
$this->setHasOptions(false);

app/code/Magento/Config/App/Config/Type/System.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
use Magento\Store\Model\Config\Processor\Fallback;
2121
use Magento\Framework\Encryption\Encryptor;
2222
use Magento\Store\Model\ScopeInterface as StoreScope;
23+
use Magento\Framework\App\Cache\StateInterface;
24+
use Magento\Framework\App\Cache\Type\Config;
2325

2426
/**
2527
* System configuration type
@@ -98,6 +100,12 @@ class System implements ConfigTypeInterface
98100
private $lockQuery;
99101

100102
/**
103+
* @var StateInterface
104+
*/
105+
private $cacheState;
106+
107+
/**
108+
* System constructor.
101109
* @param ConfigSourceInterface $source
102110
* @param PostProcessorInterface $postProcessor
103111
* @param Fallback $fallback
@@ -110,6 +118,7 @@ class System implements ConfigTypeInterface
110118
* @param Encryptor|null $encryptor
111119
* @param LockManagerInterface|null $locker
112120
* @param LockGuardedCacheLoader|null $lockQuery
121+
* @param StateInterface|null $cacheState
113122
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
114123
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
115124
*/
@@ -125,7 +134,8 @@ public function __construct(
125134
Reader $reader = null,
126135
Encryptor $encryptor = null,
127136
LockManagerInterface $locker = null,
128-
LockGuardedCacheLoader $lockQuery = null
137+
LockGuardedCacheLoader $lockQuery = null,
138+
StateInterface $cacheState = null
129139
) {
130140
$this->postProcessor = $postProcessor;
131141
$this->cache = $cache;
@@ -136,6 +146,8 @@ public function __construct(
136146
?: ObjectManager::getInstance()->get(Encryptor::class);
137147
$this->lockQuery = $lockQuery
138148
?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class);
149+
$this->cacheState = $cacheState
150+
?: ObjectManager::getInstance()->get(StateInterface::class);
139151
}
140152

141153
/**
@@ -220,6 +232,10 @@ private function getWithParts($path)
220232
*/
221233
private function loadAllData()
222234
{
235+
if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) {
236+
return $this->readData();
237+
}
238+
223239
$loadAction = function () {
224240
$cachedData = $this->cache->load($this->configType);
225241
$data = false;
@@ -245,6 +261,10 @@ private function loadAllData()
245261
*/
246262
private function loadDefaultScopeData($scopeType)
247263
{
264+
if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) {
265+
return $this->readData();
266+
}
267+
248268
$loadAction = function () use ($scopeType) {
249269
$cachedData = $this->cache->load($this->configType . '_' . $scopeType);
250270
$scopeData = false;
@@ -271,6 +291,10 @@ private function loadDefaultScopeData($scopeType)
271291
*/
272292
private function loadScopeData($scopeType, $scopeId)
273293
{
294+
if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) {
295+
return $this->readData();
296+
}
297+
274298
$loadAction = function () use ($scopeType, $scopeId) {
275299
$cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId);
276300
$scopeData = false;
@@ -393,6 +417,10 @@ public function clean()
393417
$this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
394418
};
395419

420+
if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) {
421+
return $cleanAction();
422+
}
423+
396424
$this->lockQuery->lockedCleanData(
397425
self::$lockName,
398426
$cleanAction

app/code/Magento/Config/etc/di.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,6 @@
9797
<virtualType name="systemConfigQueryLocker" type="Magento\Framework\Cache\LockGuardedCacheLoader">
9898
<arguments>
9999
<argument name="locker" xsi:type="object">Magento\Framework\Lock\Backend\Cache</argument>
100-
<argument name="lockTimeout" xsi:type="number">42000</argument>
101-
<argument name="delayTimeout" xsi:type="number">100</argument>
102100
</arguments>
103101
</virtualType>
104102

app/etc/di.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,8 +1787,6 @@
17871787
<type name="Magento\Framework\Cache\LockGuardedCacheLoader">
17881788
<arguments>
17891789
<argument name="locker" xsi:type="object">Magento\Framework\Lock\Backend\Cache</argument>
1790-
<argument name="lockTimeout" xsi:type="number">10000</argument>
1791-
<argument name="delayTimeout" xsi:type="number">20</argument>
17921790
</arguments>
17931791
</type>
17941792
<preference for="Magento\Framework\HTTP\AsyncClientInterface" type="Magento\Framework\HTTP\AsyncClient\GuzzleAsyncClient" />

lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,31 @@ class LockGuardedCacheLoader
4848
private $loadTimeout;
4949

5050
/**
51-
* LockGuardedCacheLoader constructor.
51+
* Minimal delay timeout in ms.
52+
*
53+
* @var int
54+
*/
55+
private $minimalDelayTimeout;
56+
57+
/**
5258
* @param LockManagerInterface $locker
5359
* @param int $lockTimeout
5460
* @param int $delayTimeout
5561
* @param int $loadTimeout
62+
* @param int $minimalDelayTimeout
5663
*/
5764
public function __construct(
5865
LockManagerInterface $locker,
5966
int $lockTimeout = 10000,
6067
int $delayTimeout = 20,
61-
int $loadTimeout = 10000
68+
int $loadTimeout = 10000,
69+
int $minimalDelayTimeout = 5
6270
) {
6371
$this->locker = $locker;
6472
$this->lockTimeout = $lockTimeout;
6573
$this->delayTimeout = $delayTimeout;
6674
$this->loadTimeout = $loadTimeout;
75+
$this->minimalDelayTimeout = $minimalDelayTimeout;
6776
}
6877

6978
/**
@@ -82,7 +91,7 @@ public function lockedLoadData(
8291
callable $dataSaver
8392
) {
8493
$cachedData = $dataLoader(); //optimistic read
85-
$deadline = microtime(true) + $this->loadTimeout;
94+
$deadline = microtime(true) + $this->loadTimeout / 100;
8695

8796
while ($cachedData === false) {
8897
if ($deadline <= microtime(true)) {
@@ -100,7 +109,7 @@ public function lockedLoadData(
100109
}
101110

102111
if ($cachedData === false) {
103-
usleep($this->delayTimeout * 1000);
112+
usleep($this->getLookupTimeout() * 1000);
104113
$cachedData = $dataLoader();
105114
}
106115
}
@@ -118,14 +127,21 @@ public function lockedLoadData(
118127
public function lockedCleanData(string $lockName, callable $dataCleaner)
119128
{
120129
while ($this->locker->isLocked($lockName)) {
121-
usleep($this->delayTimeout * 1000);
122-
}
123-
try {
124-
if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) {
125-
$dataCleaner();
126-
}
127-
} finally {
128-
$this->locker->unlock($lockName);
130+
usleep($this->getLookupTimeout() * 1000);
129131
}
132+
133+
$dataCleaner();
134+
}
135+
136+
/**
137+
* Delay will be applied as rand($minimalDelayTimeout, $delayTimeout).
138+
* This helps to desynchronize multiple clients trying
139+
* to acquire the lock for the same resource at the same time
140+
*
141+
* @return int
142+
*/
143+
private function getLookupTimeout()
144+
{
145+
return rand($this->minimalDelayTimeout, $this->delayTimeout);
130146
}
131147
}

lib/internal/Magento/Framework/Lock/Backend/Cache.php

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,69 @@ class Cache implements \Magento\Framework\Lock\LockManagerInterface
2424
*/
2525
private $cache;
2626

27+
/**
28+
* Sign for locks, helps to avoid removing a lock that was created by another client
29+
*
30+
* @string
31+
*/
32+
private $lockSign;
33+
2734
/**
2835
* @param FrontendInterface $cache
2936
*/
3037
public function __construct(FrontendInterface $cache)
3138
{
3239
$this->cache = $cache;
40+
$this->lockSign = $this->generateLockSign();
3341
}
3442

3543
/**
3644
* @inheritdoc
3745
*/
3846
public function lock(string $name, int $timeout = -1): bool
3947
{
40-
if ((bool)$this->cache->test($this->getIdentifier($name))) {
48+
if (empty($this->lockSign)) {
49+
$this->lockSign = $this->generateLockSign();
50+
}
51+
52+
$data = $this->cache->load($this->getIdentifier($name));
53+
54+
if (false !== $data) {
4155
return false;
4256
}
4357

44-
return $this->cache->save('1', $this->getIdentifier($name), [], $timeout);
58+
$this->cache->save($this->lockSign, $this->getIdentifier($name), [], $timeout * 100);
59+
60+
$data = $this->cache->load($this->getIdentifier($name));
61+
62+
if ($data === $this->lockSign) {
63+
return true;
64+
}
65+
66+
return false;
4567
}
4668

4769
/**
4870
* @inheritdoc
4971
*/
5072
public function unlock(string $name): bool
5173
{
52-
return (bool)$this->cache->remove($this->getIdentifier($name));
74+
if (empty($this->lockSign)) {
75+
return false;
76+
}
77+
78+
$data = $this->cache->load($this->getIdentifier($name));
79+
80+
if (false === $data) {
81+
return false;
82+
}
83+
84+
$removeResult = false;
85+
if ($data === $this->lockSign) {
86+
$removeResult = (bool)$this->cache->remove($this->getIdentifier($name));
87+
}
88+
89+
return $removeResult;
5390
}
5491

5592
/**
@@ -70,4 +107,27 @@ private function getIdentifier(string $cacheIdentifier): string
70107
{
71108
return self::LOCK_PREFIX . $cacheIdentifier;
72109
}
110+
111+
/**
112+
* Function that generates lock sign that helps to avoid removing a lock that was created by another client.
113+
*
114+
* @return string
115+
*/
116+
private function generateLockSign()
117+
{
118+
$sign = implode(
119+
'-',
120+
[
121+
\getmypid(), \crc32(\gethostname())
122+
]
123+
);
124+
125+
try {
126+
$sign .= '-' . \bin2hex(\random_bytes(4));
127+
} catch (\Exception $e) {
128+
$sign .= '-' . \uniqid('-uniqid-');
129+
}
130+
131+
return $sign;
132+
}
73133
}

lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ public function testUnlock(): void
5353
{
5454
$identifier = 'lock_name';
5555

56+
$closure = \Closure::bind(function ($cacheInstance) {
57+
return $cacheInstance->lockSign;
58+
}, null, $this->cache);
59+
$lockSign = $closure($this->cache);
60+
61+
$this->frontendCacheMock
62+
->expects($this->once())->method('load')
63+
->with(self::LOCK_PREFIX . $identifier)
64+
->willReturn($lockSign);
65+
5666
$this->frontendCacheMock
5767
->expects($this->once())
5868
->method('remove')
@@ -61,4 +71,22 @@ public function testUnlock(): void
6171

6272
$this->assertEquals(true, $this->cache->unlock($identifier));
6373
}
74+
75+
/**
76+
* Verify that lock will no be released without sign matches.
77+
* Sign generates in Cache class constructor.
78+
*
79+
* @return void
80+
*/
81+
public function testUnlockWithAnotherSign(): void
82+
{
83+
$identifier = 'lock_name';
84+
85+
$this->frontendCacheMock
86+
->expects($this->once())->method('load')
87+
->with(self::LOCK_PREFIX . $identifier)
88+
->willReturn(\uniqid('some_rand-'));
89+
90+
$this->assertEquals(false, $this->cache->unlock($identifier));
91+
}
6492
}

setup/src/Magento/Setup/Model/ConfigOptionsList/Cache.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
class Cache implements ConfigOptionsListInterface
2121
{
2222
const INPUT_VALUE_CACHE_REDIS = 'redis';
23-
const CONFIG_VALUE_CACHE_REDIS = 'Cm_Cache_Backend_Redis';
23+
const CONFIG_VALUE_CACHE_REDIS = '\\Magento\\Framework\\Cache\\Backend\Redis';
2424

2525
const INPUT_KEY_CACHE_BACKEND = 'cache-backend';
2626
const INPUT_KEY_CACHE_BACKEND_REDIS_SERVER = 'cache-backend-redis-server';
@@ -233,7 +233,7 @@ private function validateRedisConfig(array $options, DeploymentConfig $deploymen
233233
self::CONFIG_PATH_CACHE_BACKEND_DATABASE,
234234
$this->getDefaultConfigValue(self::INPUT_KEY_CACHE_BACKEND_REDIS_DATABASE)
235235
);
236-
236+
237237
$config['password'] = isset($options[self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD])
238238
? $options[self::INPUT_KEY_CACHE_BACKEND_REDIS_PASSWORD]
239239
: $deploymentConfig->get(
@@ -282,6 +282,6 @@ private function getDefaultConfigValue($inputKey)
282282
*/
283283
private function generateCachePrefix(): string
284284
{
285-
return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_';
285+
return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_';
286286
}
287287
}

setup/src/Magento/Setup/Model/ConfigOptionsList/PageCache.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
class PageCache implements ConfigOptionsListInterface
2121
{
2222
const INPUT_VALUE_PAGE_CACHE_REDIS = 'redis';
23-
const CONFIG_VALUE_PAGE_CACHE_REDIS = 'Cm_Cache_Backend_Redis';
23+
const CONFIG_VALUE_PAGE_CACHE_REDIS = '\\Magento\\Framework\\Cache\\Backend\Redis';
2424

2525
const INPUT_KEY_PAGE_CACHE_BACKEND = 'page-cache';
2626
const INPUT_KEY_PAGE_CACHE_BACKEND_REDIS_SERVER = 'page-cache-redis-server';
@@ -284,6 +284,6 @@ private function getDefaultConfigValue($inputKey)
284284
*/
285285
private function generateCachePrefix(): string
286286
{
287-
return substr(\md5(dirname(__DIR__, 6)), 0, 3) . '_';
287+
return substr(\hash('sha256', dirname(__DIR__, 6)), 0, 3) . '_';
288288
}
289289
}

0 commit comments

Comments
 (0)