Skip to content

Commit d59d106

Browse files
committed
feat(AppConfig): cache app config in local cache if available
Signed-off-by: Ferdinand Thiessen <[email protected]>
1 parent b4abfd4 commit d59d106

File tree

3 files changed

+1760
-1533
lines changed

3 files changed

+1760
-1533
lines changed

lib/private/AppConfig.php

Lines changed: 83 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use OC\Config\ConfigManager;
1616
use OC\Config\PresetManager;
1717
use OCP\Config\Lexicon\Entry;
18-
use OCP\Config\Lexicon\ILexicon;
1918
use OCP\Config\Lexicon\Strictness;
2019
use OCP\Config\ValueType;
2120
use OCP\DB\Exception as DBException;
@@ -24,6 +23,8 @@
2423
use OCP\Exceptions\AppConfigTypeConflictException;
2524
use OCP\Exceptions\AppConfigUnknownKeyException;
2625
use OCP\IAppConfig;
26+
use OCP\ICache;
27+
use OCP\ICacheFactory;
2728
use OCP\IConfig;
2829
use OCP\IDBConnection;
2930
use OCP\Security\ICrypto;
@@ -53,10 +54,12 @@ class AppConfig implements IAppConfig {
5354
private const KEY_MAX_LENGTH = 64;
5455
private const ENCRYPTION_PREFIX = '$AppConfigEncryption$';
5556
private const ENCRYPTION_PREFIX_LENGTH = 21; // strlen(self::ENCRYPTION_PREFIX)
57+
private const LOCAL_CACHE_KEY = 'OC\\AppConfig';
58+
private const LOCAL_CACHE_TTL = 180;
5659

57-
/** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
60+
/** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
5861
private array $fastCache = []; // cache for normal config keys
59-
/** @var array<string, array<string, mixed>> ['app_id' => ['config_key' => 'config_value']] */
62+
/** @var array<string, array<string, string>> ['app_id' => ['config_key' => 'config_value']] */
6063
private array $lazyCache = []; // cache for lazy config keys
6164
/** @var array<string, array<string, int>> ['app_id' => ['config_key' => bitflag]] */
6265
private array $valueTypes = []; // type for all config values
@@ -68,14 +71,23 @@ class AppConfig implements IAppConfig {
6871
/** @var ?array<string, string> */
6972
private ?array $appVersionsCache = null;
7073

74+
/**
75+
* @var null|ICache<array{'fastCache': array<string, array<string, string>>, 'lazyCache': array<string, array<string, string>>|null, 'valueTypes': array<string, array<string, int>>}
76+
*/
77+
private ?ICache $localCache = null;
78+
7179
public function __construct(
7280
protected IDBConnection $connection,
7381
protected IConfig $config,
7482
private readonly ConfigManager $configManager,
7583
private readonly PresetManager $presetManager,
7684
protected LoggerInterface $logger,
7785
protected ICrypto $crypto,
86+
readonly ICacheFactory $cacheFactory,
7887
) {
88+
if ($config->getSystemValueBool('cache__app_config') && $cacheFactory->isLocalCacheAvailable()) {
89+
$this->localCache = $cacheFactory->createLocal();
90+
}
7991
}
8092

8193
/**
@@ -85,7 +97,7 @@ public function __construct(
8597
* @since 7.0.0
8698
*/
8799
public function getApps(): array {
88-
$this->loadConfigAll();
100+
$this->loadConfig(lazy: true);
89101
$apps = array_merge(array_keys($this->fastCache), array_keys($this->lazyCache));
90102
sort($apps);
91103

@@ -103,7 +115,7 @@ public function getApps(): array {
103115
*/
104116
public function getKeys(string $app): array {
105117
$this->assertParams($app);
106-
$this->loadConfigAll($app);
118+
$this->loadConfig($app, true);
107119
$keys = array_merge(array_keys($this->fastCache[$app] ?? []), array_keys($this->lazyCache[$app] ?? []));
108120
sort($keys);
109121

@@ -149,7 +161,7 @@ public function searchKeys(string $app, string $prefix = '', bool $lazy = false)
149161
*/
150162
public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
151163
$this->assertParams($app, $key);
152-
$this->loadConfig($app, $lazy);
164+
$this->loadConfig($app, $lazy ?? true);
153165
$this->matchAndApplyLexiconDefinition($app, $key);
154166

155167
if ($lazy === null) {
@@ -175,7 +187,7 @@ public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
175187
*/
176188
public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
177189
$this->assertParams($app, $key);
178-
$this->loadConfig(null, $lazy);
190+
$this->loadConfig(null, $lazy ?? true);
179191
$this->matchAndApplyLexiconDefinition($app, $key);
180192

181193
if (!isset($this->valueTypes[$app][$key])) {
@@ -227,7 +239,7 @@ public function isLazy(string $app, string $key): bool {
227239
public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
228240
$this->assertParams($app, $prefix);
229241
// if we want to filter values, we need to get sensitivity
230-
$this->loadConfigAll($app);
242+
$this->loadConfig($app, true);
231243
// array_merge() will remove numeric keys (here config keys), so addition arrays instead
232244
$values = $this->formatAppValues($app, ($this->fastCache[$app] ?? []) + ($this->lazyCache[$app] ?? []));
233245
$values = array_filter(
@@ -479,7 +491,7 @@ private function getTypedValue(
479491
return $default;
480492
}
481493

482-
$this->loadConfig($app, $lazy);
494+
$this->loadConfig($app, $lazy ?? true);
483495

484496
/**
485497
* We ignore check if mixed type is requested.
@@ -551,7 +563,7 @@ public function getValueType(string $app, string $key, ?bool $lazy = null): int
551563
}
552564

553565
$this->assertParams($app, $key);
554-
$this->loadConfig($app, $lazy);
566+
$this->loadConfig($app, $lazy ?? true);
555567

556568
if (!isset($this->valueTypes[$app][$key])) {
557569
throw new AppConfigUnknownKeyException('unknown config key');
@@ -788,7 +800,7 @@ private function setTypedValue(
788800
if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) {
789801
return false; // returns false as database is not updated
790802
}
791-
$this->loadConfig(null, $lazy);
803+
$this->loadConfig(null, $lazy ?? true);
792804

793805
$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
794806
$inserted = $refreshCache = false;
@@ -803,7 +815,7 @@ private function setTypedValue(
803815
* no update if key is already known with set lazy status and value is
804816
* not different, unless sensitivity is switched from false to true.
805817
*/
806-
if ($origValue === $this->getTypedValue($app, $key, $value, $lazy, $type)
818+
if ($origValue === $this->getTypedValue($app, $key, $value, $lazy ?? true, $type)
807819
&& (!$sensitive || $this->isSensitive($app, $key, $lazy))) {
808820
return false;
809821
}
@@ -835,7 +847,7 @@ private function setTypedValue(
835847
if (!$inserted) {
836848
$currType = $this->valueTypes[$app][$key] ?? 0;
837849
if ($currType === 0) { // this might happen when switching lazy loading status
838-
$this->loadConfigAll();
850+
$this->loadConfig(lazy: true);
839851
$currType = $this->valueTypes[$app][$key] ?? 0;
840852
}
841853

@@ -856,7 +868,7 @@ private function setTypedValue(
856868
&& ($type | self::VALUE_SENSITIVE) !== ($currType | self::VALUE_SENSITIVE)) {
857869
try {
858870
$currType = $this->convertTypeToString($currType);
859-
$type = $this->convertTypeToString($type);
871+
$this->convertTypeToString($type);
860872
} catch (AppConfigIncorrectTypeException) {
861873
// can be ignored, this was just needed for a better exception message.
862874
}
@@ -895,6 +907,7 @@ private function setTypedValue(
895907
$this->fastCache[$app][$key] = $value;
896908
}
897909
$this->valueTypes[$app][$key] = $type;
910+
$this->updateCache();
898911

899912
return true;
900913
}
@@ -916,7 +929,7 @@ private function setTypedValue(
916929
*/
917930
public function updateType(string $app, string $key, int $type = self::VALUE_MIXED): bool {
918931
$this->assertParams($app, $key);
919-
$this->loadConfigAll();
932+
$this->loadConfig(lazy: true);
920933
$this->matchAndApplyLexiconDefinition($app, $key);
921934
$this->isLazy($app, $key); // confirm key exists
922935

@@ -959,7 +972,7 @@ public function updateType(string $app, string $key, int $type = self::VALUE_MIX
959972
*/
960973
public function updateSensitive(string $app, string $key, bool $sensitive): bool {
961974
$this->assertParams($app, $key);
962-
$this->loadConfigAll();
975+
$this->loadConfig(lazy: true);
963976
$this->matchAndApplyLexiconDefinition($app, $key);
964977

965978
try {
@@ -1019,7 +1032,7 @@ public function updateSensitive(string $app, string $key, bool $sensitive): bool
10191032
*/
10201033
public function updateLazy(string $app, string $key, bool $lazy): bool {
10211034
$this->assertParams($app, $key);
1022-
$this->loadConfigAll();
1035+
$this->loadConfig(lazy: true);
10231036
$this->matchAndApplyLexiconDefinition($app, $key);
10241037

10251038
try {
@@ -1055,7 +1068,7 @@ public function updateLazy(string $app, string $key, bool $lazy): bool {
10551068
*/
10561069
public function getDetails(string $app, string $key): array {
10571070
$this->assertParams($app, $key);
1058-
$this->loadConfigAll();
1071+
$this->loadConfig(lazy: true);
10591072
$this->matchAndApplyLexiconDefinition($app, $key);
10601073
$lazy = $this->isLazy($app, $key);
10611074

@@ -1227,12 +1240,13 @@ public function deleteApp(string $app): void {
12271240
public function clearCache(bool $reload = false): void {
12281241
$this->lazyLoaded = $this->fastLoaded = false;
12291242
$this->lazyCache = $this->fastCache = $this->valueTypes = $this->configLexiconDetails = [];
1243+
$this->localCache?->remove(self::LOCAL_CACHE_KEY);
12301244

12311245
if (!$reload) {
12321246
return;
12331247
}
12341248

1235-
$this->loadConfigAll();
1249+
$this->loadConfig(lazy: true);
12361250
}
12371251

12381252

@@ -1293,51 +1307,68 @@ private function assertParams(string $app = '', string $configKey = '', bool $al
12931307
}
12941308
}
12951309

1296-
private function loadConfigAll(?string $app = null): void {
1297-
$this->loadConfig($app, null);
1298-
}
1299-
13001310
/**
13011311
* Load normal config or config set as lazy loaded
13021312
*
1303-
* @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
1313+
* @param bool $lazy set to TRUE to also load config values set as lazy loaded
13041314
*/
1305-
private function loadConfig(?string $app = null, ?bool $lazy = false): void {
1315+
private function loadConfig(?string $app = null, bool $lazy = false): void {
13061316
if ($this->isLoaded($lazy)) {
13071317
return;
13081318
}
13091319

13101320
// if lazy is null or true, we debug log
1311-
if (($lazy ?? true) !== false && $app !== null) {
1321+
if ($lazy === true && $app !== null) {
13121322
$exception = new \RuntimeException('The loading of lazy AppConfig values have been triggered by app "' . $app . '"');
13131323
$this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
13141324
}
13151325

1316-
$qb = $this->connection->getQueryBuilder();
1317-
$qb->from('appconfig');
1326+
$loadLazyOnly = $lazy && $this->isLoaded(false);
1327+
1328+
/** @var array<mixed> */
1329+
$cacheContent = $this->localCache?->get(self::LOCAL_CACHE_KEY) ?? [];
1330+
$includesLazyValues = !empty($cacheContent) && !empty($cacheContent['lazyCache']);
1331+
if (!empty($cacheContent) && (!$lazy || $includesLazyValues)) {
1332+
$this->valueTypes = $cacheContent['valueTypes'];
1333+
$this->fastCache = $cacheContent['fastCache'];
1334+
$this->fastLoaded = !empty($this->fastCache);
1335+
if ($includesLazyValues) {
1336+
$this->lazyCache = $cacheContent['lazyCache'];
1337+
$this->lazyLoaded = !empty($this->lazyCache);
1338+
}
1339+
return;
1340+
}
13181341

1319-
// we only need value from lazy when loadConfig does not specify it
1320-
$qb->select('appid', 'configkey', 'configvalue', 'type');
1342+
// Otherwise no cache available and we need to fetch from database
1343+
$qb = $this->connection->getQueryBuilder();
1344+
$qb->from('appconfig')
1345+
->select('appid', 'configkey', 'configvalue', 'type');
13211346

1322-
if ($lazy !== null) {
1323-
$qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
1347+
if ($lazy === false) {
1348+
$qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
13241349
} else {
1350+
if ($loadLazyOnly) {
1351+
$qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)));
1352+
}
13251353
$qb->addSelect('lazy');
13261354
}
13271355

13281356
$result = $qb->executeQuery();
13291357
$rows = $result->fetchAll();
13301358
foreach ($rows as $row) {
13311359
// most of the time, 'lazy' is not in the select because its value is already known
1332-
if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
1360+
if ($lazy && $row['lazy'] === 1) {
13331361
$this->lazyCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
13341362
} else {
13351363
$this->fastCache[$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
13361364
}
13371365
$this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
13381366
}
1367+
13391368
$result->closeCursor();
1340-
$this->setAsLoaded($lazy);
1369+
$this->updateCache();
1370+
$this->fastLoaded = true;
1371+
$this->lazyLoaded = $lazy;
13411372
}
13421373

13431374
/**
@@ -1358,37 +1389,14 @@ private function isLoaded(?bool $lazy): bool {
13581389
return $lazy ? $this->lazyLoaded : $this->fastLoaded;
13591390
}
13601391

1361-
/**
1362-
* if $lazy is:
1363-
* - false: set fast config as loaded
1364-
* - true : set lazy config as loaded
1365-
* - null : set both config as loaded
1366-
*
1367-
* @param bool $lazy
1368-
*/
1369-
private function setAsLoaded(?bool $lazy): void {
1370-
if ($lazy === null) {
1371-
$this->fastLoaded = true;
1372-
$this->lazyLoaded = true;
1373-
1374-
return;
1375-
}
1376-
1377-
if ($lazy) {
1378-
$this->lazyLoaded = true;
1379-
} else {
1380-
$this->fastLoaded = true;
1381-
}
1382-
}
1383-
13841392
/**
13851393
* Gets the config value
13861394
*
13871395
* @param string $app app
13881396
* @param string $key key
13891397
* @param string $default = null, default value if the key does not exist
13901398
*
1391-
* @return string the value or $default
1399+
* @return ?string the value or $default
13921400
* @deprecated 29.0.0 use getValue*()
13931401
*
13941402
* This function gets a value from the appconfig table. If the key does
@@ -1421,7 +1429,7 @@ public function setValue($app, $key, $value) {
14211429
* or enabled (lazy=lazy-2)
14221430
*
14231431
* this solution would remove the loading of config values from disabled app
1424-
* unless calling the method {@see loadConfigAll()}
1432+
* unless calling the method.
14251433
*/
14261434
return $this->setTypedValue($app, $key, (string)$value, false, self::VALUE_MIXED);
14271435
}
@@ -1733,7 +1741,7 @@ private function matchAndApplyLexiconDefinition(
17331741
*
17341742
* @return bool TRUE if conflict can be fully ignored, FALSE if action should be not performed
17351743
* @throws AppConfigUnknownKeyException if strictness implies exception
1736-
* @see ILexicon::getStrictness()
1744+
* @see \OCP\Config\Lexicon\ILexicon::getStrictness()
17371745
*/
17381746
private function applyLexiconStrictness(
17391747
?Strictness $strictness,
@@ -1772,8 +1780,9 @@ public function getConfigDetailsFromLexicon(string $appId): array {
17721780
$configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
17731781
foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
17741782
$entries[$configEntry->getKey()] = $configEntry;
1775-
if ($configEntry->getRename() !== null) {
1776-
$aliases[$configEntry->getRename()] = $configEntry->getKey();
1783+
$newName = $configEntry->getRename();
1784+
if ($newName !== null) {
1785+
$aliases[$newName] = $configEntry->getKey();
17771786
}
17781787
}
17791788

@@ -1819,4 +1828,16 @@ public function getAppInstalledVersions(bool $onlyEnabled = false): array {
18191828
}
18201829
return $this->appVersionsCache;
18211830
}
1831+
1832+
private function updateCache(): void {
1833+
$this->localCache?->set(
1834+
self::LOCAL_CACHE_KEY,
1835+
[
1836+
'fastCache' => $this->fastCache,
1837+
'lazyCache' => $this->lazyCache,
1838+
'valueTypes' => $this->valueTypes,
1839+
],
1840+
self::LOCAL_CACHE_TTL,
1841+
);
1842+
}
18221843
}

0 commit comments

Comments
 (0)