1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 12:52:42 +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:
epriestley 2015-05-14 11:15:22 -07:00
parent fef3c778fd
commit aa550189c7
5 changed files with 132 additions and 22 deletions

View 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;

View file

@ -55,16 +55,7 @@ final class PhabricatorCalendarEventEditor
case PhabricatorCalendarEventTransaction::TYPE_INVITE: case PhabricatorCalendarEventTransaction::TYPE_INVITE:
$map = $xaction->getNewValue(); $map = $xaction->getNewValue();
$phids = array_keys($map); $phids = array_keys($map);
$invitees = array(); $invitees = mpull($object->getInvitees(), null, 'getInviteePHID');
if ($map && !$this->getIsNewObject()) {
$invitees = id(new PhabricatorCalendarEventInviteeQuery())
->setViewer($this->getActor())
->withEventPHIDs(array($object->getPHID()))
->withInviteePHIDs($phids)
->execute();
$invitees = mpull($invitees, null, 'getInviteePHID');
}
$old = array(); $old = array();
foreach ($phids as $phid) { foreach ($phids as $phid) {
@ -193,6 +184,53 @@ final class PhabricatorCalendarEventEditor
return $xactions; 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( protected function validateAllTransactions(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,

View file

@ -147,6 +147,19 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
return parent::save(); 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( private static $statusTexts = array(
self::STATUS_AWAY => 'away', self::STATUS_AWAY => 'away',
self::STATUS_SPORADIC => 'sporadic', self::STATUS_SPORADIC => 'sporadic',

View file

@ -201,8 +201,16 @@ final class PhabricatorPeopleQuery
} }
if ($this->needAvailability) { if ($this->needAvailability) {
// TODO: Add caching. $rebuild = array();
$rebuild = $users; foreach ($users as $user) {
$cache = $user->getAvailabilityCache();
if ($cache !== null) {
$user->attachAvailability($cache);
} else {
$rebuild[] = $user;
}
}
if ($rebuild) { if ($rebuild) {
$this->rebuildAvailabilityCache($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) { foreach ($rebuild as $phid => $user) {
$events = idx($map, $phid, array()); $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. // because of an event, we check again for events after that one ends.
while (true) { while (true) {
foreach ($events as $event) { foreach ($events as $event) {
$from = ($event->getDateFrom() - $margin); $from = $event->getDateFromForCache();
$to = $event->getDateTo(); $to = $event->getDateTo();
if (($from <= $cursor) && ($to > $cursor)) { if (($from <= $cursor) && ($to > $cursor)) {
$cursor = $to; $cursor = $to;
@ -436,15 +439,16 @@ final class PhabricatorPeopleQuery
); );
$availability_ttl = $cursor; $availability_ttl = $cursor;
} else { } else {
$availability = null; $availability = array(
'until' => null,
);
$availability_ttl = $max_range; $availability_ttl = $max_range;
} }
// Never TTL the cache to longer than the maximum range we examined. // Never TTL the cache to longer than the maximum range we examined.
$availability_ttl = min($availability_ttl, $max_range); $availability_ttl = min($availability_ttl, $max_range);
// TODO: Write the cache. $user->writeAvailabilityCache($availability, $availability_ttl);
$user->attachAvailability($availability); $user->attachAvailability($availability);
} }
} }

View file

@ -27,6 +27,8 @@ final class PhabricatorUser
protected $passwordHash; protected $passwordHash;
protected $profileImagePHID; protected $profileImagePHID;
protected $profileImageCache; protected $profileImageCache;
protected $availabilityCache;
protected $availabilityCacheTTL;
protected $timezoneIdentifier = ''; protected $timezoneIdentifier = '';
protected $consoleEnabled = 0; protected $consoleEnabled = 0;
@ -146,6 +148,8 @@ final class PhabricatorUser
'accountSecret' => 'bytes64', 'accountSecret' => 'bytes64',
'isEnrolledInMultiFactor' => 'bool', 'isEnrolledInMultiFactor' => 'bool',
'profileImageCache' => 'text255?', 'profileImageCache' => 'text255?',
'availabilityCache' => 'text255?',
'availabilityCacheTTL' => 'uint32?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_phid' => null, 'key_phid' => null,
@ -166,6 +170,8 @@ final class PhabricatorUser
), ),
self::CONFIG_NO_MUTATE => array( self::CONFIG_NO_MUTATE => array(
'profileImageCache' => true, 'profileImageCache' => true,
'availabilityCache' => true,
'availabilityCacheTTL' => true,
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
@ -721,7 +727,7 @@ EOBODY;
/** /**
* @task availability * @task availability
*/ */
public function attachAvailability($availability) { public function attachAvailability(array $availability) {
$this->availability = $availability; $this->availability = $availability;
return $this; 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 )------------------------------------------------ */ /* -( Profile Image Cache )------------------------------------------------ */