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:
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:
|
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,
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 )------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue