Unverified Commit 31c0baa3 authored by Alex Pott's avatar Alex Pott
Browse files

perf: #3537962 EntityFieldManager::getFieldDefinitions() per-bundle caching can be expensive

By: catch
By: berdir
parent 077d6d5b
Loading
Loading
Loading
Loading
+32 −6
Original line number Diff line number Diff line
@@ -359,18 +359,44 @@ public function getFieldDefinitions($entity_type_id, $bundle) {
      $cid = 'entity_bundle_field_definitions:' . $entity_type_id . ':' . $bundle . ':' . $langcode;
      if ($cache = $this->cacheGet($cid)) {
        $bundle_field_definitions = $cache->data;
      }
      else {
        // Rebuild the definitions and put it into the cache.
        $bundle_field_definitions = $this->buildBundleFieldDefinitions($entity_type_id, $bundle, $base_field_definitions);
        $this->cacheSet($cid, $bundle_field_definitions, Cache::PERMANENT, ['entity_types', 'entity_field_info']);
      }
        // Field definitions consist of the bundle specific overrides and the
        // base fields, merge them together. Use array_replace() to replace base
        // fields with by bundle overrides and keep them in order, append
        // additional by bundle fields.
        $this->fieldDefinitions[$entity_type_id][$bundle][$langcode] = array_replace($base_field_definitions, $bundle_field_definitions);
      }
      else {
        // In some cases, entity types can have dozens or hundreds of bundles,
        // so attempt to rebuild all of the bundle data in one go. We assume
        // that if field data is being requested for one bundle, that it will
        // also be requested for other bundles of the same entity later in the
        // request. For example when views field data is built.
        $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id);
        unset($bundles[$bundle]);
        $cache_data = [];

        // Always generate field data for the bundle that is passed in, even if
        // the bundle does not exist.
        // @todo remove support for non-existing bundles.
        // @see https://www.drupal.org/project/drupal/issues/3565232
        $cid = 'entity_bundle_field_definitions:' . $entity_type_id . ':' . $bundle . ':' . $langcode;
        $bundle_field_definitions = $this->buildBundleFieldDefinitions($entity_type_id, $bundle, $base_field_definitions);
        $cache_data[$cid] = ['data' => $bundle_field_definitions, 'tags' => ['entity_types', 'entity_field_info']];
        $this->fieldDefinitions[$entity_type_id][$bundle][$langcode] = array_replace($base_field_definitions, $bundle_field_definitions);

        foreach ($bundles as $bundle_name => $bundle_info) {
          $cid = 'entity_bundle_field_definitions:' . $entity_type_id . ':' . $bundle_name . ':' . $langcode;
          // Rebuild the definitions and put it into the cache.
          $bundle_field_definitions = $this->buildBundleFieldDefinitions($entity_type_id, $bundle_name, $base_field_definitions);
          $cache_data[$cid] = ['data' => $bundle_field_definitions, 'tags' => ['entity_types', 'entity_field_info']];
          $this->fieldDefinitions[$entity_type_id][$bundle_name][$langcode] = array_replace($base_field_definitions, $bundle_field_definitions);
        }

        if ($cache_data && $this->useCaches) {
          $this->cacheBackend->setMultiple($cache_data);
        }
      }
    }
    return $this->fieldDefinitions[$entity_type_id][$bundle][$langcode];
  }

+3 −3
Original line number Diff line number Diff line
@@ -129,11 +129,11 @@ protected function doTestNodePageAdministrator(): void {

    $expected = [
      'QueryCount' => 304,
      'CacheGetCount' => 311,
      'CacheGetCount' => 301,
      'CacheGetCountByBin' => [
        'config' => 66,
        'bootstrap' => 16,
        'discovery' => 95,
        'discovery' => 85,
        'data' => 23,
        'entity' => 25,
        'dynamic_page_cache' => 1,
@@ -141,7 +141,7 @@ protected function doTestNodePageAdministrator(): void {
        'render' => 39,
        'menu' => 24,
      ],
      'CacheSetCount' => 323,
      'CacheSetCount' => 313,
      'CacheDeleteCount' => 0,
      'CacheTagInvalidationCount' => 0,
      'CacheTagLookupQueryCount' => 30,
+2 −2
Original line number Diff line number Diff line
@@ -53,8 +53,8 @@ protected function testFrontPageColdCache(): void {

    $expected = [
      'QueryCount' => 218,
      'CacheGetCount' => 276,
      'CacheSetCount' => 294,
      'CacheGetCount' => 267,
      'CacheSetCount' => 285,
      'CacheDeleteCount' => 0,
      'CacheTagLookupQueryCount' => 25,
      'CacheTagInvalidationCount' => 0,
+2 −2
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ protected function testNodePageColdCache(): void {

    $expected = [
      'QueryCount' => 233,
      'CacheSetCount' => 281,
      'CacheSetCount' => 271,
      'CacheDeleteCount' => 0,
      'CacheTagLookupQueryCount' => 24,
      'CacheTagInvalidationCount' => 0,
@@ -119,7 +119,7 @@ protected function testNodePageCoolCache(): void {
    $expected = [
      'QueryCount' => 82,
      'CacheGetCount' => 173,
      'CacheSetCount' => 61,
      'CacheSetCount' => 60,
      'CacheDeleteCount' => 0,
      'CacheTagInvalidationCount' => 0,
      'CacheTagLookupQueryCount' => 20,
+19 −11
Original line number Diff line number Diff line
@@ -241,6 +241,11 @@ public function testGetBaseFieldDefinitions(): void {
   * @legacy-covers ::buildBundleFieldDefinitions
   */
  public function testGetFieldDefinitions(): void {
    $this->entityTypeBundleInfo->getBundleInfo('test_entity_type')->willReturn([
      'test_entity_bundle' => 'test_entity_bundle',
      'test_entity_bundle_class' => 'test_entity_bundle_class',
      'some_other_bundle' => 'some_other_bundle',
    ])->shouldBeCalled();
    $field_definition = $this->setUpEntityWithFieldDefinition();

    $bundle_field_definition = $this->prophesize()
@@ -451,28 +456,28 @@ public function testGetBaseFieldDefinitionsWithCaching(): void {
   * Tests the getFieldDefinitions() method with caching.
   */
  public function testGetFieldDefinitionsWithCaching(): void {
    $this->entityTypeBundleInfo->getBundleInfo('test_entity_type')->willReturn([
      'test_bundle' => 'test_bundle',
    ])->shouldBeCalled();
    $field_definition = $this->setUpEntityWithFieldDefinition(FALSE, 'id');

    $expected = ['id' => $field_definition];

    $cacheBackend = $this->cacheBackend;
    $this->cacheBackend->get('entity_base_field_definitions:test_entity_type:en')
      ->willReturn((object) ['data' => $expected])
      ->shouldBeCalledTimes(2);
    $this->cacheBackend->get('entity_bundle_field_definitions:test_entity_type:test_bundle:en')
      ->willReturn(FALSE)
      ->shouldBeCalledTimes(1);
    $this->cacheBackend->set('entity_bundle_field_definitions:test_entity_type:test_bundle:en', Argument::any(), Cache::PERMANENT, [
      ->shouldBeCalledTimes(2);
    $this->cacheBackend->setMultiple([
      'entity_bundle_field_definitions:test_entity_type:test_bundle:en' => [
        'data' => [],
        'tags' => [
          'entity_types',
          'entity_field_info',
    ])
      ->will(function (array $args) use ($cacheBackend): void {
        $data = (object) ['data' => $args[1]];
        $cacheBackend->get('entity_bundle_field_definitions:test_entity_type:test_bundle:en')
          ->willReturn($data)
          ->shouldBeCalled();
      })
      ->shouldBeCalled();
        ],
      ],
    ])->shouldBeCalledTimes(2);

    $this->assertSame($expected, $this->entityFieldManager->getFieldDefinitions('test_entity_type', 'test_bundle'));
    $this->entityFieldManager->testClearEntityFieldInfo();
@@ -551,6 +556,9 @@ public function testGetBaseFieldDefinitionsInvalidDefinition(): void {
   * @legacy-covers ::buildBundleFieldDefinitions
   */
  public function testGetFieldDefinitionsProvider(): void {
    $this->entityTypeBundleInfo->getBundleInfo('test_entity_type')->willReturn([
      'test_bundle' => 'test_bundle',
    ])->shouldBeCalled();
    $this->setUpEntityWithFieldDefinition(TRUE);

    $module = 'entity_field_manager_test_module';