mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 19:40:55 +01:00
Add an availability cache for users
Summary: Ref T7707. Caches availability on users to reduce the cost of loading handles. This cache is very slightly tricky to dirty properly. Test Plan: - Use DarkConsole to examine queries; saw cache hits, miss+fill, dirty. - Saw availability change correctly after canceling, joining, declining events. - Saw no queries to Calendar for pages with only availability data. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T7707 Differential Revision: https://secure.phabricator.com/D12838
This commit is contained in:
parent
fef3c778fd
commit
aa550189c7
5 changed files with 132 additions and 22 deletions
5
resources/sql/autopatches/20150514.user.cache.2.sql
Normal file
5
resources/sql/autopatches/20150514.user.cache.2.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE {$NAMESPACE}_user.user
|
||||
ADD availabilityCache VARCHAR(255) COLLATE {$COLLATE_TEXT};
|
||||
|
||||
ALTER TABLE {$NAMESPACE}_user.user
|
||||
ADD availabilityCacheTTL INT UNSIGNED;
|
|
@ -55,16 +55,7 @@ final class PhabricatorCalendarEventEditor
|
|||
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
|
||||
$map = $xaction->getNewValue();
|
||||
$phids = array_keys($map);
|
||||
$invitees = array();
|
||||
|
||||
if ($map && !$this->getIsNewObject()) {
|
||||
$invitees = id(new PhabricatorCalendarEventInviteeQuery())
|
||||
->setViewer($this->getActor())
|
||||
->withEventPHIDs(array($object->getPHID()))
|
||||
->withInviteePHIDs($phids)
|
||||
->execute();
|
||||
$invitees = mpull($invitees, null, 'getInviteePHID');
|
||||
}
|
||||
$invitees = mpull($object->getInvitees(), null, 'getInviteePHID');
|
||||
|
||||
$old = array();
|
||||
foreach ($phids as $phid) {
|
||||
|
@ -193,6 +184,53 @@ final class PhabricatorCalendarEventEditor
|
|||
return $xactions;
|
||||
}
|
||||
|
||||
protected function applyFinalEffects($object, array $xactions) {
|
||||
|
||||
// Clear the availability caches for users whose availability is affected
|
||||
// by this edit.
|
||||
|
||||
$invalidate_all = false;
|
||||
$invalidate_phids = array();
|
||||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case PhabricatorCalendarEventTransaction::TYPE_START_DATE:
|
||||
case PhabricatorCalendarEventTransaction::TYPE_END_DATE:
|
||||
case PhabricatorCalendarEventTransaction::TYPE_CANCEL:
|
||||
case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY:
|
||||
// For these kinds of changes, we need to invalidate the availabilty
|
||||
// caches for all attendees.
|
||||
$invalidate_all = true;
|
||||
break;
|
||||
case PhabricatorCalendarEventTransaction::TYPE_INVITE:
|
||||
foreach ($xaction->getNewValue() as $phid => $ignored) {
|
||||
$invalidate_phids[$phid] = $phid;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$phids = mpull($object->getInvitees(), 'getInviteePHID');
|
||||
$phids = array_fuse($phids);
|
||||
|
||||
if (!$invalidate_all) {
|
||||
$phids = array_select_keys($phids, $invalidate_phids);
|
||||
}
|
||||
|
||||
if ($phids) {
|
||||
$user = new PhabricatorUser();
|
||||
$conn_w = $user->establishConnection('w');
|
||||
queryfx(
|
||||
$conn_w,
|
||||
'UPDATE %T SET availabilityCacheTTL = NULL
|
||||
WHERE phid IN (%Ls) AND availabilityCacheTTL >= %d',
|
||||
$user->getTableName(),
|
||||
$phids,
|
||||
$object->getDateFromForCache());
|
||||
}
|
||||
|
||||
return $xactions;
|
||||
}
|
||||
|
||||
|
||||
protected function validateAllTransactions(
|
||||
PhabricatorLiskDAO $object,
|
||||
|
|
|
@ -147,6 +147,19 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
|
|||
return parent::save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event start epoch for evaluating invitee availability.
|
||||
*
|
||||
* When assessing availability, we pretend events start earlier than they
|
||||
* really. This allows us to mark users away for the entire duration of a
|
||||
* series of back-to-back meetings, even if they don't strictly overlap.
|
||||
*
|
||||
* @return int Event start date for availability caches.
|
||||
*/
|
||||
public function getDateFromForCache() {
|
||||
return ($this->getDateFrom() - phutil_units('15 minutes in seconds'));
|
||||
}
|
||||
|
||||
private static $statusTexts = array(
|
||||
self::STATUS_AWAY => 'away',
|
||||
self::STATUS_SPORADIC => 'sporadic',
|
||||
|
|
|
@ -201,8 +201,16 @@ final class PhabricatorPeopleQuery
|
|||
}
|
||||
|
||||
if ($this->needAvailability) {
|
||||
// TODO: Add caching.
|
||||
$rebuild = $users;
|
||||
$rebuild = array();
|
||||
foreach ($users as $user) {
|
||||
$cache = $user->getAvailabilityCache();
|
||||
if ($cache !== null) {
|
||||
$user->attachAvailability($cache);
|
||||
} else {
|
||||
$rebuild[] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
if ($rebuild) {
|
||||
$this->rebuildAvailabilityCache($rebuild);
|
||||
}
|
||||
|
@ -405,11 +413,6 @@ final class PhabricatorPeopleQuery
|
|||
}
|
||||
}
|
||||
|
||||
// Margin between meetings: pretend meetings start earlier than they do
|
||||
// so we mark you away for the entire time if you have a series of
|
||||
// back-to-back meetings, even if they don't strictly overlap.
|
||||
$margin = phutil_units('15 minutes in seconds');
|
||||
|
||||
foreach ($rebuild as $phid => $user) {
|
||||
$events = idx($map, $phid, array());
|
||||
|
||||
|
@ -419,7 +422,7 @@ final class PhabricatorPeopleQuery
|
|||
// because of an event, we check again for events after that one ends.
|
||||
while (true) {
|
||||
foreach ($events as $event) {
|
||||
$from = ($event->getDateFrom() - $margin);
|
||||
$from = $event->getDateFromForCache();
|
||||
$to = $event->getDateTo();
|
||||
if (($from <= $cursor) && ($to > $cursor)) {
|
||||
$cursor = $to;
|
||||
|
@ -436,15 +439,16 @@ final class PhabricatorPeopleQuery
|
|||
);
|
||||
$availability_ttl = $cursor;
|
||||
} else {
|
||||
$availability = null;
|
||||
$availability = array(
|
||||
'until' => null,
|
||||
);
|
||||
$availability_ttl = $max_range;
|
||||
}
|
||||
|
||||
// Never TTL the cache to longer than the maximum range we examined.
|
||||
$availability_ttl = min($availability_ttl, $max_range);
|
||||
|
||||
// TODO: Write the cache.
|
||||
|
||||
$user->writeAvailabilityCache($availability, $availability_ttl);
|
||||
$user->attachAvailability($availability);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ final class PhabricatorUser
|
|||
protected $passwordHash;
|
||||
protected $profileImagePHID;
|
||||
protected $profileImageCache;
|
||||
protected $availabilityCache;
|
||||
protected $availabilityCacheTTL;
|
||||
protected $timezoneIdentifier = '';
|
||||
|
||||
protected $consoleEnabled = 0;
|
||||
|
@ -146,6 +148,8 @@ final class PhabricatorUser
|
|||
'accountSecret' => 'bytes64',
|
||||
'isEnrolledInMultiFactor' => 'bool',
|
||||
'profileImageCache' => 'text255?',
|
||||
'availabilityCache' => 'text255?',
|
||||
'availabilityCacheTTL' => 'uint32?',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_phid' => null,
|
||||
|
@ -166,6 +170,8 @@ final class PhabricatorUser
|
|||
),
|
||||
self::CONFIG_NO_MUTATE => array(
|
||||
'profileImageCache' => true,
|
||||
'availabilityCache' => true,
|
||||
'availabilityCacheTTL' => true,
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
@ -721,7 +727,7 @@ EOBODY;
|
|||
/**
|
||||
* @task availability
|
||||
*/
|
||||
public function attachAvailability($availability) {
|
||||
public function attachAvailability(array $availability) {
|
||||
$this->availability = $availability;
|
||||
return $this;
|
||||
}
|
||||
|
@ -762,6 +768,50 @@ EOBODY;
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get cached availability, if present.
|
||||
*
|
||||
* @return wild|null Cache data, or null if no cache is available.
|
||||
* @task availability
|
||||
*/
|
||||
public function getAvailabilityCache() {
|
||||
$now = PhabricatorTime::getNow();
|
||||
if ($this->availabilityCacheTTL <= $now) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return phutil_json_decode($this->availabilityCache);
|
||||
} catch (Exception $ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write to the availability cache.
|
||||
*
|
||||
* @param wild Availability cache data.
|
||||
* @param int|null Cache TTL.
|
||||
* @return this
|
||||
* @task availability
|
||||
*/
|
||||
public function writeAvailabilityCache(array $availability, $ttl) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
queryfx(
|
||||
$this->establishConnection('w'),
|
||||
'UPDATE %T SET availabilityCache = %s, availabilityCacheTTL = %nd
|
||||
WHERE id = %d',
|
||||
$this->getTableName(),
|
||||
json_encode($availability),
|
||||
$ttl,
|
||||
$this->getID());
|
||||
unset($unguarded);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/* -( Profile Image Cache )------------------------------------------------ */
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue