mirror of
https://github.com/nextcloud/server.git
synced 2026-06-12 18:21:40 -04:00
feat(UserConfig): add caching for user config values
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
8a2b02ab9c
commit
f0694dd12c
1 changed files with 64 additions and 23 deletions
|
|
@ -23,6 +23,8 @@ use OCP\Config\ValueType;
|
|||
use OCP\DB\Exception as DBException;
|
||||
use OCP\DB\IResult;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Security\ICrypto;
|
||||
|
|
@ -52,6 +54,8 @@ class UserConfig implements IUserConfig {
|
|||
private const INDEX_MAX_LENGTH = 64;
|
||||
private const ENCRYPTION_PREFIX = '$UserConfigEncryption$';
|
||||
private const ENCRYPTION_PREFIX_LENGTH = 22; // strlen(self::ENCRYPTION_PREFIX)
|
||||
private const LOCAL_CACHE_PREFIX = self::class;
|
||||
private const LOCAL_CACHE_TTL = 5;
|
||||
|
||||
/** @var array<string, array<string, array<string, mixed>>> [ass'user_id' => ['app_id' => ['key' => 'value']]] */
|
||||
private array $fastCache = []; // cache for normal config keys
|
||||
|
|
@ -67,6 +71,8 @@ class UserConfig implements IUserConfig {
|
|||
private array $configLexiconDetails = [];
|
||||
private bool $ignoreLexiconAliases = false;
|
||||
|
||||
private ?ICache $localCache = null;
|
||||
|
||||
public function __construct(
|
||||
protected IDBConnection $connection,
|
||||
protected IConfig $config,
|
||||
|
|
@ -74,7 +80,11 @@ class UserConfig implements IUserConfig {
|
|||
private readonly PresetManager $presetManager,
|
||||
protected LoggerInterface $logger,
|
||||
protected ICrypto $crypto,
|
||||
ICacheFactory $cacheFactory,
|
||||
) {
|
||||
if ($config->getSystemValueBool('cache_user_config', true) && $cacheFactory->isLocalCacheAvailable()) {
|
||||
$this->localCache = $cacheFactory->createLocal(self::LOCAL_CACHE_PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -114,7 +124,7 @@ class UserConfig implements IUserConfig {
|
|||
*/
|
||||
public function getApps(string $userId): array {
|
||||
$this->assertParams($userId, allowEmptyApp: true);
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
$apps = array_merge(array_keys($this->fastCache[$userId] ?? []), array_keys($this->lazyCache[$userId] ?? []));
|
||||
sort($apps);
|
||||
|
||||
|
|
@ -132,7 +142,7 @@ class UserConfig implements IUserConfig {
|
|||
*/
|
||||
public function getKeys(string $userId, string $app): array {
|
||||
$this->assertParams($userId, $app);
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
// array_merge() will remove numeric keys (here config keys), so addition arrays instead
|
||||
$keys = array_map('strval', array_keys(($this->fastCache[$userId][$app] ?? []) + ($this->lazyCache[$userId][$app] ?? [])));
|
||||
sort($keys);
|
||||
|
|
@ -265,7 +275,7 @@ class UserConfig implements IUserConfig {
|
|||
): array {
|
||||
$this->assertParams($userId, $app, $prefix);
|
||||
// if we want to filter values, we need to get sensitivity
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
// array_merge() will remove numeric keys (here config keys), so addition arrays instead
|
||||
$values = array_filter(
|
||||
$this->formatAppValues($userId, $app, ($this->fastCache[$userId][$app] ?? []) + ($this->lazyCache[$userId][$app] ?? []), $filtered),
|
||||
|
|
@ -289,7 +299,7 @@ class UserConfig implements IUserConfig {
|
|||
*/
|
||||
public function getAllValues(string $userId, bool $filtered = false): array {
|
||||
$this->assertParams($userId, allowEmptyApp: true);
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
|
||||
$result = [];
|
||||
foreach ($this->getApps($userId) as $app) {
|
||||
|
|
@ -1148,7 +1158,7 @@ class UserConfig implements IUserConfig {
|
|||
if (!$inserted) {
|
||||
$currType = $this->valueDetails[$userId][$app][$key]['type'] ?? null;
|
||||
if ($currType === null) { // this might happen when switching lazy loading status
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
$currType = $this->valueDetails[$userId][$app][$key]['type'];
|
||||
}
|
||||
|
||||
|
|
@ -1209,6 +1219,7 @@ class UserConfig implements IUserConfig {
|
|||
'type' => $type,
|
||||
'flags' => $flags
|
||||
];
|
||||
$this->updateCache($userId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1231,7 +1242,7 @@ class UserConfig implements IUserConfig {
|
|||
*/
|
||||
public function updateType(string $userId, string $app, string $key, ValueType $type = ValueType::MIXED): bool {
|
||||
$this->assertParams($userId, $app, $key);
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
$this->matchAndApplyLexiconDefinition($userId, $app, $key);
|
||||
$this->isLazy($userId, $app, $key); // confirm key exists
|
||||
|
||||
|
|
@ -1244,6 +1255,7 @@ class UserConfig implements IUserConfig {
|
|||
$update->executeStatement();
|
||||
|
||||
$this->valueDetails[$userId][$app][$key]['type'] = $type;
|
||||
$this->updateCache($userId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1261,7 +1273,7 @@ class UserConfig implements IUserConfig {
|
|||
*/
|
||||
public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool {
|
||||
$this->assertParams($userId, $app, $key);
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
$this->matchAndApplyLexiconDefinition($userId, $app, $key);
|
||||
|
||||
try {
|
||||
|
|
@ -1303,6 +1315,7 @@ class UserConfig implements IUserConfig {
|
|||
$update->executeStatement();
|
||||
|
||||
$this->valueDetails[$userId][$app][$key]['flags'] = $flags;
|
||||
$this->updateCache($userId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1348,7 +1361,7 @@ class UserConfig implements IUserConfig {
|
|||
*/
|
||||
public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool {
|
||||
$this->assertParams($userId, $app, $key);
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
$this->matchAndApplyLexiconDefinition($userId, $app, $key);
|
||||
|
||||
try {
|
||||
|
|
@ -1389,6 +1402,7 @@ class UserConfig implements IUserConfig {
|
|||
$update->executeStatement();
|
||||
|
||||
$this->valueDetails[$userId][$app][$key]['flags'] = $flags;
|
||||
$this->updateCache($userId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1432,7 +1446,7 @@ class UserConfig implements IUserConfig {
|
|||
*/
|
||||
public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool {
|
||||
$this->assertParams($userId, $app, $key);
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
$this->matchAndApplyLexiconDefinition($userId, $app, $key);
|
||||
|
||||
try {
|
||||
|
|
@ -1493,7 +1507,7 @@ class UserConfig implements IUserConfig {
|
|||
*/
|
||||
public function getDetails(string $userId, string $app, string $key): array {
|
||||
$this->assertParams($userId, $app, $key);
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
$this->matchAndApplyLexiconDefinition($userId, $app, $key);
|
||||
|
||||
$lazy = $this->isLazy($userId, $app, $key);
|
||||
|
|
@ -1555,6 +1569,7 @@ class UserConfig implements IUserConfig {
|
|||
unset($this->lazyCache[$userId][$app][$key]);
|
||||
unset($this->fastCache[$userId][$app][$key]);
|
||||
unset($this->valueDetails[$userId][$app][$key]);
|
||||
$this->updateCache($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1618,12 +1633,13 @@ class UserConfig implements IUserConfig {
|
|||
$this->assertParams($userId, allowEmptyApp: true);
|
||||
$this->lazyLoaded[$userId] = $this->fastLoaded[$userId] = false;
|
||||
$this->lazyCache[$userId] = $this->fastCache[$userId] = $this->valueDetails[$userId] = [];
|
||||
$this->localCache?->remove($userId);
|
||||
|
||||
if (!$reload) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->loadConfigAll($userId);
|
||||
$this->loadConfig($userId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1632,6 +1648,7 @@ class UserConfig implements IUserConfig {
|
|||
* @since 31.0.0
|
||||
*/
|
||||
public function clearCacheAll(): void {
|
||||
$this->localCache?->clear();
|
||||
$this->lazyLoaded = $this->fastLoaded = [];
|
||||
$this->lazyCache = $this->fastCache = $this->valueDetails = $this->configLexiconDetails = [];
|
||||
}
|
||||
|
|
@ -1698,40 +1715,51 @@ class UserConfig implements IUserConfig {
|
|||
}
|
||||
}
|
||||
|
||||
private function loadConfigAll(string $userId): void {
|
||||
$this->loadConfig($userId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load normal config or config set as lazy loaded
|
||||
*
|
||||
* @param bool|null $lazy set to TRUE to load config set as lazy loaded, set to NULL to load all config
|
||||
* @param bool $lazy - Set to TRUE to also load config options marked as lazy loaded
|
||||
*/
|
||||
private function loadConfig(string $userId, ?bool $lazy = false): void {
|
||||
private function loadConfig(string $userId, bool $lazy = false): void {
|
||||
if ($this->isLoaded($userId, $lazy)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (($lazy ?? true) !== false) { // if lazy is null or true, we debug log
|
||||
if ($lazy === true) {
|
||||
$this->logger->debug('The loading of lazy UserConfig values have been requested', ['exception' => new \RuntimeException('ignorable exception')]);
|
||||
}
|
||||
|
||||
/** @var array<mixed> */
|
||||
$cacheContent = $this->localCache?->get($userId) ?? [];
|
||||
$includesLazyValues = !empty($cacheContent) && !empty($cacheContent['lazyCache']);
|
||||
if (!empty($cacheContent) && (!$lazy || $includesLazyValues)) {
|
||||
$this->valueDetails[$userId] = $cacheContent['valueDetails'];
|
||||
$this->fastCache[$userId] = $cacheContent['fastCache'];
|
||||
$this->fastLoaded[$userId] = true;
|
||||
if ($includesLazyValues) {
|
||||
$this->lazyCache[$userId] = $cacheContent['lazyCache'];
|
||||
$this->lazyLoaded[$userId] = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->from('preferences');
|
||||
$qb->select('appid', 'configkey', 'configvalue', 'type', 'flags');
|
||||
$qb->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId)));
|
||||
|
||||
// we only need value from lazy when loadConfig does not specify it
|
||||
if ($lazy !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
|
||||
} else {
|
||||
if ($lazy) {
|
||||
$qb->addSelect('lazy');
|
||||
}
|
||||
if ($lazy === false || $this->isLoaded($userId, false)) {
|
||||
// we only filter by lazy if not requested or if we already have the non-lazy
|
||||
$qb->andWhere($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
|
||||
$result = $qb->executeQuery();
|
||||
$rows = $result->fetchAll();
|
||||
foreach ($rows as $row) {
|
||||
if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
|
||||
if ((($row['lazy'] ?? $lazy) ? 1 : 0) === 1) {
|
||||
$this->lazyCache[$userId][$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
|
||||
} else {
|
||||
$this->fastCache[$userId][$row['appid']][$row['configkey']] = $row['configvalue'] ?? '';
|
||||
|
|
@ -1740,6 +1768,7 @@ class UserConfig implements IUserConfig {
|
|||
}
|
||||
$result->closeCursor();
|
||||
$this->setAsLoaded($userId, $lazy);
|
||||
$this->updateCache($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2036,4 +2065,16 @@ class UserConfig implements IUserConfig {
|
|||
public function ignoreLexiconAliases(bool $ignore): void {
|
||||
$this->ignoreLexiconAliases = $ignore;
|
||||
}
|
||||
|
||||
private function updateCache(string $userId): void {
|
||||
$this->localCache?->set(
|
||||
$userId,
|
||||
[
|
||||
'fastCache' => $this->fastCache[$userId],
|
||||
'lazyCache' => $this->lazyCache[$userId],
|
||||
'valueDetails' => $this->valueDetails[$userId],
|
||||
],
|
||||
self::LOCAL_CACHE_TTL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue