@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.

Convert user profile images into a standard cache

Summary:
Ref T4103. Ref T10078. This moves profile image caches to new usercache infrastructure.

These dirty automatically based on configuration and User properties, so add some stuff to make that happen.

This reduces the number of queries issued on every page by 1.

Test Plan: Browsed around, changed profile image, viewed as self, viewed as another user, verified no more query to pull this information on every page

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4103, T10078

Differential Revision: https://secure.phabricator.com/D16040

+165 -143
+2
resources/sql/autopatches/20160604.user.02.removeimagecache.sql
··· 1 + ALTER TABLE {$NAMESPACE}_user.user 2 + DROP COLUMN profileImageCache;
+2
src/__phutil_library_map__.php
··· 3650 3650 'PhabricatorUserPreferencesTransactionQuery' => 'applications/settings/query/PhabricatorUserPreferencesTransactionQuery.php', 3651 3651 'PhabricatorUserProfile' => 'applications/people/storage/PhabricatorUserProfile.php', 3652 3652 'PhabricatorUserProfileEditor' => 'applications/people/editor/PhabricatorUserProfileEditor.php', 3653 + 'PhabricatorUserProfileImageCacheType' => 'applications/people/cache/PhabricatorUserProfileImageCacheType.php', 3653 3654 'PhabricatorUserRealNameField' => 'applications/people/customfield/PhabricatorUserRealNameField.php', 3654 3655 'PhabricatorUserRolesField' => 'applications/people/customfield/PhabricatorUserRolesField.php', 3655 3656 'PhabricatorUserSchemaSpec' => 'applications/people/storage/PhabricatorUserSchemaSpec.php', ··· 8465 8466 'PhabricatorUserPreferencesTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 8466 8467 'PhabricatorUserProfile' => 'PhabricatorUserDAO', 8467 8468 'PhabricatorUserProfileEditor' => 'PhabricatorApplicationTransactionEditor', 8469 + 'PhabricatorUserProfileImageCacheType' => 'PhabricatorUserCacheType', 8468 8470 'PhabricatorUserRealNameField' => 'PhabricatorUserCustomField', 8469 8471 'PhabricatorUserRolesField' => 'PhabricatorUserCustomField', 8470 8472 'PhabricatorUserSchemaSpec' => 'PhabricatorConfigSchemaSpec',
+22 -2
src/applications/auth/engine/PhabricatorAuthSessionEngine.php
··· 113 113 $session_key = PhabricatorHash::digest($session_token); 114 114 115 115 $cache_parts = $this->getUserCacheQueryParts($conn_r); 116 - list($cache_selects, $cache_joins, $cache_map) = $cache_parts; 116 + list($cache_selects, $cache_joins, $cache_map, $types_map) = $cache_parts; 117 117 118 118 $info = queryfx_one( 119 119 $conn_r, ··· 162 162 163 163 $user = $user_table->loadFromArray($info); 164 164 165 + $cache_raw = $this->filterRawCacheData($user, $types_map, $cache_raw); 165 166 $user->attachRawCacheData($cache_raw); 166 167 $user->setAllowInlineCacheGeneration(true); 167 168 ··· 761 762 $cache_map = array(); 762 763 763 764 $keys = array(); 765 + $types_map = array(); 764 766 765 767 $cache_types = PhabricatorUserCacheType::getAllCacheTypes(); 766 768 foreach ($cache_types as $cache_type) { 767 769 foreach ($cache_type->getAutoloadKeys() as $autoload_key) { 768 770 $keys[] = $autoload_key; 771 + $types_map[$autoload_key] = $cache_type; 769 772 } 770 773 } 771 774 ··· 809 812 $cache_joins = ''; 810 813 } 811 814 812 - return array($cache_selects, $cache_joins, $cache_map); 815 + return array($cache_selects, $cache_joins, $cache_map, $types_map); 816 + } 817 + 818 + private function filterRawCacheData( 819 + PhabricatorUser $user, 820 + array $types_map, 821 + array $cache_raw) { 822 + 823 + foreach ($cache_raw as $cache_key => $cache_data) { 824 + $type = $types_map[$cache_key]; 825 + if ($type->shouldValidateRawCacheData()) { 826 + if (!$type->isRawCacheDataValid($user, $cache_key, $cache_data)) { 827 + unset($cache_raw[$cache_key]); 828 + } 829 + } 830 + } 831 + 832 + return $cache_raw; 813 833 } 814 834 815 835 }
+8
src/applications/people/cache/PhabricatorUserCacheType.php
··· 18 18 return array(); 19 19 } 20 20 21 + public function shouldValidateRawCacheData() { 22 + return false; 23 + } 24 + 25 + public function isRawCacheDataValid(PhabricatorUser $user, $key, $data) { 26 + throw new PhutilMethodNotImplementedException(); 27 + } 28 + 21 29 public function getValueFromStorage($value) { 22 30 return phutil_json_decode($value); 23 31 }
+82
src/applications/people/cache/PhabricatorUserProfileImageCacheType.php
··· 1 + <?php 2 + 3 + final class PhabricatorUserProfileImageCacheType 4 + extends PhabricatorUserCacheType { 5 + 6 + const CACHETYPE = 'user.profile'; 7 + 8 + const KEY_URI = 'user.profile.image.uri.v1'; 9 + 10 + public function getAutoloadKeys() { 11 + return array( 12 + self::KEY_URI, 13 + ); 14 + } 15 + 16 + public function canManageKey($key) { 17 + return ($key === self::KEY_URI); 18 + } 19 + 20 + public function newValueForUsers($key, array $users) { 21 + $viewer = $this->getViewer(); 22 + 23 + $file_phids = mpull($users, 'getProfileImagePHID'); 24 + $file_phids = array_filter($file_phids); 25 + 26 + if ($file_phids) { 27 + $files = id(new PhabricatorFileQuery()) 28 + ->setViewer($viewer) 29 + ->withPHIDs($file_phids) 30 + ->execute(); 31 + $files = mpull($files, null, 'getPHID'); 32 + } else { 33 + $files = array(); 34 + } 35 + 36 + $results = array(); 37 + foreach ($users as $user) { 38 + $image_phid = $user->getProfileImagePHID(); 39 + if (isset($files[$image_phid])) { 40 + $image_uri = $files[$image_phid]->getBestURI(); 41 + } else { 42 + $image_uri = PhabricatorUser::getDefaultProfileImageURI(); 43 + } 44 + 45 + $user_phid = $user->getPHID(); 46 + $version = $this->getCacheVersion($user); 47 + $results[$user_phid] = "{$version},{$image_uri}"; 48 + } 49 + 50 + return $results; 51 + } 52 + 53 + public function getValueFromStorage($value) { 54 + $parts = explode(',', $value, 2); 55 + return end($parts); 56 + } 57 + 58 + public function getValueForStorage($value) { 59 + return $value; 60 + } 61 + 62 + public function shouldValidateRawCacheData() { 63 + return true; 64 + } 65 + 66 + public function isRawCacheDataValid(PhabricatorUser $user, $key, $data) { 67 + $parts = explode(',', $data, 2); 68 + $version = reset($parts); 69 + return ($version === $this->getCacheVersion($user)); 70 + } 71 + 72 + private function getCacheVersion(PhabricatorUser $user) { 73 + $parts = array( 74 + PhabricatorEnv::getCDNURI('/'), 75 + PhabricatorEnv::getEnvConfig('cluster.instance'), 76 + $user->getProfileImagePHID(), 77 + ); 78 + $parts = serialize($parts); 79 + return PhabricatorHash::digestForIndex($parts); 80 + } 81 + 82 + }
+1 -9
src/applications/people/extension/PhabricatorPeopleMainMenuBarExtension.php
··· 7 7 8 8 public function buildMainMenus() { 9 9 $viewer = $this->getViewer(); 10 - 11 - // TODO: This should get cached. 12 - 13 - $profile = id(new PhabricatorPeopleQuery()) 14 - ->setViewer($viewer) 15 - ->needProfileImage(true) 16 - ->withPHIDs(array($viewer->getPHID())) 17 - ->executeOne(); 18 - $image = $profile->getProfileImageURI(); 10 + $image = $viewer->getProfileImageURI(); 19 11 20 12 $bar_item = id(new PHUIListItemView()) 21 13 ->setName($viewer->getUsername())
+42 -55
src/applications/people/query/PhabricatorPeopleQuery.php
··· 106 106 } 107 107 108 108 public function needProfileImage($need) { 109 - $this->needProfileImage = $need; 109 + $cache_key = PhabricatorUserProfileImageCacheType::KEY_URI; 110 + 111 + if ($need) { 112 + $this->cacheKeys[$cache_key] = true; 113 + } else { 114 + unset($this->cacheKeys[$cache_key]); 115 + } 116 + 110 117 return $this; 111 118 } 112 119 ··· 179 186 $user_awards = idx($awards, $user->getPHID(), array()); 180 187 $badge_phids = mpull($user_awards, 'getBadgePHID'); 181 188 $user->attachBadgePHIDs($badge_phids); 182 - } 183 - } 184 - 185 - if ($this->needProfileImage) { 186 - $rebuild = array(); 187 - foreach ($users as $user) { 188 - $image_uri = $user->getProfileImageCache(); 189 - if ($image_uri) { 190 - // This user has a valid cache, so we don't need to fetch any 191 - // data or rebuild anything. 192 - 193 - $user->attachProfileImageURI($image_uri); 194 - continue; 195 - } 196 - 197 - // This user's cache is invalid or missing, so we're going to rebuild 198 - // it. 199 - $rebuild[] = $user; 200 - } 201 - 202 - if ($rebuild) { 203 - $file_phids = mpull($rebuild, 'getProfileImagePHID'); 204 - $file_phids = array_filter($file_phids); 205 - 206 - if ($file_phids) { 207 - // NOTE: We're using the omnipotent user here because older profile 208 - // images do not have the 'profile' flag, so they may not be visible 209 - // to the executing viewer. At some point, we could migrate to add 210 - // this flag and then use the real viewer, or just use the real 211 - // viewer after enough time has passed to limit the impact of old 212 - // data. The consequence of missing here is that we cache a default 213 - // image when a real image exists. 214 - $files = id(new PhabricatorFileQuery()) 215 - ->setParentQuery($this) 216 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 217 - ->withPHIDs($file_phids) 218 - ->execute(); 219 - $files = mpull($files, null, 'getPHID'); 220 - } else { 221 - $files = array(); 222 - } 223 - 224 - foreach ($rebuild as $user) { 225 - $image_phid = $user->getProfileImagePHID(); 226 - if (isset($files[$image_phid])) { 227 - $image_uri = $files[$image_phid]->getBestURI(); 228 - } else { 229 - $image_uri = PhabricatorUser::getDefaultProfileImageURI(); 230 - } 231 - 232 - $user->writeProfileImageCache($image_uri); 233 - $user->attachProfileImageURI($image_uri); 234 - } 235 189 } 236 190 } 237 191 ··· 509 463 $hashes[] = PhabricatorHash::digestForIndex($key); 510 464 } 511 465 466 + $types = PhabricatorUserCacheType::getAllCacheTypes(); 467 + 512 468 // First, pull any available caches. If we wanted to be particularly clever 513 469 // we could do this with JOINs in the main query. 514 470 ··· 517 473 518 474 $cache_data = queryfx_all( 519 475 $cache_conn, 520 - 'SELECT cacheKey, userPHID, cacheData FROM %T 476 + 'SELECT cacheKey, userPHID, cacheData, cacheType FROM %T 521 477 WHERE cacheIndex IN (%Ls) AND userPHID IN (%Ls)', 522 478 $cache_table->getTableName(), 523 479 $hashes, 524 480 array_keys($user_map)); 481 + 482 + $skip_validation = array(); 483 + 484 + // After we read caches from the database, discard any which have data that 485 + // invalid or out of date. This allows cache types to implement TTLs or 486 + // versions instead of or in addition to explicit cache clears. 487 + foreach ($cache_data as $row_key => $row) { 488 + $cache_type = $row['cacheType']; 489 + 490 + if (isset($skip_validation[$cache_type])) { 491 + continue; 492 + } 493 + 494 + if (empty($types[$cache_type])) { 495 + unset($cache_data[$row_key]); 496 + continue; 497 + } 498 + 499 + $type = $types[$cache_type]; 500 + if (!$type->shouldValidateRawCacheData()) { 501 + $skip_validation[$cache_type] = true; 502 + continue; 503 + } 504 + 505 + $user = $user_map[$row['userPHID']]; 506 + $raw_data = $row['cacheData']; 507 + if (!$type->isRawCacheDataValid($user, $row['cacheKey'], $raw_data)) { 508 + unset($cache_data[$row_key]); 509 + continue; 510 + } 511 + } 525 512 526 513 $need = array(); 527 514
+3 -76
src/applications/people/storage/PhabricatorUser.php
··· 30 30 protected $passwordSalt; 31 31 protected $passwordHash; 32 32 protected $profileImagePHID; 33 - protected $profileImageCache; 34 33 protected $availabilityCache; 35 34 protected $availabilityCacheTTL; 36 35 ··· 46 45 47 46 protected $accountSecret; 48 47 49 - private $profileImage = self::ATTACHABLE; 50 48 private $profile = null; 51 49 private $availability = self::ATTACHABLE; 52 50 private $preferences = null; ··· 196 194 'isApproved' => 'uint32', 197 195 'accountSecret' => 'bytes64', 198 196 'isEnrolledInMultiFactor' => 'bool', 199 - 'profileImageCache' => 'text255?', 200 197 'availabilityCache' => 'text255?', 201 198 'availabilityCacheTTL' => 'uint32?', 202 199 ), ··· 218 215 ), 219 216 ), 220 217 self::CONFIG_NO_MUTATE => array( 221 - 'profileImageCache' => true, 222 218 'availabilityCache' => true, 223 219 'availabilityCacheTTL' => true, 224 220 ), ··· 791 787 return celerity_get_resource_uri('/rsrc/image/avatar.png'); 792 788 } 793 789 794 - public function attachProfileImageURI($uri) { 795 - $this->profileImage = $uri; 796 - return $this; 797 - } 798 - 799 790 public function getProfileImageURI() { 800 - return $this->assertAttached($this->profileImage); 791 + $uri_key = PhabricatorUserProfileImageCacheType::KEY_URI; 792 + return $this->requireCacheData($uri_key); 801 793 } 802 794 803 795 public function getFullName() { ··· 1008 1000 } 1009 1001 1010 1002 1011 - /* -( Profile Image Cache )------------------------------------------------ */ 1012 - 1013 - 1014 - /** 1015 - * Get this user's cached profile image URI. 1016 - * 1017 - * @return string|null Cached URI, if a URI is cached. 1018 - * @task image-cache 1019 - */ 1020 - public function getProfileImageCache() { 1021 - $version = $this->getProfileImageVersion(); 1022 - 1023 - $parts = explode(',', $this->profileImageCache, 2); 1024 - if (count($parts) !== 2) { 1025 - return null; 1026 - } 1027 - 1028 - if ($parts[0] !== $version) { 1029 - return null; 1030 - } 1031 - 1032 - return $parts[1]; 1033 - } 1034 - 1035 - 1036 - /** 1037 - * Generate a new cache value for this user's profile image. 1038 - * 1039 - * @return string New cache value. 1040 - * @task image-cache 1041 - */ 1042 - public function writeProfileImageCache($uri) { 1043 - $version = $this->getProfileImageVersion(); 1044 - $cache = "{$version},{$uri}"; 1045 - 1046 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 1047 - queryfx( 1048 - $this->establishConnection('w'), 1049 - 'UPDATE %T SET profileImageCache = %s WHERE id = %d', 1050 - $this->getTableName(), 1051 - $cache, 1052 - $this->getID()); 1053 - unset($unguarded); 1054 - } 1055 - 1056 - 1057 - /** 1058 - * Get a version identifier for a user's profile image. 1059 - * 1060 - * This version will change if the image changes, or if any of the 1061 - * environment configuration which goes into generating a URI changes. 1062 - * 1063 - * @return string Cache version. 1064 - * @task image-cache 1065 - */ 1066 - private function getProfileImageVersion() { 1067 - $parts = array( 1068 - PhabricatorEnv::getCDNURI('/'), 1069 - PhabricatorEnv::getEnvConfig('cluster.instance'), 1070 - $this->getProfileImagePHID(), 1071 - ); 1072 - $parts = serialize($parts); 1073 - return PhabricatorHash::digestForIndex($parts); 1074 - } 1075 - 1076 - 1077 1003 /* -( Multi-Factor Authentication )---------------------------------------- */ 1078 1004 1079 1005 ··· 1529 1455 if (array_key_exists($user_phid, $map)) { 1530 1456 $usable_value = $map[$user_phid]; 1531 1457 $raw_value = $type->getValueForStorage($usable_value); 1458 + $usable_value = $type->getValueFromStorage($raw_value); 1532 1459 1533 1460 $this->rawCacheData[$key] = $raw_value; 1534 1461 PhabricatorUserCache::writeCache(
+3 -1
src/applications/people/storage/PhabricatorUserCache.php
··· 86 86 $conn_w, 87 87 'INSERT INTO %T (userPHID, cacheIndex, cacheKey, cacheData, cacheType) 88 88 VALUES %Q 89 - ON DUPLICATE KEY UPDATE cacheData = VALUES(cacheData)', 89 + ON DUPLICATE KEY UPDATE 90 + cacheData = VALUES(cacheData), 91 + cacheType = VALUES(cacheType)', 90 92 $table->getTableName(), 91 93 $chunk); 92 94 }