1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-20 20:40:56 +01:00

Dramatically increase cache hit rate for feed

Summary:
Ref T8631. The query plan for feed stories is really bad right now, because we miss caches we should be hitting:

  - The workspace cache is stored at each query, so adjacent queries can't benefit from the cache (only subqueries). Feed has primarily sibling queries.
    - There is no technical reason to do this. Store the workspace cache on the root query, so sibling queries can hit it.
  - In `ObjectQuery`, we check the workspace once, then load all the PHIDs. When the PHIDs are a mixture of transactions and objects, we always miss the workspace and load the objects twice.
    - Instead, check the workspace after loading each type of object.
  - `HandleQuery` does not set itself as the parent query for `ObjectQuery`, so handles never hit the workspace cache.
    - Pass it, so they can hit the workspace cache.
  - Feed's weird `PhabricatorFeedStory::loadAllFromRows()` method does not specify a parent query on its object/handle queries.
    - Just declare the object query to be the "root" query until this eventually gets cleaned up.

Test Plan: Saw queries for each object drop from 4-6x to 1x in `/feed/`.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T8631

Differential Revision: https://secure.phabricator.com/D13479
This commit is contained in:
epriestley 2015-06-30 11:19:41 -07:00
parent 0ef1d4bc17
commit 4adaf53bf0
6 changed files with 77 additions and 35 deletions

View file

@ -78,10 +78,11 @@ abstract class PhabricatorFeedStory
$object_phids += $phids; $object_phids += $phids;
} }
$objects = id(new PhabricatorObjectQuery()) $object_query = id(new PhabricatorObjectQuery())
->setViewer($viewer) ->setViewer($viewer)
->withPHIDs(array_keys($object_phids)) ->withPHIDs(array_keys($object_phids));
->execute();
$objects = $object_query->execute();
foreach ($key_phids as $key => $phids) { foreach ($key_phids as $key => $phids) {
if (!$phids) { if (!$phids) {
@ -142,8 +143,13 @@ abstract class PhabricatorFeedStory
$handle_phids += $key_phids[$key]; $handle_phids += $key_phids[$key];
} }
// NOTE: This setParentQuery() is a little sketchy. Ideally, this whole
// method should be inside FeedQuery and it should be the parent query of
// both subqueries. We're just trying to share the workspace cache.
$handles = id(new PhabricatorHandleQuery()) $handles = id(new PhabricatorHandleQuery())
->setViewer($viewer) ->setViewer($viewer)
->setParentQuery($object_query)
->withPHIDs(array_keys($handle_phids)) ->withPHIDs(array_keys($handle_phids))
->execute(); ->execute();

View file

@ -1073,6 +1073,29 @@ final class PhabricatorUser
} }
/**
* Get a scalar string identifying this user.
*
* This is similar to using the PHID, but distinguishes between ominpotent
* and public users explicitly. This allows safe construction of cache keys
* or cache buckets which do not conflate public and omnipotent users.
*
* @return string Scalar identifier.
*/
public function getCacheFragment() {
if ($this->isOmnipotent()) {
return 'u.omnipotent';
}
$phid = $this->getPHID();
if ($phid) {
return 'u.'.$phid;
}
return 'u.public';
}
/* -( Managing Handles )--------------------------------------------------- */ /* -( Managing Handles )--------------------------------------------------- */

View file

@ -33,6 +33,7 @@ final class PhabricatorHandleQuery
$object_query = id(new PhabricatorObjectQuery()) $object_query = id(new PhabricatorObjectQuery())
->withPHIDs($phids) ->withPHIDs($phids)
->setParentQuery($this)
->requireCapabilities($this->getRequiredObjectCapabilities()) ->requireCapabilities($this->getRequiredObjectCapabilities())
->setViewer($this->getViewer()); ->setViewer($this->getViewer());

View file

@ -106,19 +106,6 @@ final class PhabricatorObjectQuery
private function loadObjectsByPHID(array $types, array $phids) { private function loadObjectsByPHID(array $types, array $phids) {
$results = array(); $results = array();
$workspace = $this->getObjectsFromWorkspace($phids);
foreach ($phids as $key => $phid) {
if (isset($workspace[$phid])) {
$results[$phid] = $workspace[$phid];
unset($phids[$key]);
}
}
if (!$phids) {
return $results;
}
$groups = array(); $groups = array();
foreach ($phids as $phid) { foreach ($phids as $phid) {
$type = phid_get_type($phid); $type = phid_get_type($phid);
@ -127,6 +114,21 @@ final class PhabricatorObjectQuery
$in_flight = $this->getPHIDsInFlight(); $in_flight = $this->getPHIDsInFlight();
foreach ($groups as $type => $group) { foreach ($groups as $type => $group) {
// We check the workspace for each group, because some groups may trigger
// other groups to load (for example, transactions load their objects).
$workspace = $this->getObjectsFromWorkspace($group);
foreach ($group as $key => $phid) {
if (isset($workspace[$phid])) {
$results[$phid] = $workspace[$phid];
unset($group[$key]);
}
}
if (!$group) {
continue;
}
// Don't try to load PHIDs which are already "in flight"; this prevents // Don't try to load PHIDs which are already "in flight"; this prevents
// us from recursing indefinitely if policy checks or edges form a loop. // us from recursing indefinitely if policy checks or edges form a loop.
// We will decline to load the corresponding objects. // We will decline to load the corresponding objects.
@ -139,7 +141,10 @@ final class PhabricatorObjectQuery
if ($group && isset($types[$type])) { if ($group && isset($types[$type])) {
$this->putPHIDsInFlight($group); $this->putPHIDsInFlight($group);
$objects = $types[$type]->loadObjects($this, $group); $objects = $types[$type]->loadObjects($this, $group);
$results += mpull($objects, null, 'getPHID');
$map = mpull($objects, null, 'getPHID');
$this->putObjectsInWorkspace($map);
$results += $map;
} }
} }

View file

@ -143,7 +143,7 @@ final class PhabricatorSpacesNamespaceQuery
public static function getViewerSpaces(PhabricatorUser $viewer) { public static function getViewerSpaces(PhabricatorUser $viewer) {
$cache = PhabricatorCaches::getRequestCache(); $cache = PhabricatorCaches::getRequestCache();
$cache_key = self::KEY_VIEWER.'('.$viewer->getPHID().')'; $cache_key = self::KEY_VIEWER.'('.$viewer->getCacheFragment().')';
$result = $cache->getKey($cache_key); $result = $cache->getKey($cache_key);
if ($result === null) { if ($result === null) {

View file

@ -406,8 +406,8 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery {
* *
* **Fully enrich objects pulled from the workspace.** After pulling objects * **Fully enrich objects pulled from the workspace.** After pulling objects
* from the workspace, you still need to load and attach any additional * from the workspace, you still need to load and attach any additional
* content the query requests. Otherwise, a query might return objects without * content the query requests. Otherwise, a query might return objects
* requested content. * without requested content.
* *
* Generally, you do not need to update the workspace yourself: it is * Generally, you do not need to update the workspace yourself: it is
* automatically populated as a side effect of objects surviving policy * automatically populated as a side effect of objects surviving policy
@ -419,16 +419,22 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery {
* @task workspace * @task workspace
*/ */
public function putObjectsInWorkspace(array $objects) { public function putObjectsInWorkspace(array $objects) {
assert_instances_of($objects, 'PhabricatorPolicyInterface'); $parent = $this->getParentQuery();
if ($parent) {
$viewer_phid = $this->getViewer()->getPHID(); $parent->putObjectsInWorkspace($objects);
return $this;
// The workspace is scoped per viewer to prevent accidental contamination.
if (empty($this->workspace[$viewer_phid])) {
$this->workspace[$viewer_phid] = array();
} }
$this->workspace[$viewer_phid] += $objects; assert_instances_of($objects, 'PhabricatorPolicyInterface');
$viewer_fragment = $this->getViewer()->getCacheFragment();
// The workspace is scoped per viewer to prevent accidental contamination.
if (empty($this->workspace[$viewer_fragment])) {
$this->workspace[$viewer_fragment] = array();
}
$this->workspace[$viewer_fragment] += $objects;
return $this; return $this;
} }
@ -445,20 +451,21 @@ abstract class PhabricatorPolicyAwareQuery extends PhabricatorOffsetPagedQuery {
* @task workspace * @task workspace
*/ */
public function getObjectsFromWorkspace(array $phids) { public function getObjectsFromWorkspace(array $phids) {
$viewer_phid = $this->getViewer()->getPHID(); $parent = $this->getParentQuery();
if ($parent) {
return $parent->getObjectsFromWorkspace($phids);
}
$viewer_fragment = $this->getViewer()->getCacheFragment();
$results = array(); $results = array();
foreach ($phids as $key => $phid) { foreach ($phids as $key => $phid) {
if (isset($this->workspace[$viewer_phid][$phid])) { if (isset($this->workspace[$viewer_fragment][$phid])) {
$results[$phid] = $this->workspace[$viewer_phid][$phid]; $results[$phid] = $this->workspace[$viewer_fragment][$phid];
unset($phids[$key]); unset($phids[$key]);
} }
} }
if ($phids && $this->getParentQuery()) {
$results += $this->getParentQuery()->getObjectsFromWorkspace($phids);
}
return $results; return $results;
} }