1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-20 04:20:55 +01:00

Cache user notification and message counts

Summary:
Ref T4103. Ref T10078. This puts a user cache in front of notification and message counts.

This reduces the number of queries issued on every page by 4 (2x building the menu, 2x building Quicksand data).

Also fixes some minor issues:

  - Daemons could choke on sending mail in the user's translation.
  - No-op object updates could fail in the daemons.
  - Questionable data access pattern in the file query coming out of the profile file cache.

Test Plan:
  - Sent myself notifications. Saw count go up.
  - Cleared them by visiting objects and clearing all notifications. Saw count go down.
  - Sent myself messages. Saw count go up.
  - Cleared them by visiting threads. Saw count go down.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4103, T10078

Differential Revision: https://secure.phabricator.com/D16041
This commit is contained in:
epriestley 2016-06-04 18:19:16 -07:00
parent 6f1053c206
commit c1331bcb7b
16 changed files with 174 additions and 39 deletions

View file

@ -3640,6 +3640,8 @@ phutil_register_library_map(array(
'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php', 'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php',
'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php', 'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.php',
'PhabricatorUserLogView' => 'applications/people/view/PhabricatorUserLogView.php', 'PhabricatorUserLogView' => 'applications/people/view/PhabricatorUserLogView.php',
'PhabricatorUserMessageCountCacheType' => 'applications/people/cache/PhabricatorUserMessageCountCacheType.php',
'PhabricatorUserNotificationCountCacheType' => 'applications/people/cache/PhabricatorUserNotificationCountCacheType.php',
'PhabricatorUserPHIDResolver' => 'applications/phid/resolver/PhabricatorUserPHIDResolver.php', 'PhabricatorUserPHIDResolver' => 'applications/phid/resolver/PhabricatorUserPHIDResolver.php',
'PhabricatorUserPreferences' => 'applications/settings/storage/PhabricatorUserPreferences.php', 'PhabricatorUserPreferences' => 'applications/settings/storage/PhabricatorUserPreferences.php',
'PhabricatorUserPreferencesCacheType' => 'applications/people/cache/PhabricatorUserPreferencesCacheType.php', 'PhabricatorUserPreferencesCacheType' => 'applications/people/cache/PhabricatorUserPreferencesCacheType.php',
@ -8451,6 +8453,8 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface', 'PhabricatorPolicyInterface',
), ),
'PhabricatorUserLogView' => 'AphrontView', 'PhabricatorUserLogView' => 'AphrontView',
'PhabricatorUserMessageCountCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserNotificationCountCacheType' => 'PhabricatorUserCacheType',
'PhabricatorUserPHIDResolver' => 'PhabricatorPHIDResolver', 'PhabricatorUserPHIDResolver' => 'PhabricatorPHIDResolver',
'PhabricatorUserPreferences' => array( 'PhabricatorUserPreferences' => array(
'PhabricatorUserDAO', 'PhabricatorUserDAO',

View file

@ -46,17 +46,15 @@ final class AphlictDropdownDataQuery extends Phobject {
$is_c_installed = PhabricatorApplication::isClassInstalledForViewer( $is_c_installed = PhabricatorApplication::isClassInstalledForViewer(
$conpherence_app, $conpherence_app,
$viewer); $viewer);
if ($is_c_installed) {
$raw_message_count_number = $viewer->getUnreadMessageCount();
$message_count_number = $this->formatNumber($raw_message_count_number);
} else {
$raw_message_count_number = null; $raw_message_count_number = null;
$message_count_number = null; $message_count_number = null;
if ($is_c_installed) {
$unread_status = ConpherenceParticipationStatus::BEHIND;
$unread = id(new ConpherenceParticipantCountQuery())
->withParticipantPHIDs(array($viewer->getPHID()))
->withParticipationStatus($unread_status)
->execute();
$raw_message_count_number = idx($unread, $viewer->getPHID(), 0);
$message_count_number = $this->formatNumber($raw_message_count_number);
} }
$conpherence_data = array( $conpherence_data = array(
'isInstalled' => $is_c_installed, 'isInstalled' => $is_c_installed,
'countType' => 'messages', 'countType' => 'messages',
@ -69,15 +67,15 @@ final class AphlictDropdownDataQuery extends Phobject {
$is_n_installed = PhabricatorApplication::isClassInstalledForViewer( $is_n_installed = PhabricatorApplication::isClassInstalledForViewer(
$notification_app, $notification_app,
$viewer); $viewer);
$notification_count_number = null;
$raw_notification_count_number = null;
if ($is_n_installed) { if ($is_n_installed) {
$raw_notification_count_number = $raw_notification_count_number = $viewer->getUnreadNotificationCount();
id(new PhabricatorFeedStoryNotification())
->countUnread($viewer);
$notification_count_number = $this->formatNumber( $notification_count_number = $this->formatNumber(
$raw_notification_count_number); $raw_notification_count_number);
} else {
$notification_count_number = null;
$raw_notification_count_number = null;
} }
$notification_data = array( $notification_data = array(
'isInstalled' => $is_n_installed, 'isInstalled' => $is_n_installed,
'countType' => 'notifications', 'countType' => 'notifications',

View file

@ -68,10 +68,13 @@ final class ConpherenceViewController extends
$latest_transaction = head($transactions); $latest_transaction = head($transactions);
$participant = $conpherence->getParticipantIfExists($user->getPHID()); $participant = $conpherence->getParticipantIfExists($user->getPHID());
if ($participant) { if ($participant) {
if (!$participant->isUpToDate($conpherence)) {
$write_guard = AphrontWriteGuard::beginScopedUnguardedWrites(); $write_guard = AphrontWriteGuard::beginScopedUnguardedWrites();
$participant->markUpToDate($conpherence, $latest_transaction); $participant->markUpToDate($conpherence, $latest_transaction);
$user->clearCacheData(PhabricatorUserMessageCountCacheType::KEY_COUNT);
unset($write_guard); unset($write_guard);
} }
}
$data = ConpherenceTransactionRenderer::renderTransactions( $data = ConpherenceTransactionRenderer::renderTransactions(
$user, $user,

View file

@ -422,6 +422,10 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
$participant->save(); $participant->save();
} }
PhabricatorUserCache::clearCaches(
PhabricatorUserMessageCountCacheType::KEY_COUNT,
array_keys($participants));
if ($xactions) { if ($xactions) {
$data = array( $data = array(
'type' => 'message', 'type' => 'message',

View file

@ -47,11 +47,16 @@ final class ConpherenceParticipant extends ConpherenceDAO {
$this->setBehindTransactionPHID($xaction->getPHID()); $this->setBehindTransactionPHID($xaction->getPHID());
$this->setSeenMessageCount($conpherence->getMessageCount()); $this->setSeenMessageCount($conpherence->getMessageCount());
$this->save(); $this->save();
PhabricatorUserCache::clearCache(
PhabricatorUserMessageCountCacheType::KEY_COUNT,
$this->getParticipantPHID());
} }
return $this; return $this;
} }
private function isUpToDate(ConpherenceThread $conpherence) { public function isUpToDate(ConpherenceThread $conpherence) {
return return
($this->getSeenMessageCount() == $conpherence->getMessageCount()) ($this->getSeenMessageCount() == $conpherence->getMessageCount())
&& &&

View file

@ -159,7 +159,8 @@ final class PhabricatorFeedStoryPublisher extends Phobject {
$will_receive_mail = array_fill_keys($this->mailRecipientPHIDs, true); $will_receive_mail = array_fill_keys($this->mailRecipientPHIDs, true);
foreach (array_unique($subscribed_phids) as $user_phid) { $user_phids = array_unique($subscribed_phids);
foreach ($user_phids as $user_phid) {
if (isset($will_receive_mail[$user_phid])) { if (isset($will_receive_mail[$user_phid])) {
$mark_read = 1; $mark_read = 1;
} else { } else {
@ -184,6 +185,10 @@ final class PhabricatorFeedStoryPublisher extends Phobject {
$notif->getTableName(), $notif->getTableName(),
implode(', ', $sql)); implode(', ', $sql));
} }
PhabricatorUserCache::clearCaches(
PhabricatorUserNotificationCountCacheType::KEY_COUNT,
$user_phids);
} }
private function sendNotification($chrono_key, array $subscribed_phids) { private function sendNotification($chrono_key, array $subscribed_phids) {

View file

@ -134,6 +134,9 @@ final class PhabricatorFileQuery
return $files; return $files;
} }
$viewer = $this->getViewer();
$is_omnipotent = $viewer->isOmnipotent();
// We need to load attached objects to perform policy checks for files. // We need to load attached objects to perform policy checks for files.
// First, load the edges. // First, load the edges.
@ -156,6 +159,13 @@ final class PhabricatorFileQuery
continue; continue;
} }
if ($is_omnipotent) {
// If the viewer is omnipotent, we don't need to load the associated
// objects either since they can certainly see the object. Skipping
// this can improve performance and prevent cycles.
continue;
}
foreach ($phids as $phid) { foreach ($phids as $phid) {
$object_phids[$phid] = true; $object_phids[$phid] = true;
} }

View file

@ -18,6 +18,10 @@ final class PhabricatorNotificationClearController
$viewer->getPHID(), $viewer->getPHID(),
$chrono_key); $chrono_key);
PhabricatorUserCache::clearCache(
PhabricatorUserNotificationCountCacheType::KEY_COUNT,
$viewer->getPHID());
return id(new AphrontReloadResponse()) return id(new AphrontReloadResponse())
->setURI('/notification/'); ->setURI('/notification/');
} }

View file

@ -71,8 +71,7 @@ final class PhabricatorNotificationPanelController
$content, $content,
$connection_ui); $connection_ui);
$unread_count = id(new PhabricatorFeedStoryNotification()) $unread_count = $viewer->getUnreadNotificationCount();
->countUnread($viewer);
$json = array( $json = array(
'content' => $content, 'content' => $content,

View file

@ -60,20 +60,10 @@ final class PhabricatorFeedStoryNotification extends PhabricatorFeedDAO {
$object_phid); $object_phid);
unset($unguarded); unset($unguarded);
}
public function countUnread(PhabricatorUser $user) { $count_key = PhabricatorUserNotificationCountCacheType::KEY_COUNT;
$conn = $this->establishConnection('r'); PhabricatorUserCache::clearCache($count_key, $user->getPHID());
$user->clearCacheData($count_key);
$data = queryfx_one(
$conn,
'SELECT COUNT(*) as count
FROM %T
WHERE userPHID = %s AND hasViewed = 0',
$this->getTableName(),
$user->getPHID());
return $data['count'];
} }
} }

