From 5002afde09856703ede32fc951cf04ca6e2fe772 Mon Sep 17 00:00:00 2001 From: ArnabChatterjee20k Date: Fri, 8 May 2026 12:05:03 +0530 Subject: [PATCH] optimizations fix: update presence handling to include hostname and correct usage trigger parameter improvement: removed an external findOne call to the db and used the timestamps for knowing upsert did an update or a create --- src/Appwrite/Platform/Workers/Deletes.php | 19 ++++++++++++++++--- .../Realtime/Message/Handlers/Presence.php | 15 +++++++++------ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 4e0fc075aa..7c591cd268 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -6,7 +6,10 @@ use Appwrite\Certificates\Adapter as CertificatesAdapter; use Appwrite\Deletes\Identities; use Appwrite\Deletes\Targets; use Appwrite\Event\Delete as DeleteEvent; +use Appwrite\Event\Message\Usage; +use Appwrite\Event\Publisher\Usage as UsagePublisher; use Appwrite\Extend\Exception; +use Appwrite\Usage\Context as UsageContext; use Executor\Executor; use Throwable; use Utopia\Abuse\Adapters\TimeLimit\Database as AbuseDatabase; @@ -68,6 +71,7 @@ class Deletes extends Action ->inject('log') ->inject('queueForDeletes') ->inject('getAudit') + ->inject('publisherForUsage') ->callback($this->action(...)); } @@ -95,6 +99,7 @@ class Deletes extends Action Log $log, DeleteEvent $queueForDeletes, callable $getAudit, + UsagePublisher $publisherForUsage, ): void { $payload = $message->getPayload(); @@ -214,7 +219,7 @@ class Deletes extends Action $this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime); $this->deleteExpiredSessions($project, $getProjectDB); $this->deleteExpiredTransactions($project, $getProjectDB); - $this->deleteExpiredPresences($project, $getProjectDB); + $this->deleteExpiredPresences($project, $getProjectDB, $publisherForUsage); $this->deleteOldDeployments($queueForDeletes, $project, $getProjectDB); break; default: @@ -1707,7 +1712,7 @@ class Deletes extends Action }); } - private function deleteExpiredPresences(Document $project, callable $getProjectDB): void + private function deleteExpiredPresences(Document $project, callable $getProjectDB, UsagePublisher $publisherForUsage): void { $dbForProject = $getProjectDB($project); @@ -1720,7 +1725,7 @@ class Deletes extends Action $now = DateTime::format(new \DateTime()); $oldestAllowed = DateTime::format((new \DateTime())->sub(\DateInterval::createFromDateString('30 days'))); - $dbForProject->deleteDocuments('presenceLogs', [ + $deleted = $dbForProject->deleteDocuments('presenceLogs', [ Query::or([ Query::and([ Query::isNotNull('expiresAt'), @@ -1731,5 +1736,13 @@ class Deletes extends Action ], onError: function (Throwable $th) { // Swallow errors to avoid breaking the cleanup process }); + + if ($deleted > 0) { + $usage = (new UsageContext())->addMetric(METRIC_USERS_PRESENCE, -$deleted); + $publisherForUsage->enqueue(new Usage( + project: $project, + metrics: $usage->getMetrics(), + )); + } } } diff --git a/src/Appwrite/Realtime/Message/Handlers/Presence.php b/src/Appwrite/Realtime/Message/Handlers/Presence.php index fbab6ec70e..0ff7e19328 100644 --- a/src/Appwrite/Realtime/Message/Handlers/Presence.php +++ b/src/Appwrite/Realtime/Message/Handlers/Presence.php @@ -40,7 +40,7 @@ class Presence extends Action /** * @param array|null $permissions - * @param Closure(int, string): void $triggerPresenceUsage + * @param Closure(int, Document): void $triggerPresenceUsage * @param Closure(?Document, User, string, Document): void $triggerPresenceEvent * @return array */ @@ -78,6 +78,9 @@ class Presence extends Action 'userId' => $user->getId(), 'source' => 'realtime', 'status' => $status, + // Pod identity, used by the realtime worker's startup sweep to clean up rows + // orphaned by a previous incarnation of this hostname (i.e. pod crash before onClose ran). + 'hostname' => \gethostname() ?: null, ]; if ($metadata !== null) { $presenceData['metadata'] = $metadata; @@ -92,16 +95,16 @@ class Presence extends Action $presenceId, (string) $user->getSequence(), function () use ($project, $triggerPresenceUsage): void { - $triggerPresenceUsage(1, $project->getId()); + $triggerPresenceUsage(1, $project); }, ); $presence->removeAttribute('hostname'); - $realtime->connections[$connectionId]['presences'] = \array_merge( - $realtime->connections[$connectionId]['presences'] ?? [], - [$presence->getId()], - ); + // Stash the Document keyed by ID so onClose can build delete-event payloads without + // re-reading the row from the DB. hostname is already stripped above so it won't leak + // into the realtime payload sent to subscribers. + $realtime->connections[$connectionId]['presences'][$presence->getId()] = $presence; $triggerPresenceEvent($project, $user, 'presences.[presenceId].upsert', $presence);