From 99394e6dfa477974aea3fb146f2b4081c06bdfe9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Wed, 1 Apr 2026 22:20:52 +1300 Subject: [PATCH] (fix): use static cache for collection mapping to avoid per-request queries The mapping query was running once per getDatabasesDB call (once per request). With static cache keyed by database sequence, the query runs once per database per Swoole worker process. Subsequent requests reuse the cached mapping. setCollectionId updates both instance and static caches for newly created collections. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Utopia/Database/Hooks/Metadata.php | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Hooks/Metadata.php b/src/Appwrite/Utopia/Database/Hooks/Metadata.php index 17656d5de6..5e4b735545 100644 --- a/src/Appwrite/Utopia/Database/Hooks/Metadata.php +++ b/src/Appwrite/Utopia/Database/Hooks/Metadata.php @@ -21,11 +21,14 @@ class Metadata implements Decorator /** @var array> */ private array $relationshipCache = []; - /** @var array internal collection name -> user-facing collection ID */ - private array $collectionIdMap = []; + /** @var array> static cache keyed by database sequence */ + private static array $collectionIdMaps = []; - /** @var bool whether the collection map has been loaded */ - private bool $mapLoaded = false; + /** @var bool whether this instance has initialized its map reference */ + private bool $mapInitialized = false; + + /** @var array reference to the map for this database */ + private array $collectionIdMap = []; private int $operations = 0; @@ -43,6 +46,10 @@ class Metadata implements Decorator public function setCollectionId(string $internalName, string $externalId): void { $this->collectionIdMap[$internalName] = $externalId; + $databaseSequence = $this->database->getSequence(); + if (isset(self::$collectionIdMaps[$databaseSequence])) { + self::$collectionIdMaps[$databaseSequence][$internalName] = $externalId; + } } public function decorate(Event $event, Document $collection, Document $document): Document @@ -81,30 +88,42 @@ class Metadata implements Decorator */ private function ensureMapLoaded(): void { - if ($this->mapLoaded) { + if ($this->mapInitialized) { return; } - $this->mapLoaded = true; + $this->mapInitialized = true; $databaseSequence = $this->database->getSequence(); + + if (isset(self::$collectionIdMaps[$databaseSequence])) { + $this->collectionIdMap = self::$collectionIdMaps[$databaseSequence]; + return; + } + $metadataCollection = 'database_' . $databaseSequence; - $collections = $this->authorization->skip( - fn () => $this->dbForProject->silent( - fn () => $this->dbForProject->find($metadataCollection) - ) - ); + try { + $collections = $this->authorization->skip( + fn () => $this->dbForProject->silent( + fn () => $this->dbForProject->find($metadataCollection) + ) + ); - foreach ($collections as $collection) { - $externalId = $collection->getId(); - $sequence = $collection->getSequence(); + foreach ($collections as $collection) { + $externalId = $collection->getId(); + $sequence = $collection->getSequence(); - $relativeKey = 'collection_' . $sequence; - $fullKey = 'database_' . $databaseSequence . '_collection_' . $sequence; + $relativeKey = 'collection_' . $sequence; + $fullKey = 'database_' . $databaseSequence . '_collection_' . $sequence; - $this->collectionIdMap[$relativeKey] = $externalId; - $this->collectionIdMap[$fullKey] = $externalId; + $this->collectionIdMap[$relativeKey] = $externalId; + $this->collectionIdMap[$fullKey] = $externalId; + } + } catch (\Throwable) { + // Silently fail — fall back to internal names } + + self::$collectionIdMaps[$databaseSequence] = $this->collectionIdMap; } private function decorateRelationships(Document $collection, Document $document, int $depth = 0): void