diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 013e4639cc..5cadece6ee 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -3468,11 +3468,34 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $document->setAttribute('$databaseId', $database->getId()); $document->setAttribute('$collectionId', $collection->getId()); + static $relatedCollectionsCache = []; + $relationships = \array_filter( $collection->getAttribute('attributes', []), fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP ); + + // First collect all required related collection IDs + $relatedCollectionIds = []; + foreach ($relationships as $relationship) { + $relatedCollectionId = $relationship->getAttribute('relatedCollection'); + $cacheKey = 'database_' . $database->getInternalId() . '_' . $relatedCollectionId; + + if (!isset($relatedCollectionsCache[$cacheKey])) { + $relatedCollectionIds[$cacheKey] = $relatedCollectionId; + } + } + + // Fetch all required collections in a single batch if needed + if (!empty($relatedCollectionIds)) { + foreach ($relatedCollectionIds as $cacheKey => $relatedCollectionId) { + $relatedCollectionsCache[$cacheKey] = Authorization::skip( + fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId) + ); + } + } + // Now process each relationship with cached collection data foreach ($relationships as $relationship) { $related = $document->getAttribute($relationship->getAttribute('key')); @@ -3491,8 +3514,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); - // todo: Use local cache for this getDocument - $relatedCollection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)); + $cacheKey = 'database_' . $database->getInternalId() . '_' . $relatedCollectionId; + $relatedCollection = $relatedCollectionsCache[$cacheKey]; foreach ($relations as $index => $doc) { if ($doc instanceof Document) { diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 583468f6c1..9d226e54de 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1915,7 +1915,17 @@ App::post('/v1/functions/:functionId/executions') throw new Exception($validator->getDescription(), 400); } - $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); + // Cache keys for frequently accessed documents + static $functionCache = []; + static $deploymentCache = []; + static $buildCache = []; + + // Get function document with caching + $cacheKey = $functionId; + if (!isset($functionCache[$cacheKey])) { + $functionCache[$cacheKey] = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); + } + $function = $functionCache[$cacheKey]; $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -1934,7 +1944,13 @@ App::post('/v1/functions/:functionId/executions') throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported'); } - $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); + // Get deployment document with caching + $deploymentId = $function->getAttribute('deployment', ''); + $deploymentCacheKey = $deploymentId; + if (!isset($deploymentCache[$deploymentCacheKey])) { + $deploymentCache[$deploymentCacheKey] = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $deploymentId)); + } + $deployment = $deploymentCache[$deploymentCacheKey]; if ($deployment->getAttribute('resourceId') !== $function->getId()) { throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function'); @@ -1945,7 +1961,12 @@ App::post('/v1/functions/:functionId/executions') } /** Check if build has completed */ - $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + $buildId = $deployment->getAttribute('buildId', ''); + $buildCacheKey = $buildId; + if (!isset($buildCache[$buildCacheKey])) { + $buildCache[$buildCacheKey] = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId)); + } + $build = $buildCache[$buildCacheKey]; if ($build->isEmpty()) { throw new Exception(Exception::BUILD_NOT_FOUND); } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 4a551b7478..cc3456a8e5 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -809,16 +809,38 @@ App::get('/v1/users/:userId/memberships') throw new Exception(Exception::USER_NOT_FOUND); } - $memberships = array_map(function ($membership) use ($dbForProject, $user) { - $team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId')); - + $memberships = $user->getAttribute('memberships', []); + + // Get all team IDs to fetch in a single query + $teamIds = array_map(function ($membership) { + return $membership->getAttribute('teamId'); + }, $memberships); + + // Fetch all teams in a single query if there are any memberships + $teams = []; + if (!empty($teamIds)) { + $teamsDocuments = $dbForProject->find('teams', [ + Query::equal('$id', $teamIds), + ]); + + // Index teams by ID for quick lookup + foreach ($teamsDocuments as $team) { + $teams[$team->getId()] = $team; + } + } + + // Now map memberships with team data + $memberships = array_map(function ($membership) use ($teams, $user) { + $teamId = $membership->getAttribute('teamId'); + $team = $teams[$teamId] ?? null; + $membership - ->setAttribute('teamName', $team->getAttribute('name')) + ->setAttribute('teamName', $team ? $team->getAttribute('name') : '') ->setAttribute('userName', $user->getAttribute('name')) ->setAttribute('userEmail', $user->getAttribute('email')); - + return $membership; - }, $user->getAttribute('memberships', [])); + }, $memberships); $response->dynamic(new Document([ 'memberships' => $memberships,