Problem/Motivation


https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Entity%21...

User entities don't have a langcode entity key.

When constructing a new user entity, we try to get the default langcode.

There's no entity key, so it loads all the field definitions to see if one is for langcode.

Then it doesn't find one, so sets it to LanguageInterface::LANGCODE_NOT_SPECIFIED

I only found this because I tried switching to this to fix #2479529: \Drupal\user\ContextProvider\CurrentUserContext::onBlockActiveContext() should avoid loading the anonymous user, but profiling shows it's significantly worse.

Proposed resolution

Shortcut all this for entity types that we know aren't translatable, without completely removing the ability to make them translatable again from contrib. Not sure the best approach for this.

A fix for user module would be to add a langcode entity key but force it to LANGCODE_NOT_SPECIFIED all the time, but that won't help other entities and it'd probably mean a schema change.

Remaining tasks

User interface changes

None.

API changes

Possibly a change to the User class/annotations.

Comments

dawehner’s picture

Title: User:getAnonymousUser() takes 13ms due to ConfigEntityBase::getDefaultLanguage() » User:getAnonymousUser() takes 13ms due to ContentEntityBase::getDef\Drupal\Core\Entity\ContentEntityBase::\Drupal\Core\Entity\ContentEntityBase::setDefaultLangcode()

Shortcut all this for entity types that we know aren't translatable, without completely removing the ability to make them translatable again from contrib. Not sure the best approach for this.

Well, in case the logic is in the content entity class, people could, and we maybe should try to make that easy, override those methods to make it translatable again.

dawehner’s picture

Title: User:getAnonymousUser() takes 13ms due to ContentEntityBase::getDef\Drupal\Core\Entity\ContentEntityBase::\Drupal\Core\Entity\ContentEntityBase::setDefaultLangcode() » User:getAnonymousUser() takes 13ms due to ContentEntityBase::setDefaultLangcode()
andypost’s picture

The related #2345611-70: Load user entity in Cookie AuthenticationProvider instead of using manual queries shows that we need language to access roles in authentication
suppose we need optimize access to entity values always providing langcode

yesct’s picture

Issue tags: +D8MI

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

catch’s picture

Version: 8.1.x-dev » 8.3.x-dev
Component: base system » entity system
Issue tags: +Triaged core major

Discussed with xjm, alexpott and effulgentsia and we triaged this as major due to the performance impact.

berdir’s picture

Status: Active » Needs review
StatusFileSize
new928 bytes

Can we just do this?

I didn't profile this, but I verified that it did go into the first if().

jibran’s picture

Title: User:getAnonymousUser() takes 13ms due to ContentEntityBase::setDefaultLangcode() » User::getAnonymousUser() takes 13ms due to ContentEntityBase::setDefaultLangcode()
+++ b/core/modules/user/src/Entity/User.php
@@ -7,6 +7,7 @@
+use Drupal\Core\Language\Language;

This is not needed.

berdir’s picture

StatusFileSize
new766 bytes
jibran’s picture

Status: Needs review » Needs work
Issue tags: +needs profiling, +Needs tests

I think we should add some tests for this and profile the patch.

berdir’s picture

There is no functional change here, it results in exactly the same behavior, just in a directer way, so not sure what you want to test?

fabianx’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs tests

I agree and it is pretty clear this saves performance - though it would be nice to see how much.

alexpott’s picture

I thought about requiring a unit test here. The best I could come with is:

<?php

namespace Drupal\Tests\user\Unit\Entity;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\EntityManager;
use Drupal\Core\Entity\EntityType;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\user\Entity\User;


/**
 * @coversDefaultClass \Drupal\user\Entity\User
 * @group user
 */
class UserTest extends UnitTestCase {

  /**
   * @covers ::getAnonymousUser
   */
  public function testGetAnonymousUser() {
    $entity_type = $this->prophesize(EntityType::class);
    $entity_type->getClass()->willReturn(TestUser::class);
    $entity_type->id()->willReturn('user');
    $entity_manager = $this->prophesize(EntityManager::class);
    $entity_manager->getDefinition('user')->willReturn($entity_type->reveal());
    $container = new ContainerBuilder();
    $container->set('entity.manager', $entity_manager->reveal());
    \Drupal::setContainer($container);

    $anonymous = TestUser::getAnonymousUser();
    $this->assertEquals($anonymous->getValues()['langcode'], [LanguageInterface::LANGCODE_DEFAULT => LanguageInterface::LANGCODE_NOT_SPECIFIED]);
  }

}

/**
 * A test user entity to allow testing of the initialised values.
 */
class TestUser extends User {

  public function __construct(array $values, $entity_type) {
    $this->values = $values;
  }

  public function getValues() {
    return $this->values;
  }

  public function __clone() {
    // Don't do \Drupal\user\Entity\User::__clone().
  }
}

I think this might be a bit heavy on testing implementation and not behaviour.

alexpott’s picture

On php 5.6 on a page with 1 anonymous comment I see very small improvements...

Drupal\user\Entity\User::getAnonymousUser Run #57f2c0d9315d9 Run #57f2c11023956 Diff Diff%
Number of Function Calls 2 2 0 0.0%
Incl. Wall Time (microsec) 511 237 -274 -53.6%
Incl. Wall Time (microsec) per call 256 119 -137 -53.6%
Excl. Wall Time (microsec) 16 16 0 0.0%
Incl. MemUse (bytes) 44,320 32,624 -11,696 -26.4%
Incl. MemUse (bytes) per call 22,160 16,312 -5,848 -26.4%
Excl. MemUse (bytes) 4,584 4,928 344 7.5%
Incl. PeakMemUse (bytes) 25,096 22,576 -2,520 -10.0%
Incl. PeakMemUse (bytes) per call 12,548 11,288 -1,260 -10.0%
Excl. PeakMemUse (bytes) 200 200 0 0.0%
alexpott’s picture

Version: 8.3.x-dev » 8.2.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

These improvements will grow with more anonymous comments therefore I'm going to commit this to 8.3.x and set to patch to be ported for 8.2.x. I've credit everyone who did any performance profiling on the issue.

Committed 05705fc and pushed to 8.3.x. Thanks!

  • alexpott committed 05705fc on 8.3.x
    Issue #2504849 by Berdir, catch, alexpott: User::getAnonymousUser()...

  • catch committed bf8c0b1 on 8.2.x authored by alexpott
    Issue #2504849 by Berdir, catch, alexpott: User::getAnonymousUser()...
catch’s picture

Status: Patch (to be ported) » Fixed

Cherry-picked to 8.2.x

Status: Fixed » Closed (fixed)

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