Skip to content

Commit d816ce7

Browse files
author
Sergii Kovalenko
authored
Merge pull request #1219 from magento-tango/MAGETWO-69894
[Tango] Configuration values return null when redis reaches max_memory
2 parents 10c6ffa + e773529 commit d816ce7

File tree

7 files changed

+412
-146
lines changed

7 files changed

+412
-146
lines changed

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

Lines changed: 133 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
namespace Magento\Config\App\Config\Type;
77

88
use Magento\Framework\App\Config\ConfigTypeInterface;
9-
use Magento\Framework\DataObject;
9+
use Magento\Framework\App\ObjectManager;
10+
use Magento\Config\App\Config\Type\System\Reader;
1011

1112
/**
12-
* Class process source, cache them and retrieve value by path
13+
* System configuration type
1314
*/
1415
class System implements ConfigTypeInterface
1516
{
@@ -23,9 +24,9 @@ class System implements ConfigTypeInterface
2324
private $source;
2425

2526
/**
26-
* @var DataObject
27+
* @var array
2728
*/
28-
private $data;
29+
private $data = [];
2930

3031
/**
3132
* @var \Magento\Framework\App\Config\Spi\PostProcessorInterface
@@ -65,14 +66,18 @@ class System implements ConfigTypeInterface
6566
private $configType;
6667

6768
/**
68-
* Key name for flag which displays whether configuration is cached or not.
69+
* @var Reader
70+
*/
71+
private $reader;
72+
73+
/**
74+
* List of scopes that were retrieved from configuration storage
6975
*
70-
* Once configuration is cached additional flag pushed to cache storage
71-
* to be able check cache existence without data load.
76+
* Is used to make sure that we don't try to load non-existing configuration scopes.
7277
*
73-
* @var string
78+
* @var array
7479
*/
75-
private $cacheExistenceKey;
80+
private $availableDataScopes = null;
7681

7782
/**
7883
* @param \Magento\Framework\App\Config\ConfigSourceInterface $source
@@ -83,6 +88,7 @@ class System implements ConfigTypeInterface
8388
* @param \Magento\Framework\App\Config\Spi\PreProcessorInterface $preProcessor
8489
* @param int $cachingNestedLevel
8590
* @param string $configType
91+
* @param Reader $reader
8692
*/
8793
public function __construct(
8894
\Magento\Framework\App\Config\ConfigSourceInterface $source,
@@ -92,7 +98,8 @@ public function __construct(
9298
\Magento\Framework\Serialize\SerializerInterface $serializer,
9399
\Magento\Framework\App\Config\Spi\PreProcessorInterface $preProcessor,
94100
$cachingNestedLevel = 1,
95-
$configType = self::CONFIG_TYPE
101+
$configType = self::CONFIG_TYPE,
102+
Reader $reader = null
96103
) {
97104
$this->source = $source;
98105
$this->postProcessor = $postProcessor;
@@ -102,150 +109,174 @@ public function __construct(
102109
$this->fallback = $fallback;
103110
$this->serializer = $serializer;
104111
$this->configType = $configType;
105-
$this->cacheExistenceKey = $this->configType . '_CACHE_EXISTS';
112+
$this->reader = $reader ?: ObjectManager::getInstance()->get(Reader::class);
106113
}
107114

108115
/**
116+
* System configuration is separated by scopes (default, websites, stores). Configuration of a scope is inherited
117+
* from its parent scope (store inherits website).
118+
*
119+
* Because there can be many scopes on single instance of application, the configuration data can be pretty large,
120+
* so it does not make sense to load all of it on every application request. That is why we cache configuration
121+
* data by scope and only load configuration scope when a value from that scope is requested.
122+
*
123+
* Possible path values:
124+
* '' - will return whole system configuration (default scope + all other scopes)
125+
* 'default' - will return all default scope configuration values
126+
* '{scopeType}' - will return data from all scopes of a specified {scopeType} (websites, stores)
127+
* '{scopeType}/{scopeCode}' - will return data for all values of the scope specified by {scopeCode} and scope type
128+
* '{scopeType}/{scopeCode}/some/config/variable' - will return value of the config variable in the specified scope
129+
*
109130
* @inheritdoc
110131
*/
111132
public function get($path = '')
112133
{
113-
if ($path === null) {
114-
$path = '';
134+
if ($path === '') {
135+
$this->data = array_replace_recursive($this->loadAllData(), $this->data);
136+
return $this->data;
115137
}
116-
if ($this->isConfigRead($path)) {
117-
return $this->data->getData($path);
138+
$pathParts = explode('/', $path);
139+
if (count($pathParts) === 1 && $pathParts[0] !== 'default') {
140+
if (!isset($this->data[$pathParts[0]])) {
141+
$data = $this->reader->read();
142+
$this->data = array_replace_recursive($data, $this->data);
143+
}
144+
return $this->data[$pathParts[0]];
118145
}
119-
120-
if (!empty($path) && $this->isCacheExists()) {
121-
return $this->readFromCache($path);
146+
$scopeType = array_shift($pathParts);
147+
if ($scopeType === 'default') {
148+
if (!isset($this->data[$scopeType])) {
149+
$this->data = array_replace_recursive($this->loadDefaultScopeData($scopeType), $this->data);
150+
}
151+
return $this->getDataByPathParts($this->data[$scopeType], $pathParts);
122152
}
123-
124-
$config = $this->loadConfig();
125-
$this->cacheConfig($config);
126-
$this->data = new DataObject($config);
127-
return $this->data->getData($path);
128-
}
129-
130-
/**
131-
* Check whether configuration is cached
132-
*
133-
* In case configuration cache exists method 'load' returns
134-
* value equal to $this->cacheExistenceKey
135-
*
136-
* @return bool
137-
*/
138-
private function isCacheExists()
139-
{
140-
return $this->cache->load($this->cacheExistenceKey) !== false;
153+
$scopeId = array_shift($pathParts);
154+
if (!isset($this->data[$scopeType][$scopeId])) {
155+
$this->data = array_replace_recursive($this->loadScopeData($scopeType, $scopeId), $this->data);
156+
}
157+
return isset($this->data[$scopeType][$scopeId])
158+
? $this->getDataByPathParts($this->data[$scopeType][$scopeId], $pathParts)
159+
: null;
141160
}
142161

143162
/**
144-
* Explode path by '/'(forward slash) separator
145-
*
146-
* In case $path string contains forward slash symbol(/) the $path is exploded and parts array is returned
147-
* In other case empty array is returned
163+
* Load configuration data for all scopes
148164
*
149-
* @param string $path
150165
* @return array
151166
*/
152-
private function getPathParts($path)
167+
private function loadAllData()
153168
{
154-
$pathParts = [];
155-
if (strpos($path, '/') !== false) {
156-
$pathParts = explode('/', $path);
169+
$cachedData = $this->cache->load($this->configType);
170+
if ($cachedData === false) {
171+
$data = $this->reader->read();
172+
} else {
173+
$data = $this->serializer->unserialize($cachedData);
157174
}
158-
return $pathParts;
175+
return $data;
159176
}
160177

161178
/**
162-
* Check whether requested configuration data is read to memory
163-
*
164-
* Because of configuration is cached partially each part can be loaded separately
165-
* Method performs check if corresponding system configuration part is already loaded to memory
166-
* and value can be retrieved directly without cache look up
179+
* Load configuration data for default scope
167180
*
168-
*
169-
* @param string $path
170-
* @return bool
181+
* @param string $scopeType
182+
* @return array
171183
*/
172-
private function isConfigRead($path)
184+
private function loadDefaultScopeData($scopeType)
173185
{
174-
$pathParts = $this->getPathParts($path);
175-
return !empty($pathParts) && isset($this->data[$pathParts[0]][$pathParts[1]]);
186+
$cachedData = $this->cache->load($this->configType . '_' . $scopeType);
187+
if ($cachedData === false) {
188+
$data = $this->reader->read();
189+
$this->cacheData($data);
190+
} else {
191+
$data = [$scopeType => $this->serializer->unserialize($cachedData)];
192+
}
193+
return $data;
176194
}
177195

178196
/**
179-
* Load configuration from all the sources
180-
*
181-
* System configuration is loaded in 3 steps performing consecutive calls to
182-
* Pre Processor, Fallback Processor, Post Processor accordingly
197+
* Load configuration data for a specified scope
183198
*
199+
* @param string $scopeType
200+
* @param string $scopeId
184201
* @return array
185202
*/
186-
private function loadConfig()
203+
private function loadScopeData($scopeType, $scopeId)
187204
{
188-
$data = $this->preProcessor->process($this->source->get());
189-
$this->data = new DataObject($data);
190-
$data = $this->fallback->process($data);
191-
$this->data = new DataObject($data);
192-
193-
return $this->postProcessor->process($data);
205+
$cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId);
206+
if ($cachedData === false) {
207+
if ($this->availableDataScopes === null) {
208+
$cachedScopeData = $this->cache->load($this->configType . '_scopes');
209+
if ($cachedScopeData !== false) {
210+
$this->availableDataScopes = $this->serializer->unserialize($cachedScopeData);
211+
}
212+
}
213+
if (is_array($this->availableDataScopes) && !isset($this->availableDataScopes[$scopeType][$scopeId])) {
214+
return [$scopeType => [$scopeId => []]];
215+
}
216+
$data = $this->reader->read();
217+
$this->cacheData($data);
218+
} else {
219+
$data = [$scopeType => [$scopeId => $this->serializer->unserialize($cachedData)]];
220+
}
221+
return $data;
194222
}
195223

196224
/**
197-
*
198-
* Load configuration and caching it by parts.
199-
*
200-
* To be cached configuration is loaded first.
201-
* Then it is cached by parts to minimize memory usage on load.
202-
* Additional flag cached as well to give possibility check cache existence without data load.
225+
* Cache configuration data.
226+
* Caches data per scope to avoid reading data for all scopes on every request
203227
*
204228
* @param array $data
205229
* @return void
206230
*/
207-
private function cacheConfig($data)
231+
private function cacheData(array $data)
208232
{
209-
foreach ($data as $scope => $scopeData) {
210-
foreach ($scopeData as $key => $config) {
233+
$this->cache->save(
234+
$this->serializer->serialize($data),
235+
$this->configType,
236+
[self::CACHE_TAG]
237+
);
238+
$this->cache->save(
239+
$this->serializer->serialize($data['default']),
240+
$this->configType . '_default',
241+
[self::CACHE_TAG]
242+
);
243+
$scopes = [];
244+
foreach (['websites', 'stores'] as $curScopeType) {
245+
foreach ($data[$curScopeType] as $curScopeId => $curScopeData) {
246+
$scopes[$curScopeType][$curScopeId] = 1;
211247
$this->cache->save(
212-
$this->serializer->serialize($config),
213-
$this->configType . '_' . $scope . $key,
248+
$this->serializer->serialize($curScopeData),
249+
$this->configType . '_' . $curScopeType . '_' . $curScopeId,
214250
[self::CACHE_TAG]
215251
);
216252
}
217253
}
218-
$this->cache->save($this->cacheExistenceKey, $this->cacheExistenceKey, [self::CACHE_TAG]);
254+
$this->cache->save(
255+
$this->serializer->serialize($scopes),
256+
$this->configType . "_scopes",
257+
[self::CACHE_TAG]
258+
);
219259
}
220260

221261
/**
222-
* Read cached configuration
262+
* Walk nested hash map by keys from $pathParts
223263
*
224-
* Read section of system configuration corresponding to requested $path from cache
225-
* Configuration stored to internal property right after load to prevent additional
226-
* requests to cache storage
227-
*
228-
* @param string $path
264+
* @param array $data to walk in
265+
* @param array $pathParts keys path
229266
* @return mixed
230267
*/
231-
private function readFromCache($path)
268+
private function getDataByPathParts($data, $pathParts)
232269
{
233-
if ($this->data === null) {
234-
$this->data = new DataObject();
235-
}
236-
237-
$result = null;
238-
$pathParts = $this->getPathParts($path);
239-
if (!empty($pathParts)) {
240-
$result = $this->cache->load($this->configType . '_' . $pathParts[0] . $pathParts[1]);
241-
if ($result !== false) {
242-
$readData = $this->data->getData();
243-
$readData[$pathParts[0]][$pathParts[1]] = $this->serializer->unserialize($result);
244-
$this->data->setData($readData);
270+
foreach ($pathParts as $key) {
271+
if ((array)$data === $data && isset($data[$key])) {
272+
$data = $data[$key];
273+
} elseif ($data instanceof \Magento\Framework\DataObject) {
274+
$data = $data->getDataByKey($key);
275+
} else {
276+
return null;
245277
}
246278
}
247-
248-
return $this->data->getData($path);
279+
return $data;
249280
}
250281

251282
/**
@@ -259,7 +290,7 @@ private function readFromCache($path)
259290
*/
260291
public function clean()
261292
{
262-
$this->data = null;
293+
$this->data = [];
263294
$this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
264295
}
265296
}

0 commit comments

Comments
 (0)