Every config entity save we check to see if the UUID is in use by a different config entity. We load every config entity of that type and loop through them. This issue will explore optimising that and also seeing if we can do something generic so a config entity type can optimise for lookup of particular keys. For example, this could be useful for selecting blocks by themes.

Comments

alexpott’s picture

Status: Active » Needs review
StatusFileSize
new22.31 KB

Here's the idea sketched out in code.

Status: Needs review » Needs work

The last submitted patch, 1: 2430219.1.patch, failed testing.

alexpott’s picture

catch’s picture

Alternative storage for config such as mongodb shouldn't need to do this (it can query the config object natively), so would be good to ensure we don't add overhead for those implementations in terms of undoing what core does.

Denormalizing like this feels preferable to some of the other SQL-storage tweaks that have been suggested (like XML etc.).

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new14.87 KB
new21.44 KB

Okay moved everything inside the QueryFactory and Query code. ConfigEntityStorage is not involved at all.

Now trying to solve the Tour use case.

  $results = \Drupal::entityQuery('tour')
    ->condition('routes.*.route_name', $route_name)
    ->execute();

That look up could get expensive.

alexpott’s picture

StatusFileSize
new5.63 KB
new22.59 KB

Recursion... with wildcards... my head hurts.

Status: Needs review » Needs work

The last submitted patch, 6: 2430219.6.patch, failed testing.

berdir’s picture

/resurrect

This shows up in #2473983: [meta] Evaluate Entity Field API Scalability, which is partially a duplicate of #2423591: Optimize config entity import.

alexpott’s picture

Issue tags: +needs profiling
StatusFileSize
new22.58 KB

Rerolled... the failed test seems to pass.

alexpott’s picture

Status: Needs work » Needs review
alexpott’s picture

Priority: Normal » Major

Bumping to major - the performance implications of a slow lookup on UUID are nicely illustrated by #2473983: [meta] Evaluate Entity Field API Scalability

alexpott’s picture

Running @Berdir's test from #2473983-18: [meta] Evaluate Entity Field API Scalability

0: Created bundle with 20 fields in 0.18s
1: Created bundle with 20 fields in 0.2s
2: Created bundle with 20 fields in 0.19s
3: Created bundle with 20 fields in 0.21s
4: Created bundle with 20 fields in 0.19s
5: Created bundle with 20 fields in 0.19s
6: Created bundle with 20 fields in 0.19s
7: Created bundle with 20 fields in 0.2s
8: Created bundle with 20 fields in 0.22s
9: Created bundle with 20 fields in 0.19s
10: Created bundle with 20 fields in 0.2s
...
100: Created bundle with 20 fields in 0.29s
101: Created bundle with 20 fields in 0.28s
102: Created bundle with 20 fields in 0.37s
103: Created bundle with 20 fields in 0.27s
104: Created bundle with 20 fields in 0.28s
105: Created bundle with 20 fields in 0.28s
106: Created bundle with 20 fields in 0.29s
107: Created bundle with 20 fields in 0.35s
108: Created bundle with 20 fields in 0.27s
109: Created bundle with 20 fields in 0.3s
110: Created bundle with 20 fields in 0.32s
...
200: Created bundle with 20 fields in 0.47s
201: Created bundle with 20 fields in 0.51s
202: Created bundle with 20 fields in 0.48s
203: Created bundle with 20 fields in 0.47s
204: Created bundle with 20 fields in 0.37s
205: Created bundle with 20 fields in 0.59s
206: Created bundle with 20 fields in 0.59s
207: Created bundle with 20 fields in 0.46s
208: Created bundle with 20 fields in 0.37s
209: Created bundle with 20 fields in 0.37s
210: Created bundle with 20 fields in 0.49s
...
300: Created bundle with 20 fields in 0.63s
301: Created bundle with 20 fields in 0.48s
302: Created bundle with 20 fields in 0.47s
303: Created bundle with 20 fields in 0.47s
304: Created bundle with 20 fields in 0.48s
305: Created bundle with 20 fields in 0.65s
306: Created bundle with 20 fields in 0.47s
307: Created bundle with 20 fields in 0.48s
308: Created bundle with 20 fields in 0.48s
309: Created bundle with 20 fields in 0.47s
310: Created bundle with 20 fields in 0.53s
...
400: Created bundle with 20 fields in 0.54s
401: Created bundle with 20 fields in 0.54s
402: Created bundle with 20 fields in 0.55s
403: Created bundle with 20 fields in 0.55s
404: Created bundle with 20 fields in 0.55s
405: Created bundle with 20 fields in 0.79s
406: Created bundle with 20 fields in 0.54s
407: Created bundle with 20 fields in 0.56s
408: Created bundle with 20 fields in 0.55s
409: Created bundle with 20 fields in 0.54s
410: Created bundle with 20 fields in 0.78s
...
490: Created bundle with 20 fields in 0.65s
491: Created bundle with 20 fields in 0.63s
492: Created bundle with 20 fields in 0.66s
493: Created bundle with 20 fields in 0.9s
494: Created bundle with 20 fields in 0.66s
495: Created bundle with 20 fields in 0.67s
496: Created bundle with 20 fields in 0.65s
497: Created bundle with 20 fields in 0.66s
498: Created bundle with 20 fields in 0.66s
499: Created bundle with 20 fields in 0.93s
500: Created 100 bundle with 20 fields each in 231.32s

In HEAD and the patch on that issue by the time we're at 300 bundles we're well over 10 secs. Maybe this should be critical?

dawehner’s picture

+++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
@@ -60,4 +67,137 @@ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
+    foreach ($entity_type->getLookups() as $lookup_key => $type) {

Its a bit odd that contrib can't easily expand the lookup keys, not sure whether its worth to worry about, given its already quite a good improvement.

catch’s picture

Priority: Major » Critical

Yes since this affects any config entity you could easily run into it with combinations like views + aggregator + fields/bundles, and this is seconds of savings rather than milliseconds, so bumping to critical.

berdir’s picture

  1. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -60,4 +67,137 @@ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
    +      foreach($this->getKeys($lookup_key, $config) as $key) {
    +        if ($type == 'UNIQUE') {
    +          $config_key_store->set($key, $config->getName());
    +        }
    +        else {
    +          if ($type == 'NOT UNIQUE') {
    +            $values = $config_key_store->get($key, []);
    +            if (!in_array($config->getName(), $values, TRUE)) {
    +              $values[] = $config->getName();
    +              $config_key_store->set($key, $values);
    +            }
    +          }
    +        }
    

    NOT UNIQUE is a bit a special name for this, it sounds like it *must* not be unique (like assertNotUniqueText()).

    Do we really need this additional complexity? We're not actually enforcing that it is UNIQUE..

    Maybe we should just always allow multiple keys internally?

  2. +++ b/core/modules/block/src/Entity/Block.php
    @@ -38,6 +38,9 @@
    + *   },
    + *   lookups = {
    + *     "theme" = "NOT UNIQUE"
      *   }
    

    So this would speed up #2479459: BlockRepository::getVisibleBlocksPerRegion() does an uncached entity query on every request as well, but adding caching there still makes sense I think.

alexpott’s picture

In IRC @chx suggested looking at his plan for XML - because of MySQL can do optimised queries on XML - see #2162161: Change DatabaseStorage to use XML instead of serialize. I'm reluctant because of earlier issues with config and XML (but I could be convinced with tests) and also (and probably more importantly) that it's being put forward as a MySQL only optimisation.

andypost’s picture

Also it could seriously affects UI (machine name checks) with related #2091871: Add an ConfigEntityForm with an exists() method

alexpott’s picture

re #15. It looks like removing the unique code is costly. Trying to work out why.

Without the unique/non unique code

50: Created bundle with 20 fields in 0.63s
51: Created bundle with 20 fields in 0.64s
52: Created bundle with 20 fields in 0.65s
53: Created bundle with 20 fields in 0.7s
54: Created bundle with 20 fields in 0.67s
55: Created bundle with 20 fields in 0.68s
56: Created bundle with 20 fields in 0.7s
57: Created bundle with 20 fields in 0.76s
58: Created bundle with 20 fields in 0.71s
59: Created bundle with 20 fields in 0.71s

With it...

50: Created bundle with 20 fields in 0.25s
51: Created bundle with 20 fields in 0.23s
52: Created bundle with 20 fields in 0.22s
53: Created bundle with 20 fields in 0.22s
54: Created bundle with 20 fields in 0.23s
55: Created bundle with 20 fields in 0.26s
56: Created bundle with 20 fields in 0.25s
57: Created bundle with 20 fields in 0.24s
58: Created bundle with 20 fields in 0.22s
59: Created bundle with 20 fields in 0.23s
60: Created bundle with 20 fields in 0.27s
andypost’s picture

Related issues: +#2472709: PostgreSQL: Support JSON data type

Another way to manage uniqueness, so it needs to be swappable

alexpott’s picture

Issue tags: -needs profiling +Needs tests
StatusFileSize
new7.66 KB
new22.5 KB

Found out why... doing

diff --git a/core/lib/Drupal/Core/Config/Entity/Query/Query.php b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
index 575151b..47c73f1 100644
--- a/core/lib/Drupal/Core/Config/Entity/Query/Query.php
+++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
@@ -134,17 +134,15 @@ protected function loadRecords() {
             }, $ids);
           }
           elseif (in_array($condition['field'], $lookups)) {
+            // If we don't find anything then there are no matches. No point in
+            // listing anything.
+            $names = array();
             $keys = (array) $condition['value'];
             $keys = array_map(function ($value) use ($condition) {
               return $condition['field'] . ':' . $value;
             }, $keys);
             foreach ($this->configKeyStore->getMultiple($keys) as $list) {
-              if (is_array($names)) {
-                $names = array_merge($names, $list);
-              }
-              else {
-                $names = $list;
-              }
+              $names = array_merge($names, $list);
             }
           }
         }
@@ -152,7 +150,7 @@ protected function loadRecords() {
         // case where there are additional restricting conditions, results
         // will be eliminated when the conditions are checked on the loaded
         // records.
-        if ($names) {
+        if ($names !== FALSE) {
           break;
         }
       }

Fixed it.

490: Created bundle with 20 fields in 0.6s
491: Created bundle with 20 fields in 0.63s
492: Created bundle with 20 fields in 0.62s
493: Created bundle with 20 fields in 0.61s
494: Created bundle with 20 fields in 0.86s
495: Created bundle with 20 fields in 0.63s
496: Created bundle with 20 fields in 0.62s
497: Created bundle with 20 fields in 0.62s
498: Created bundle with 20 fields in 0.63s
499: Created bundle with 20 fields in 0.89s
500: Created 100 bundle with 20 fields each in 221.2s

So just as fast with this patch. And in fact non unique look ups will be faster when they return no matches... for example the block lookup :)

alexpott’s picture

re #19 but that's only if we go for database specific solution - the solution in this issue is database agnostic.

alexpott’s picture

StatusFileSize
new5.36 KB
new23.48 KB

We have a ConfigEntityTypeInterface - we need tests now.

berdir’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
    @@ -146,4 +154,11 @@ protected function checkStorageClass($class) {
    +   */
    +  public function getDenormalizedKeys() {
    +    return $this->lookups;
    +  }
    

    lookup vs denormalized keys is a bit confusing, why go for a completely different name here?

    Does denormalized really make sense here, is that really what we're it means? The thing is that it's not actually typical denormalization that we're doing here.

    What about LookupKeys(), for both the method and property? That's what it means.. having a separate lookup mechanism for those.

  2. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php
    @@ -61,4 +61,12 @@
    +  /**
    +   * Gets the keys that are denormalized into a key value store for fast lookup.
    

    The description seems very specific, that we are using key value is kind of an implementation detail and not relevant here?

  3. +++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
    @@ -34,12 +48,16 @@ class Query extends QueryBase implements QueryInterface {
        */
    -  function __construct(EntityTypeInterface $entity_type, $conjunction, ConfigFactoryInterface $config_factory, array $namespaces) {
    +  function __construct(EntityTypeInterface $entity_type, $conjunction, ConfigFactoryInterface $config_factory, KeyValueStoreInterface $key_value, array $namespaces) {
         parent::__construct($entity_type, $conjunction, $namespaces);
         $this->configFactory = $config_factory;
    +    $this->configKeyStore = $key_value;
       }
     
    

    As disussed yesterday in IRC, again the question of injecting they key value factory or the collection. In this case, it's probably more likely that we will use it.

  4. +++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
    @@ -108,37 +126,44 @@ protected function loadRecords() {
    +            $keys = (array) $condition['value'];
    +            $keys = array_map(function ($value) use ($condition) {
    +              return $condition['field'] . ':' . $value;
    +            }, $keys);
    +            foreach ($this->configKeyStore->getMultiple($keys) as $list) {
    +              $names = array_merge($names, $list);
    +            }
    

    There's an issue to also support partial (STARTS_WITH, CONTAINS...) matches on the ID. That would be very useful to speed up thinks like field_entity_bundle_field_info(). I'm wondering if that would also be useful here, but I guess not so much.

    That issue is #2247379: Optimize config entity query conditions on ID and it's probably going to conflict badly with this.

  5. +++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
    @@ -108,37 +126,44 @@ protected function loadRecords() {
    +        // We stop at the first restricting condition on name. In the (weird)
    +        // case where there are additional restricting conditions, results
    

    weird => rare? It's not *that* weird to e.g. have an entity query on entity_type + bundle for example, although for fields/displays, we mostly optimize it to load on the id.

  6. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -60,4 +68,141 @@ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
    +  protected function updateConfigKeyStore(ConfigEntityTypeInterface $entity_type, Config $config) {
    ...
    +  protected function deleteConfigKeyStore(ConfigEntityTypeInterface $entity_type, Config $config) {
    

    We will need a way to initialize they lookup collection, should we make those methods public for that purpose?

  7. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -60,4 +68,141 @@ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
    +  /**
    +   * @todo
    +   *
    +   * @param $key
    

    :)

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new25.02 KB

Thanks @Berdir - re #23

  1. Fixed
  2. Fixed
  3. Yep - I agree though that doing as little as possible when creating things is a good thing. Fixed
  4. Indeed it will
  5. This is actually in HEAD - removing the (weird).
  6. This is tricky because this is a config object not a configuration entity object. Not sure what to do here. Maybe we could add a public method that accepts a configuration entity?
  7. Wow this is harder @todo after not having thought about this code for awhile.

Still need to add tests and hopefully that'll help me improve the documentation I added to address @Berdir's (7).

I also realised that we could remove the condition if Query::loadRecords() handles it. This should result in less looping and improved performance as well.

490: Created bundle with 20 fields in 0.65s
491: Created bundle with 20 fields in 0.64s
492: Created bundle with 20 fields in 0.69s
493: Created bundle with 20 fields in 0.64s
494: Created bundle with 20 fields in 0.91s
495: Created bundle with 20 fields in 0.7s
496: Created bundle with 20 fields in 0.62s
497: Created bundle with 20 fields in 0.63s
498: Created bundle with 20 fields in 0.63s
499: Created bundle with 20 fields in 1s
500: Created 100 bundle with 20 fields each in 231.94s

No performance regression in this patch compared to the most recent runs. I wouldn't expect the condition removal to speed up this query because we're checking for non-existence.

Status: Needs review » Needs work

The last submitted patch, 24: 2430219.24.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new12.07 KB
new25.18 KB

Ignore the patch in #24

alexpott’s picture

StatusFileSize
new6.56 KB
new30.3 KB

Added some tests for the QueryFactory::getKeys() and QueryFactory::getValues(). Although they are protected considering the recursive nature and the ability to use wildcards extremely worth testing.

490: Created bundle with 20 fields in 0.63s
491: Created bundle with 20 fields in 0.63s
492: Created bundle with 20 fields in 0.65s
493: Created bundle with 20 fields in 0.91s
494: Created bundle with 20 fields in 0.63s
495: Created bundle with 20 fields in 0.65s
496: Created bundle with 20 fields in 0.62s
497: Created bundle with 20 fields in 0.64s
498: Created bundle with 20 fields in 0.63s
499: Created bundle with 20 fields in 0.91s
500: Created 100 bundle with 20 fields each in 227.52s

The changes have no impact on performance (as expected).

Status: Needs review » Needs work

The last submitted patch, 27: 2430219.27.patch, failed testing.

berdir’s picture

  1. +++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
    @@ -129,8 +137,9 @@ protected function loadRecords() {
    +      foreach ($conditions as $condition_key =>$condition) {
    

    missing space ;)

  2. +++ b/core/lib/Drupal/Core/Config/Entity/Query/Query.php
    @@ -175,4 +186,17 @@ protected function loadRecords() {
    +  protected function getConfigKeyStore() {
    +    if (!isset($this->configKeyStore)) {
    +      $this->configKeyStore = $this->keyValueFactory->get(QueryFactory::CONFIG_LOOKUP_PREFIX . $this->entityTypeId);
    +    }
    +    return $this->configKeyStore;
    +  }
    

    Note that the key value factory already maintains a "static" cache, so doing this here again shouldn't be necessary, the overhead should be negligible.

    Having a method is still useful, I should probably add that to entity manager, to avoid repeating the collection string many times.

  3. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -129,12 +134,18 @@ protected function deleteConfigKeyStore(ConfigEntityTypeInterface $entity_type,
        * @param $key
    +   *   The configuration key to look for.
    

    might be fixed already, missing "string".

  4. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -143,16 +154,23 @@ protected function getKeys($key, Config $config, $get_method = 'get') {
    +   *   Which position of $parts we are pocessing.
    

    typo in pocessing.

+++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
@@ -145,12 +145,34 @@ protected function deleteConfigKeyStore(ConfigEntityTypeInterface $entity_type,
+   * @throws \LogicException
+   *   Keys ending with a wildcard make no sense since you can not query against
+   *   this.

maybe restructure a bit to say that exception is thrown when such a key is passed in?

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new8.22 KB
new30.08 KB

re #29 - fixed everything thanks!

And reordered params to be consistent and less confusing.

Status: Needs review » Needs work

The last submitted patch, 30: 2430219.30.patch, failed testing.

alexpott’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new4.9 KB
new33.47 KB

Fixed fails... silly alexpott.

And added an integration test - which caught the fact I wasn't handling deletes correctly.

I think we have the necessary test coverage.

Status: Needs review » Needs work

The last submitted patch, 32: 2430219.32.patch, failed testing.

Status: Needs work » Needs review

alexpott queued 32: 2430219.32.patch for re-testing.

alexpott’s picture

StatusFileSize
new10.2 KB
new26.06 KB

Did my own review of the patch and found some unnecessary changes.

berdir’s picture

Issue tags: +D8 upgrade path

I think this is close.

A few nitpicks (sorry) was all I could find. There's still the upgrade path question.

The only thing I'm not sure about is whether this should somehow be more pluggable. But given that AFAIK the query service itself is pluggable, someone could just replace that if they want to provide an alternative implementation?

  1. +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
    @@ -35,6 +35,13 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
    +   * @var array
    
    +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityTypeInterface.php
    @@ -61,4 +61,12 @@
    +   *
    +   * @return array
    +   *   The list of lookup keys.
    

    I think we document this as string[] now...

  2. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -60,4 +75,183 @@ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
    +  protected function updateConfigKeyStore(ConfigEntityTypeInterface $entity_type, Config $config) {
    

    I guess there's still the question of what to do with this for the upgrade path. I left @amateescu a tell, so he can have a look and decide himself what makes sense.

  3. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -60,4 +75,183 @@ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
    +   * @return array
    +   */
    +  protected function getValues(Config $config, $key, $get_method, array $parts, $start = 0) {
    

    Missing description.

  4. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -60,4 +75,183 @@ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
    +      if (!isset($parts[$start])) {
    +        // @todo consider throwing an exception?
    +        return NULL;
    +      }
    

    not exactly sure what that means, but we should probably either get rid of the todo by deciding what to do or have an issue reference as follow-up?

  5. +++ b/core/lib/Drupal/Core/Field/BaseFieldOverrideStorage.php
    @@ -8,6 +8,7 @@
     
     use Drupal\Core\Entity\EntityTypeInterface;
    +use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
    

    no other change in this file, looks like a left-over.

  6. +++ b/core/tests/Drupal/Tests/Core/Config/Entity/Query/QueryFactoryTest.php
    @@ -0,0 +1,132 @@
    +   * @param $name
    +   *   The config name.
    +   *
    +   * @return \Drupal\Core\Config\Config
    

    string missing for @param and description for @return.

alexpott’s picture

StatusFileSize
new8.74 KB
new26.93 KB

The interdiff won't apply to #35 because the patch needed a reroll but it does represent the changes I've made to address #36. Thanks for the review @Berdir.

  1. Fixed
  2. Maybe we should implement a method to rebuild for a particular entity type?
  3. Fixed
  4. I've done some more thinking about this and removed the @todo - if the configuration object does not have a value that corresponds to the key then the correct thing is that the config entity id should not have an entry in the fast lookup key value store for this key. That's what happens now.
  5. Fixed
  6. Fixed

Will work on implementing 2 afrer talking with @Berdir in IRC sometime.

Status: Needs review » Needs work

The last submitted patch, 37: 2430219.37.patch, failed testing.

berdir’s picture

  1. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -144,19 +144,21 @@ protected function deleteConfigKeyStore(ConfigEntityTypeInterface $entity_type,
    -   * @throws \LogicException
    +   * @throws InvalidLookupKeyException
    

    Should use the full namespace I think.

  2. +++ b/core/tests/Drupal/Tests/Core/Config/Entity/Query/QueryFactoryTest.php
    @@ -103,24 +102,31 @@ public function providerTestGetKeys() {
        * @param $name
        *   The config name.
    

    type still missing (@param string $name) :)

Test run was terminated somehow, lets try again...

Status: Needs work » Needs review

Berdir queued 37: 2430219.37.patch for re-testing.

alexpott’s picture

Issue tags: +Triaged D8 critical

Discussed with @catch, @effulgentsia, @webchick and @xjm. This meets the performance criteria for a critical. The performance improvement is measured in seconds once there are more than a few hundred configuration entities.

alexpott’s picture

Discussed the possible race conditions that this patch introduces with @catch, @berdir and @xjm.

+++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
@@ -60,4 +75,189 @@ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
+  protected function updateConfigKeyStore(ConfigEntityTypeInterface $entity_type, Config $config) {
+    $config_key_store = $this->getConfigKeyStore($entity_type);
+    foreach ($entity_type->getLookupKeys() as $lookup_key) {
+      foreach($this->getKeys($config, $lookup_key, 'get', $entity_type) as $key) {
+        $values = $config_key_store->get($key, []);
+        if (!in_array($config->getName(), $values, TRUE)) {
+          $values[] = $config->getName();
+          $config_key_store->set($key, $values);
+        }
+      }
+    }
+  }

If two configuration entities are being saved at the same time, for example blocks and they have the same theme key it's possible the only one with end up in the lookup. Currently on configuration entity save we have race conditions when saving the same entity - this does open possibilities of new race conditions but the solution of locking during a config save would be the same - opening #2485077: Add concurrent editing protection to configuration entities to discuss if we need to do something about this.

alexpott’s picture

StatusFileSize
new1.76 KB
new26.97 KB

Patches addresses #39. Thanks @Berdir.

berdir’s picture

Status: Needs review » Reviewed & tested by the community

I can't find anything to complain about anymore :) This is well tested and documented and provides huge performance improvements with a lot of configuration.

With the follow-up for the race conditions, I think this is RTBC.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Discussed the potential for race conditions. Since this is (mostly) pre-existing and we have a documented follow-up whereas we didn't before, I think that's OK. The probability of sites falling over due to long config lookups is higher than the probability of high-concurrency config writes causing a race condition.

Couple of whitespace issues I fixed on commit, otherwise I also couldn't find anything to complain about, and I'd like to see this in sooner than later just in case something does get flushed out before the next beta. So, committed/pushed to 8.0.x, thanks!

  1. +++ b/core/lib/Drupal/Core/Config/Entity/Query/QueryFactory.php
    @@ -60,4 +75,189 @@ public function getAggregate(EntityTypeInterface $entity_type, $conjunction) {
    +      foreach($this->getKeys($config, $lookup_key, 'get', $entity_type) as $key) {
    
    +++ b/core/modules/system/src/Tests/Entity/ConfigEntityQueryTest.php
    @@ -470,6 +471,64 @@ public function testCaseSensitivity() {
    +
    

    Nit, missing space. Fixed on commit.

  2. +++ b/core/modules/system/src/Tests/Entity/ConfigEntityQueryTest.php
    @@ -470,6 +471,64 @@ public function testCaseSensitivity() {
    +
    +
    

    Double line, fixed on commit.

Committed/pushed to 8.0.x, thanks!

  • catch committed 2b49f7b on 8.0.x
    Issue #2430219 by alexpott: Implement a key value store to optimise...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.