@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+132 -22
+5
resources/sql/autopatches/20150514.user.cache.2.sql
··· 1 + ALTER TABLE {$NAMESPACE}_user.user 2 + ADD availabilityCache VARCHAR(255) COLLATE {$COLLATE_TEXT}; 3 + 4 + ALTER TABLE {$NAMESPACE}_user.user 5 + ADD availabilityCacheTTL INT UNSIGNED;
+48 -10
src/applications/calendar/editor/PhabricatorCalendarEventEditor.php
··· 55 55 case PhabricatorCalendarEventTransaction::TYPE_INVITE: 56 56 $map = $xaction->getNewValue(); 57 57 $phids = array_keys($map); 58 - $invitees = array(); 59 - 60 - if ($map && !$this->getIsNewObject()) { 61 - $invitees = id(new PhabricatorCalendarEventInviteeQuery()) 62 - ->setViewer($this->getActor()) 63 - ->withEventPHIDs(array($object->getPHID())) 64 - ->withInviteePHIDs($phids) 65 - ->execute(); 66 - $invitees = mpull($invitees, null, 'getInviteePHID'); 67 - } 58 + $invitees = mpull($object->getInvitees(), null, 'getInviteePHID'); 68 59 69 60 $old = array(); 70 61 foreach ($phids as $phid) { ··· 189 180 array $xactions) { 190 181 191 182 $object->removeViewerTimezone($this->requireActor()); 183 + 184 + return $xactions; 185 + } 186 + 187 + protected function applyFinalEffects($object, array $xactions) { 188 + 189 + // Clear the availability caches for users whose availability is affected 190 + // by this edit. 191 + 192 + $invalidate_all = false; 193 + $invalidate_phids = array(); 194 + foreach ($xactions as $xaction) { 195 + switch ($xaction->getTransactionType()) { 196 + case PhabricatorCalendarEventTransaction::TYPE_START_DATE: 197 + case PhabricatorCalendarEventTransaction::TYPE_END_DATE: 198 + case PhabricatorCalendarEventTransaction::TYPE_CANCEL: 199 + case PhabricatorCalendarEventTransaction::TYPE_ALL_DAY: 200 + // For these kinds of changes, we need to invalidate the availabilty 201 + // caches for all attendees. 202 + $invalidate_all = true; 203 + break; 204 + case PhabricatorCalendarEventTransaction::TYPE_INVITE: 205 + foreach ($xaction->getNewValue() as $phid => $ignored) { 206 + $invalidate_phids[$phid] = $phid; 207 + } 208 + break; 209 + } 210 + } 211 + 212 + $phids = mpull($object->getInvitees(), 'getInviteePHID'); 213 + $phids = array_fuse($phids); 214 + 215 + if (!$invalidate_all) { 216 + $phids = array_select_keys($phids, $invalidate_phids); 217 + } 218 + 219 + if ($phids) { 220 + $user = new PhabricatorUser(); 221 + $conn_w = $user->establishConnection('w'); 222 + queryfx( 223 + $conn_w, 224 + 'UPDATE %T SET availabilityCacheTTL = NULL 225 + WHERE phid IN (%Ls) AND availabilityCacheTTL >= %d', 226 + $user->getTableName(), 227 + $phids, 228 + $object->getDateFromForCache()); 229 + } 192 230 193 231 return $xactions; 194 232 }
+13
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 147 147 return parent::save(); 148 148 } 149 149 150 + /** 151 + * Get the event start epoch for evaluating invitee availability. 152 + * 153 + * When assessing availability, we pretend events start earlier than they 154 + * really. This allows us to mark users away for the entire duration of a 155 + * series of back-to-back meetings, even if they don't strictly overlap. 156 + * 157 + * @return int Event start date for availability caches. 158 + */ 159 + public function getDateFromForCache() { 160 + return ($this->getDateFrom() - phutil_units('15 minutes in seconds')); 161 + } 162 + 150 163 private static $statusTexts = array( 151 164 self::STATUS_AWAY => 'away', 152 165 self::STATUS_SPORADIC => 'sporadic',
+15 -11
src/applications/people/query/PhabricatorPeopleQuery.php
··· 201 201 } 202 202 203 203 if ($this->needAvailability) { 204 - // TODO: Add caching. 205 - $rebuild = $users; 204 + $rebuild = array(); 205 + foreach ($users as $user) { 206 + $cache = $user->getAvailabilityCache(); 207 + if ($cache !== null) { 208 + $user->attachAvailability($cache); 209 + } else { 210 + $rebuild[] = $user; 211 + } 212 + } 213 + 206 214 if ($rebuild) { 207 215 $this->rebuildAvailabilityCache($rebuild); 208 216 } ··· 405 413 } 406 414 } 407 415 408 - // Margin between meetings: pretend meetings start earlier than they do 409 - // so we mark you away for the entire time if you have a series of 410 - // back-to-back meetings, even if they don't strictly overlap. 411 - $margin = phutil_units('15 minutes in seconds'); 412 - 413 416 foreach ($rebuild as $phid => $user) { 414 417 $events = idx($map, $phid, array()); 415 418 ··· 419 422 // because of an event, we check again for events after that one ends. 420 423 while (true) { 421 424 foreach ($events as $event) { 422 - $from = ($event->getDateFrom() - $margin); 425 + $from = $event->getDateFromForCache(); 423 426 $to = $event->getDateTo(); 424 427 if (($from <= $cursor) && ($to > $cursor)) { 425 428 $cursor = $to; ··· 436 439 ); 437 440 $availability_ttl = $cursor; 438 441 } else { 439 - $availability = null; 442 + $availability = array( 443 + 'until' => null, 444 + ); 440 445 $availability_ttl = $max_range; 441 446 } 442 447 443 448 // Never TTL the cache to longer than the maximum range we examined. 444 449 $availability_ttl = min($availability_ttl, $max_range); 445 450 446 - // TODO: Write the cache. 447 - 451 + $user->writeAvailabilityCache($availability, $availability_ttl); 448 452 $user->attachAvailability($availability); 449 453 } 450 454 }
+51 -1
src/applications/people/storage/PhabricatorUser.php
··· 27 27 protected $passwordHash; 28 28 protected $profileImagePHID; 29 29 protected $profileImageCache; 30 + protected $availabilityCache; 31 + protected $availabilityCacheTTL; 30 32 protected $timezoneIdentifier = ''; 31 33 32 34 protected $consoleEnabled = 0; ··· 146 148 'accountSecret' => 'bytes64', 147 149 'isEnrolledInMultiFactor' => 'bool', 148 150 'profileImageCache' => 'text255?', 151 + 'availabilityCache' => 'text255?', 152 + 'availabilityCacheTTL' => 'uint32?', 149 153 ), 150 154 self::CONFIG_KEY_SCHEMA => array( 151 155 'key_phid' => null, ··· 166 170 ), 167 171 self::CONFIG_NO_MUTATE => array( 168 172 'profileImageCache' => true, 173 + 'availabilityCache' => true, 174 + 'availabilityCacheTTL' => true, 169 175 ), 170 176 ) + parent::getConfiguration(); 171 177 } ··· 721 727 /** 722 728 * @task availability 723 729 */ 724 - public function attachAvailability($availability) { 730 + public function attachAvailability(array $availability) { 725 731 $this->availability = $availability; 726 732 return $this; 727 733 } ··· 759 765 } else { 760 766 return pht('Available'); 761 767 } 768 + } 769 + 770 + 771 + /** 772 + * Get cached availability, if present. 773 + * 774 + * @return wild|null Cache data, or null if no cache is available. 775 + * @task availability 776 + */ 777 + public function getAvailabilityCache() { 778 + $now = PhabricatorTime::getNow(); 779 + if ($this->availabilityCacheTTL <= $now) { 780 + return null; 781 + } 782 + 783 + try { 784 + return phutil_json_decode($this->availabilityCache); 785 + } catch (Exception $ex) { 786 + return null; 787 + } 788 + } 789 + 790 + 791 + /** 792 + * Write to the availability cache. 793 + * 794 + * @param wild Availability cache data. 795 + * @param int|null Cache TTL. 796 + * @return this 797 + * @task availability 798 + */ 799 + public function writeAvailabilityCache(array $availability, $ttl) { 800 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 801 + queryfx( 802 + $this->establishConnection('w'), 803 + 'UPDATE %T SET availabilityCache = %s, availabilityCacheTTL = %nd 804 + WHERE id = %d', 805 + $this->getTableName(), 806 + json_encode($availability), 807 + $ttl, 808 + $this->getID()); 809 + unset($unguarded); 810 + 811 + return $this; 762 812 } 763 813 764 814