added caching layer

This commit is contained in:
ArnabChatterjee20k
2026-03-26 18:21:17 +05:30
parent fd2b85a3af
commit 102988ce85
2 changed files with 104 additions and 23 deletions
@@ -39,28 +39,104 @@ class XList extends Action
// just for getting group based presence list based on permissions
public function action(UtopiaResponse $response, Database $dbForProject, Authorization $authorization, Document $user): void
{
$requestingUserId = $user->getId();
$hostname = $dbForProject->getAdapter()->getHostname();
$usersCacheKey = \sprintf(
'%s-cache-%s:%s:%s:collection:presence',
$dbForProject->getCacheName(),
$hostname ?? '',
$dbForProject->getNamespace(),
$dbForProject->getTenant()
);
$presenceLogsCacheKey = \sprintf(
'%s-cache-%s:%s:%s:collection:presenceLogs:user:%s',
$dbForProject->getCacheName(),
$hostname ?? '',
$dbForProject->getNamespace(),
$dbForProject->getTenant(),
$requestingUserId
);
$ttl = 60;
try {
$presenceLogs = [];
$totalStart = microtime(true);
$userStart = microtime(true);
$users = $authorization->skip(fn () => $dbForProject->find('presence', [
Query::limit(10000),
]));
foreach ($users as $user) {
$presenceCacheStart = microtime(true);
$cachedPresenceLogs = $dbForProject->getCache()->load($presenceLogsCacheKey, $ttl);
if($cachedPresenceLogs !== null && $cachedPresenceLogs !== false && \is_array($cachedPresenceLogs)){
$presenceLogs = array_map(fn($doc) => new Document($doc), $cachedPresenceLogs);
$presenceLogEnd = microtime(true) - $presenceCacheStart;
$totalEnd = microtime(true) - $totalStart;
Console::info(sprintf(
"Cache | Requesting User [%s] | [Total][time] %.2f ms | [Users][time] %.2f ms | [PresenceCache][time] %.2f ms\n",
$requestingUserId,
$totalEnd * 1000,
0,
$presenceLogEnd * 1000
));
$response->dynamic(new Document([
'presences' => $presenceLogs,
'total' => \count($presenceLogs),
]), UtopiaResponse::MODEL_PRESENCE_LIST);
return;
}
$cachedUsers = $dbForProject->getCache()->load($usersCacheKey, $ttl);
if($cachedUsers !== null && $cachedUsers !== false && \is_array($cachedUsers)){
$users = array_map(fn($doc) => new Document($doc), $cachedUsers);
}
else{
$users = $authorization->skip(fn () => $dbForProject->find('presence', [
Query::limit(10000),
]));
// saving to cache
$documentsArray = \array_map(function ($doc) {
return $doc->getArrayCopy();
}, $users);
$dbForProject->getCache()->save($usersCacheKey, $documentsArray);
}
$userEnd = microtime(true) - $userStart;
$presenceLogStart = microtime(true);
foreach ($users as $presenceUser) {
// $presenceCacheKey = \sprintf(
// '%s-cache-%s:%s:%s:collection:presenceLogs:user:%s',
// $dbForProject->getCacheName(),
// $hostname ?? '',
// $dbForProject->getNamespace(),
// $dbForProject->getTenant(),
// $presenceUser['userId'] ?? ''
// );
$presenceLog = $dbForProject->findOne('presenceLogs', [
Query::equal('userId', [$user['userId']]),
Query::orderDesc('$updatedAt'),
// Tie-breaker: `$updatedAt` has only millisecond precision.
Query::orderDesc('$id'),
Query::limit(1),
]);
Query::equal('userId', [$presenceUser['userId']]),
Query::orderDesc('$updatedAt'),
// Tie-breaker: `$updatedAt` has only millisecond precision.
Query::orderDesc('$id'),
Query::limit(1),
]);
$presenceLogs[] = $presenceLog;
}
$dbForProject->getCache()->save($presenceLogsCacheKey, $presenceLogs);
$presenceLogEnd = microtime(true) - $presenceLogStart;
$totalEnd = microtime(true) - $totalStart;
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order column '{$e->getAttribute()}' had a null value. Cursor pagination requires all rows order column values are non-null.");
} catch (QueryException) {
throw new Exception(Exception::GENERAL_QUERY_INVALID);
}
Console::info(sprintf(
"Requesting User [%s] | [Total][time] %.2f ms | [Users][time] %.2f ms | [PresenceLogs][time] %.2f ms\n",
$requestingUserId,
$totalEnd * 1000,
$userEnd * 1000,
$presenceLogEnd * 1000
));
$response->dynamic(new Document([
'presences' => $presenceLogs,
'total' => \count($presenceLogs),
+17 -12
View File
@@ -763,16 +763,19 @@ abstract class PresenceBase extends Scope
// Pick 2 viewers from each group: first two indices within group.
$viewerIndices = [];
for ($g = 0; $g < $numGroups; $g++) {
for ($g = 0; $g < $numGroups && \count($viewerIndices) < $viewerCount; $g++) {
$base = $g * $groupSize;
$viewerIndices[] = $base;
if ($viewerCount >= 10) {
// keep exactly 10; should trigger only if group math changes
if (\count($viewerIndices) >= $viewerCount) {
break;
}
$viewerIndices[] = $base + 1;
// Add the next index within the group if it exists.
if ($base + 1 < $totalUsers) {
$viewerIndices[] = $base + 1;
}
}
$viewerIndices = \array_slice($viewerIndices, 0, $viewerCount);
$statusSampleOwnerOffsets = [0, 50, 99];
@@ -828,11 +831,12 @@ abstract class PresenceBase extends Scope
// Allow realtime write to settle.
\usleep(100000);
for ($occurrence = 0; $occurrence < $occurrences; $occurrence++) {
foreach ($viewerIndices as $viewerSampleIndex) {
$viewer = $users[$viewerSampleIndex];
$group = (int) \floor($viewerSampleIndex / $groupSize);
// Fetch listPresence `occurrences` times per viewer, in-order.
foreach ($viewerIndices as $viewerSampleIndex) {
$viewer = $users[$viewerSampleIndex];
$group = (int) \floor($viewerSampleIndex / $groupSize);
for ($occurrence = 0; $occurrence < $occurrences; $occurrence++) {
$benchmark = $this->fetchPresenceListAs($viewer);
$this->assertEquals(200, $benchmark['response']['headers']['status-code']);
$this->assertGreaterThan(0.0, $benchmark['elapsedMs']);
@@ -861,10 +865,11 @@ abstract class PresenceBase extends Scope
}
Console::info(\sprintf(
'[Presence Benchmark F] cycle=%d occurrence=%d listCallsThisOcc=%d',
'[Presence Benchmark F] cycle=%d viewerIndex=%d viewerGroup=%d occurrences=%d',
$cycle,
$occurrence + 1,
$viewerCount
$viewerSampleIndex,
$group,
$occurrences
));
}
}