View file

@ -0,0 +1,45 @@
<?php
final class PhabricatorUserMessageCountCacheType
extends PhabricatorUserCacheType {
const CACHETYPE = 'message.count';
const KEY_COUNT = 'user.message.count.v1';
public function getAutoloadKeys() {
return array(
self::KEY_COUNT,
);
}
public function canManageKey($key) {
return ($key === self::KEY_COUNT);
}
public function getValueFromStorage($value) {
return (int)$value;
}
public function getValueForStorage($value) {
return $value;
}
public function newValueForUsers($key, array $users) {
if (!$users) {
return array();
}
$user_phids = mpull($users, 'getPHID');
$unread_status = ConpherenceParticipationStatus::BEHIND;
$unread = id(new ConpherenceParticipantCountQuery())
->withParticipantPHIDs($user_phids)
->withParticipationStatus($unread_status)
->execute();
$empty = array_fill_keys($user_phids, 0);
return $unread + $empty;
}
}

View file

@ -0,0 +1,50 @@
<?php
final class PhabricatorUserNotificationCountCacheType
extends PhabricatorUserCacheType {
const CACHETYPE = 'notification.count';
const KEY_COUNT = 'user.notification.count.v1';
public function getAutoloadKeys() {
return array(
self::KEY_COUNT,
);
}
public function canManageKey($key) {
return ($key === self::KEY_COUNT);
}
public function getValueFromStorage($value) {
return (int)$value;
}
public function getValueForStorage($value) {
return $value;
}
public function newValueForUsers($key, array $users) {
if (!$users) {
return array();
}
$user_phids = mpull($users, 'getPHID');
$table = new PhabricatorFeedStoryNotification();
$conn_r = $table->establishConnection('r');
$rows = queryfx_all(
$conn_r,
'SELECT userPHID, COUNT(*) N FROM %T
WHERE userPHID IN (%Ls) AND hasViewed = 0
GROUP BY userPHID',
$table->getTableName(),
$user_phids);
$empty = array_fill_keys($user_phids, 0);
return ipull($rows, 'N', 'userPHID') + $empty;
}
}

