mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-31 00:48:21 +01:00
Provide a cached class map query for making key-based class lookups more efficient
Summary: Ref T11954. Depends on D16993. We have a couple of "look up the class for this key" queries which are costly enough to show up on a profile. These aren't huge wins, but they're pretty easy. We currently do this like this: ``` $class_map = load_every_subclass(); return idx($class_map, $key); ``` However, we don't need to load EVERY subclass if we're only looking for, say, the Conduit method subclass which implements `user.whoami`. This allows us to cache that map and find the right class efficiently. This cache is self-validating and completely safe even in development. Test Plan: - Used `curl` to make queries to `user.whoami`, verified that content was identical before and after the change. - Used `ab -n100` to roughly measure 99th percentile time, which dropped from 74ms to 65ms. This is a small improvement (13% in the best case, here) but it benefits every Conduit method call. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11954 Differential Revision: https://secure.phabricator.com/D16994
This commit is contained in:
parent
52112620a3
commit
1f3fcce6fe
2 changed files with 131 additions and 0 deletions
|
@ -2031,6 +2031,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCacheSetupCheck' => 'applications/config/check/PhabricatorCacheSetupCheck.php',
|
||||
'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php',
|
||||
'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php',
|
||||
'PhabricatorCachedClassMapQuery' => 'applications/cache/PhabricatorCachedClassMapQuery.php',
|
||||
'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php',
|
||||
'PhabricatorCachesTestCase' => 'applications/cache/__tests__/PhabricatorCachesTestCase.php',
|
||||
'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php',
|
||||
|
@ -6889,6 +6890,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorCacheSpec' => 'Phobject',
|
||||
'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'PhabricatorCachedClassMapQuery' => 'Phobject',
|
||||
'PhabricatorCaches' => 'Phobject',
|
||||
'PhabricatorCachesTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorCalendarApplication' => 'PhabricatorApplication',
|
||||
|
|
129
src/applications/cache/PhabricatorCachedClassMapQuery.php
vendored
Normal file
129
src/applications/cache/PhabricatorCachedClassMapQuery.php
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Cached @{class:PhutilClassMapQuery} which can perform lookups for single
|
||||
* classes efficiently.
|
||||
*
|
||||
* Some class trees (like Conduit methods and PHID types) contain a huge number
|
||||
* of classes but are frequently accessed by looking for a specific class by
|
||||
* a known identifier (like a Conduit method name or a PHID type constant).
|
||||
*
|
||||
* Loading the entire class map for these cases has a small but measurable
|
||||
* performance cost. Instead, we can build a cache from each Conduit method
|
||||
* name to just the class required to serve that request. This means that we
|
||||
* load fewer classes and have less overhead to execute API calls.
|
||||
*/
|
||||
final class PhabricatorCachedClassMapQuery
|
||||
extends Phobject {
|
||||
|
||||
private $query;
|
||||
private $queryCacheKey;
|
||||
private $mapKeyMethod;
|
||||
private $objectMap;
|
||||
|
||||
public function setClassMapQuery(PhutilClassMapQuery $query) {
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMapKeyMethod($method) {
|
||||
$this->mapKeyMethod = $method;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function loadClasses(array $values) {
|
||||
$cache = PhabricatorCaches::getRuntimeCache();
|
||||
|
||||
$cache_keys = $this->getCacheKeys($values);
|
||||
$cache_map = $cache->getKeys($cache_keys);
|
||||
|
||||
$results = array();
|
||||
$writes = array();
|
||||
foreach ($cache_keys as $value => $cache_key) {
|
||||
if (isset($cache_map[$cache_key])) {
|
||||
$class_name = $cache_map[$cache_key];
|
||||
try {
|
||||
$result = $this->newObject($class_name);
|
||||
if ($this->getObjectMapKey($result) === $value) {
|
||||
$results[$value] = $result;
|
||||
continue;
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
// Keep going, we'll handle this immediately below.
|
||||
}
|
||||
|
||||
// If we didn't "continue;" above, there was either a direct issue with
|
||||
// the cache or the cached class did not generate the correct map key.
|
||||
// Wipe the cache and pretend we missed.
|
||||
$cache->deleteKey($cache_key);
|
||||
}
|
||||
|
||||
if ($this->objectMap === null) {
|
||||
$this->objectMap = $this->newObjectMap();
|
||||
}
|
||||
|
||||
if (isset($this->objectMap[$value])) {
|
||||
$results[$value] = $this->objectMap[$value];
|
||||
$writes[$cache_key] = get_class($results[$value]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($writes) {
|
||||
$cache->setKeys($writes);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function loadClass($value) {
|
||||
$result = $this->loadClasses(array($value));
|
||||
return idx($result, $value);
|
||||
}
|
||||
|
||||
private function getCacheKeys(array $values) {
|
||||
if ($this->queryCacheKey === null) {
|
||||
$this->queryCacheKey = $this->query->getCacheKey();
|
||||
}
|
||||
|
||||
$key = $this->queryCacheKey;
|
||||
$method = $this->mapKeyMethod;
|
||||
|
||||
$keys = array();
|
||||
foreach ($values as $value) {
|
||||
$keys[$value] = "classmap({$key}).{$method}({$value})";
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
private function newObject($class_name) {
|
||||
return newv($class_name, array());
|
||||
}
|
||||
|
||||
private function newObjectMap() {
|
||||
$map = $this->query->execute();
|
||||
|
||||
$result = array();
|
||||
foreach ($map as $object) {
|
||||
$value = $this->getObjectMapKey($object);
|
||||
if (isset($result[$value])) {
|
||||
$other = $result[$value];
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Two objects (of classes "%s" and "%s") generate the same map '.
|
||||
'value ("%s"). Each object must generate a unique map value.',
|
||||
get_class($object),
|
||||
get_class($other),
|
||||
$value));
|
||||
}
|
||||
$result[$value] = $object;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getObjectMapKey($object) {
|
||||
return call_user_func(array($object, $this->mapKeyMethod));
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue