aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorprovokateurin <kate@provokateurin.de>2025-04-22 08:59:51 +0200
committerprovokateurin <kate@provokateurin.de>2025-04-22 13:09:54 +0200
commit7bd2de338868c4f1fc64aa3355993a185bc7ad84 (patch)
treecf193ba3f980ae121b319bb66020fb6f8653f574
parentfb3f1abec2fd48c18a3153469070784265898d4c (diff)
downloadnextcloud-server-perf/usermountcache/local-cache.tar.gz
nextcloud-server-perf/usermountcache/local-cache.zip
perf(UserMountCache): Use local cache for user mountsperf/usermountcache/local-cache
Signed-off-by: provokateurin <kate@provokateurin.de>
-rw-r--r--lib/private/Files/Config/UserMountCache.php73
-rw-r--r--tests/lib/Files/Config/UserMountCacheTest.php3
2 files changed, 57 insertions, 19 deletions
diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php
index 50893d2ffcd..17c1a3374fa 100644
--- a/lib/private/Files/Config/UserMountCache.php
+++ b/lib/private/Files/Config/UserMountCache.php
@@ -15,9 +15,12 @@ use OCP\Files\Config\ICachedMountFileInfo;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\NotFoundException;
+use OCP\ICache;
+use OCP\ICacheFactory;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
+use phpDocumentor\Reflection\Types\This;
use Psr\Log\LoggerInterface;
/**
@@ -27,9 +30,8 @@ class UserMountCache implements IUserMountCache {
/**
* Cached mount info.
- * @var CappedMemoryCache<ICachedMountInfo[]>
**/
- private CappedMemoryCache $mountsForUsers;
+ private ICache $mountsForUsers;
/**
* fileid => internal path mapping for cached mount info.
* @var CappedMemoryCache<string>
@@ -46,10 +48,11 @@ class UserMountCache implements IUserMountCache {
private IUserManager $userManager,
private LoggerInterface $logger,
private IEventLogger $eventLogger,
+ private ICacheFactory $cacheFactory,
) {
$this->cacheInfoCache = new CappedMemoryCache();
$this->internalPathCache = new CappedMemoryCache();
- $this->mountsForUsers = new CappedMemoryCache();
+ $this->mountsForUsers = $this->cacheFactory->createLocal('user-mounts');
}
public function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null) {
@@ -96,29 +99,30 @@ class UserMountCache implements IUserMountCache {
if ($addedMounts || $removedMounts || $changedMounts) {
$this->connection->beginTransaction();
$userUID = $user->getUID();
+
try {
foreach ($addedMounts as $mount) {
$this->logger->debug("Adding mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
$this->addToCache($mount);
- /** @psalm-suppress InvalidArgument */
- $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ $cachedMounts[$mount->getKey()] = $mount;
}
foreach ($removedMounts as $mount) {
$this->logger->debug("Removing mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
$this->removeFromCache($mount);
- unset($this->mountsForUsers[$userUID][$mount->getKey()]);
+ unset($cachedMounts[$mount->getKey()]);
}
foreach ($changedMounts as $mount) {
$this->logger->debug("Updating mount '{$mount->getKey()}' for user '$userUID'", ['app' => 'files', 'mount_provider' => $mount->getMountProvider()]);
$this->updateCachedMount($mount);
- /** @psalm-suppress InvalidArgument */
- $this->mountsForUsers[$userUID][$mount->getKey()] = $mount;
+ $cachedMounts[$mount->getKey()] = $mount;
}
$this->connection->commit();
} catch (\Throwable $e) {
$this->connection->rollBack();
throw $e;
}
+
+ $this->mountsForUsers->set($userUID, array_map(fn (ICachedMountInfo $cachedMountInfo) => $this->mountInfoToDbRow($cachedMountInfo), $cachedMounts), 60);
}
$this->eventLogger->end('fs:setup:user:register');
}
@@ -220,15 +224,50 @@ class UserMountCache implements IUserMountCache {
}
/**
+ * Converts the cached mount info into an array to be parsed by @see UserMountCache::dbRowToMountInfo
+ *
+ * The 'path' is intentionally omitted to keep the laziness, so any attempt at calling @see UserMountCache::dbRowToMountInfo must pass the $pathCallback parameter.
+ */
+ private function mountInfoToDbRow(ICachedMountInfo $cachedMountInfo): array {
+ return [
+ 'user_id' => $cachedMountInfo->getUser()->getUID(),
+ 'mount_id' => $cachedMountInfo->getMountId(),
+ 'storage_id' => $cachedMountInfo->getStorageId(),
+ 'root_id' => $cachedMountInfo->getRootId(),
+ 'mount_point' => $cachedMountInfo->getMountPoint(),
+ 'mount_provider_class' => $cachedMountInfo->getMountProvider(),
+ ];
+ }
+
+ /**
* @param IUser $user
- * @return ICachedMountInfo[]
+ * @return array<string, ICachedMountInfo>
*/
public function getMountsForUser(IUser $user) {
$userUID = $user->getUID();
if (!$this->userManager->userExists($userUID)) {
return [];
}
- if (!isset($this->mountsForUsers[$userUID])) {
+
+ $rawMounts = $this->mountsForUsers->get($userUID);
+ $mounts = null;
+ if ($rawMounts !== null) {
+ $mounts = [];
+ foreach ($rawMounts as $serializedMount) {
+ try {
+ $mount = $this->dbRowToMountInfo($serializedMount, [$this, 'getInternalPathForMountInfo']);
+ $mounts[$mount->getKey()] = $mount;
+ } catch (\Exception) {
+ // Discard all cached mounts if we fail to parse any of the cached mounts.
+ $mounts = null;
+ break;
+ }
+ }
+ }
+
+ if ($mounts === null) {
+ $mounts = [];
+
$builder = $this->connection->getQueryBuilder();
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'mount_provider_class')
->from('mounts', 'm')
@@ -238,17 +277,15 @@ class UserMountCache implements IUserMountCache {
$rows = $result->fetchAll();
$result->closeCursor();
- /** @var array<string, ICachedMountInfo> $mounts */
- $mounts = [];
foreach ($rows as $row) {
$mount = $this->dbRowToMountInfo($row, [$this, 'getInternalPathForMountInfo']);
- if ($mount !== null) {
- $mounts[$mount->getKey()] = $mount;
- }
+ $mounts[$mount->getKey()] = $mount;
}
- $this->mountsForUsers[$userUID] = $mounts;
+
+ $this->mountsForUsers->set($userUID, array_map(fn (ICachedMountInfo $cachedMountInfo) => $this->mountInfoToDbRow($cachedMountInfo), $mounts), 60);
}
- return $this->mountsForUsers[$userUID];
+
+ return $mounts;
}
public function getInternalPathForMountInfo(CachedMountInfo $info): string {
@@ -447,7 +484,7 @@ class UserMountCache implements IUserMountCache {
public function clear(): void {
$this->cacheInfoCache = new CappedMemoryCache();
- $this->mountsForUsers = new CappedMemoryCache();
+ $this->mountsForUsers = $this->cacheFactory->createLocal('user-mounts');
}
public function getMountForPath(IUser $user, string $path): ICachedMountInfo {
diff --git a/tests/lib/Files/Config/UserMountCacheTest.php b/tests/lib/Files/Config/UserMountCacheTest.php
index 212dd97f9a3..f67db01f16c 100644
--- a/tests/lib/Files/Config/UserMountCacheTest.php
+++ b/tests/lib/Files/Config/UserMountCacheTest.php
@@ -20,6 +20,7 @@ use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUserManager;
+use OCP\Server;
use Psr\Log\LoggerInterface;
use Test\TestCase;
use Test\Util\User\Dummy;
@@ -67,7 +68,7 @@ class UserMountCacheTest extends TestCase {
$userBackend->createUser('u2', '');
$userBackend->createUser('u3', '');
$this->userManager->registerBackend($userBackend);
- $this->cache = new \OC\Files\Config\UserMountCache($this->connection, $this->userManager, $this->createMock(LoggerInterface::class), $this->createMock(IEventLogger::class));
+ $this->cache = new \OC\Files\Config\UserMountCache($this->connection, $this->userManager, $this->createMock(LoggerInterface::class), $this->createMock(IEventLogger::class), Server::get(ICacheFactory::class));
}
protected function tearDown(): void {