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:
parent
6f1053c206
commit
c1331bcb7b
16 changed files with 174 additions and 39 deletions
|
@ -3640,6 +3640,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorUserIconField' => 'applications/people/customfield/PhabricatorUserIconField.php',
|
||||
'PhabricatorUserLog' => 'applications/people/storage/PhabricatorUserLog.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',
|
||||
'PhabricatorUserPreferences' => 'applications/settings/storage/PhabricatorUserPreferences.php',
|
||||
'PhabricatorUserPreferencesCacheType' => 'applications/people/cache/PhabricatorUserPreferencesCacheType.php',
|
||||
|
@ -8451,6 +8453,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PhabricatorUserLogView' => 'AphrontView',
|
||||
'PhabricatorUserMessageCountCacheType' => 'PhabricatorUserCacheType',
|
||||
'PhabricatorUserNotificationCountCacheType' => 'PhabricatorUserCacheType',
|
||||
'PhabricatorUserPHIDResolver' => 'PhabricatorPHIDResolver',
|
||||
'PhabricatorUserPreferences' => array(
|
||||
'PhabricatorUserDAO',
|
||||
|
|
|
@ -46,17 +46,15 @@ final class AphlictDropdownDataQuery extends Phobject {
|
|||
$is_c_installed = PhabricatorApplication::isClassInstalledForViewer(
|
||||
$conpherence_app,
|
||||
$viewer);
|
||||
$raw_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);
|
||||
$raw_message_count_number = $viewer->getUnreadMessageCount();
|
||||
$message_count_number = $this->formatNumber($raw_message_count_number);
|
||||
} else {
|
||||
$raw_message_count_number = null;
|
||||
$message_count_number = null;
|
||||
}
|
||||
|
||||
|
||||
$conpherence_data = array(
|
||||
'isInstalled' => $is_c_installed,
|
||||
'countType' => 'messages',
|
||||
|
@ -69,15 +67,15 @@ final class AphlictDropdownDataQuery extends Phobject {
|
|||
$is_n_installed = PhabricatorApplication::isClassInstalledForViewer(
|
||||
$notification_app,
|
||||
$viewer);
|
||||
$notification_count_number = null;
|
||||
$raw_notification_count_number = null;
|
||||
if ($is_n_installed) {
|
||||
$raw_notification_count_number =
|
||||
id(new PhabricatorFeedStoryNotification())
|
||||
->countUnread($viewer);
|
||||
$raw_notification_count_number = $viewer->getUnreadNotificationCount();
|
||||
$notification_count_number = $this->formatNumber(
|
||||
$raw_notification_count_number);
|
||||
} else {
|
||||
$notification_count_number = null;
|
||||
$raw_notification_count_number = null;
|
||||
}
|
||||
|
||||
$notification_data = array(
|
||||
'isInstalled' => $is_n_installed,
|
||||
'countType' => 'notifications',
|
||||
|
|
|
@ -68,9 +68,12 @@ final class ConpherenceViewController extends
|
|||
$latest_transaction = head($transactions);
|
||||
$participant = $conpherence->getParticipantIfExists($user->getPHID());
|
||||
if ($participant) {
|
||||
$write_guard = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$participant->markUpToDate($conpherence, $latest_transaction);
|
||||
unset($write_guard);
|
||||
if (!$participant->isUpToDate($conpherence)) {
|
||||
$write_guard = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$participant->markUpToDate($conpherence, $latest_transaction);
|
||||
$user->clearCacheData(PhabricatorUserMessageCountCacheType::KEY_COUNT);
|
||||
unset($write_guard);
|
||||
}
|
||||
}
|
||||
|
||||
$data = ConpherenceTransactionRenderer::renderTransactions(
|
||||
|
|
|
@ -422,6 +422,10 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
$participant->save();
|
||||
}
|
||||
|
||||
PhabricatorUserCache::clearCaches(
|
||||
PhabricatorUserMessageCountCacheType::KEY_COUNT,
|
||||
array_keys($participants));
|
||||
|
||||
if ($xactions) {
|
||||
$data = array(
|
||||
'type' => 'message',
|
||||
|
|
|
@ -47,11 +47,16 @@ final class ConpherenceParticipant extends ConpherenceDAO {
|
|||
$this->setBehindTransactionPHID($xaction->getPHID());
|
||||
$this->setSeenMessageCount($conpherence->getMessageCount());
|
||||
$this->save();
|
||||
|
||||
PhabricatorUserCache::clearCache(
|
||||
PhabricatorUserMessageCountCacheType::KEY_COUNT,
|
||||
$this->getParticipantPHID());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function isUpToDate(ConpherenceThread $conpherence) {
|
||||
public function isUpToDate(ConpherenceThread $conpherence) {
|
||||
return
|
||||
($this->getSeenMessageCount() == $conpherence->getMessageCount())
|
||||
&&
|
||||
|
|
|
@ -159,7 +159,8 @@ final class PhabricatorFeedStoryPublisher extends Phobject {
|
|||
|
||||
$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])) {
|
||||
$mark_read = 1;
|
||||
} else {
|
||||
|
@ -184,6 +185,10 @@ final class PhabricatorFeedStoryPublisher extends Phobject {
|
|||
$notif->getTableName(),
|
||||
implode(', ', $sql));
|
||||
}
|
||||
|
||||
PhabricatorUserCache::clearCaches(
|
||||
PhabricatorUserNotificationCountCacheType::KEY_COUNT,
|
||||
$user_phids);
|
||||
}
|
||||
|
||||
private function sendNotification($chrono_key, array $subscribed_phids) {
|
||||
|
|
|
@ -134,6 +134,9 @@ final class PhabricatorFileQuery
|
|||
return $files;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$is_omnipotent = $viewer->isOmnipotent();
|
||||
|
||||
// We need to load attached objects to perform policy checks for files.
|
||||
// First, load the edges.
|
||||
|
||||
|
@ -156,6 +159,13 @@ final class PhabricatorFileQuery
|
|||
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) {
|
||||
$object_phids[$phid] = true;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@ final class PhabricatorNotificationClearController
|
|||
$viewer->getPHID(),
|
||||
$chrono_key);
|
||||
|
||||
PhabricatorUserCache::clearCache(
|
||||
PhabricatorUserNotificationCountCacheType::KEY_COUNT,
|
||||
$viewer->getPHID());
|
||||
|
||||
return id(new AphrontReloadResponse())
|
||||
->setURI('/notification/');
|
||||
}
|
||||
|
|
|
@ -71,8 +71,7 @@ final class PhabricatorNotificationPanelController
|
|||
$content,
|
||||
$connection_ui);
|
||||
|
||||
$unread_count = id(new PhabricatorFeedStoryNotification())
|
||||
->countUnread($viewer);
|
||||
$unread_count = $viewer->getUnreadNotificationCount();
|
||||
|
||||
$json = array(
|
||||
'content' => $content,
|
||||
|
|
|
@ -60,20 +60,10 @@ final class PhabricatorFeedStoryNotification extends PhabricatorFeedDAO {
|
|||
$object_phid);
|
||||
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
public function countUnread(PhabricatorUser $user) {
|
||||
$conn = $this->establishConnection('r');
|
||||
|
||||
$data = queryfx_one(
|
||||
$conn,
|
||||
'SELECT COUNT(*) as count
|
||||
FROM %T
|
||||
WHERE userPHID = %s AND hasViewed = 0',
|
||||
$this->getTableName(),
|
||||
$user->getPHID());
|
||||
|
||||
return $data['count'];
|
||||
$count_key = PhabricatorUserNotificationCountCacheType::KEY_COUNT;
|
||||
PhabricatorUserCache::clearCache($count_key, $user->getPHID());
|
||||
$user->clearCacheData($count_key);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
45
src/applications/people/cache/PhabricatorUserMessageCountCacheType.php
vendored
Normal file
45
src/applications/people/cache/PhabricatorUserMessageCountCacheType.php
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
50
src/applications/people/cache/PhabricatorUserNotificationCountCacheType.php
vendored
Normal file
50
src/applications/people/cache/PhabricatorUserNotificationCountCacheType.php
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -792,6 +792,16 @@ final class PhabricatorUser
|
|||
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() {
|
||||
if (strlen($this->getRealName())) {
|
||||
return $this->getUsername().' ('.$this->getRealName().')';
|
||||
|
|
|
@ -97,10 +97,18 @@ final class PhabricatorUserCache extends PhabricatorUserDAO {
|
|||
}
|
||||
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$user_phids) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = new self();
|
||||
$conn_w = $table->establishConnection('w');
|
||||
|
||||
|
@ -108,15 +116,14 @@ final class PhabricatorUserCache extends PhabricatorUserDAO {
|
|||
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'DELETE FROM %T WHERE cacheIndex = %s AND userPHID = %s',
|
||||
'DELETE FROM %T WHERE cacheIndex = %s AND userPHID IN (%Ls)',
|
||||
$table->getTableName(),
|
||||
PhabricatorHash::digestForIndex($key),
|
||||
$user_phid);
|
||||
$user_phids);
|
||||
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
|
||||
public static function clearCacheForAllUsers($key) {
|
||||
if (PhabricatorEnv::isReadOnly()) {
|
||||
return;
|
||||
|
|
|
@ -162,7 +162,6 @@ final class PhabricatorUserPreferencesEditor
|
|||
PhabricatorUserPreferencesCacheType::KEY_PREFERENCES);
|
||||
}
|
||||
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,8 +84,10 @@ final class PhabricatorApplicationTransactionPublishWorker
|
|||
|
||||
$xaction_phids = idx($data, 'xactionPHIDs');
|
||||
if (!$xaction_phids) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('Task has no transaction PHIDs!'));
|
||||
// It's okay if we don't have any transactions. This can happen when
|
||||
// creating objects or performing no-op updates. We will still apply
|
||||
// meaningful side effects like updating search engine indexes.
|
||||
return array();
|
||||
}
|
||||
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
|
Loading…
Reference in a new issue