View file

@ -792,6 +792,16 @@ final class PhabricatorUser
return $this->requireCacheData($uri_key); return $this->requireCacheData($uri_key);
} }
public function getUnreadNotificationCount() {
$notification_key = PhabricatorUserNotificationCountCacheType::KEY_COUNT;
return $this->requireCacheData($notification_key);
}
public function getUnreadMessageCount() {
$message_key = PhabricatorUserMessageCountCacheType::KEY_COUNT;
return $this->requireCacheData($message_key);
}
public function getFullName() { public function getFullName() {
if (strlen($this->getRealName())) { if (strlen($this->getRealName())) {
return $this->getUsername().' ('.$this->getRealName().')'; return $this->getUsername().' ('.$this->getRealName().')';

View file

@ -97,10 +97,18 @@ final class PhabricatorUserCache extends PhabricatorUserDAO {
} }
public static function clearCache($key, $user_phid) { public static function clearCache($key, $user_phid) {
return self::clearCaches($key, array($user_phid));
}
public static function clearCaches($key, array $user_phids) {
if (PhabricatorEnv::isReadOnly()) { if (PhabricatorEnv::isReadOnly()) {
return; return;
} }
if (!$user_phids) {
return;
}
$table = new self(); $table = new self();
$conn_w = $table->establishConnection('w'); $conn_w = $table->establishConnection('w');
@ -108,15 +116,14 @@ final class PhabricatorUserCache extends PhabricatorUserDAO {
queryfx( queryfx(
$conn_w, $conn_w,
'DELETE FROM %T WHERE cacheIndex = %s AND userPHID = %s', 'DELETE FROM %T WHERE cacheIndex = %s AND userPHID IN (%Ls)',
$table->getTableName(), $table->getTableName(),
PhabricatorHash::digestForIndex($key), PhabricatorHash::digestForIndex($key),
$user_phid); $user_phids);
unset($unguarded); unset($unguarded);
} }
public static function clearCacheForAllUsers($key) { public static function clearCacheForAllUsers($key) {
if (PhabricatorEnv::isReadOnly()) { if (PhabricatorEnv::isReadOnly()) {
return; return;

View file

@ -162,7 +162,6 @@ final class PhabricatorUserPreferencesEditor
PhabricatorUserPreferencesCacheType::KEY_PREFERENCES); PhabricatorUserPreferencesCacheType::KEY_PREFERENCES);
} }
return $xactions; return $xactions;
} }

View file

@ -84,8 +84,10 @@ final class PhabricatorApplicationTransactionPublishWorker
$xaction_phids = idx($data, 'xactionPHIDs'); $xaction_phids = idx($data, 'xactionPHIDs');
if (!$xaction_phids) { if (!$xaction_phids) {
throw new PhabricatorWorkerPermanentFailureException( // It's okay if we don't have any transactions. This can happen when
pht('Task has no transaction PHIDs!')); // creating objects or performing no-op updates. We will still apply
// meaningful side effects like updating search engine indexes.
return array();
} }
$viewer = PhabricatorUser::getOmnipotentUser(); $viewer = PhabricatorUser::getOmnipotentUser();