Problem/Motivation

When user entity base fields 'access' and 'login' are changing their values cache associated with user cache tags is not cleared. There's a good reason for this. If the cache associated with user cache tags would be invalidated on every change of 'access', this will degrade the performance because that cache will be cleared too often.

However, for this reason, the pages where these values are displayed (like /admin/people view page) are displaying stale content.

Proposed resolution

Display the 2 fields using formatters that are lazy rendering their values.

Remaining tasks

None.

User interface changes

None.

API changes

New field type 'user_timestamp' —­ an unmodified extension of 'timestamp'.

New field formatters 'user_timestamp' (extending 'timestamp') and 'user_timestamp_ago' (extending 'user_timestamp_ago'). The new formatters have the same output bu are using lazy rendering.

Data model changes

The user entity base fields are no more fields of type 'timestamp' but of the new 'user_timestamp' type.

Views or displays having user entity 'access' or 'login' fields and using the 'timestamp' or 'timestamp_ago' formatters will use the new 'user_timestamp' and 'user_timestamp_ago' new formatters.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

proxiss created an issue. See original summary.

proxiss’s picture

Issue summary: View changes
cilefen’s picture

Title: /admin/people content get's cached » /admin/people content is cached
claudiu.cristea’s picture

Assigned: Unassigned » Wim Leers
Status: Active » Needs review
FileSize
1.27 KB

This should fix the issue, can you try?

But keep in mind that the "last access" time is refreshing NO often than 180 sec (3 mins). You can lower the value by adding this in settings.php:

// This updates 'last access' time every 5 secs.
$settings['session_write_interval'] = 5;

For testing purposes you can do this.

While it's a caching issue I would like to pass this issue to @Wim Leers. Also a question for Wim: Shouldn't we trigger the cache invalidation [Cache::invalidateTags()] inside the entity internal cache invalidation? I mean when $this->resetCache(array($account->id())) is called, subsequently, the internal cache mechanism can also call Cache::invalidateTags(), instead of:

  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
    $this->invalidateTagsOnSave($update);
  }
claudiu.cristea’s picture

Title: /admin/people content is cached » Updating user last access and last login time doesn't invalidate user cache tags
Version: 8.0.2 » 8.0.x-dev
Issue summary: View changes
Issue tags: -cache +D8 cacheability, +Needs tests

Fixed title, summary, version. and yes, needs tests.

claudiu.cristea’s picture

Issue summary: View changes

Status: Needs review » Needs work

The last submitted patch, 4: 2650768-4.patch, failed testing.

claudiu.cristea’s picture

Title: Updating user last access and last login time doesn't invalidate user cache tags » The cache for user last access and login timestamps doesn't invalidate
Status: Needs work » Needs review
Issue tags: +Needs upgrade path, +Needs upgrade path tests
FileSize
9.83 KB

Definitely, cacheability it's not an easy topic :)

So I guess that #4 is wrong because will invalidate the user cache too often. That's why I think this case fits better to auto-placeholdering. Here's a experimental patch but take into consideration following notes:

  • The new 'user_timestamp' is still cached. It doesn't work as expected.
  • The patch is missing an upgrade path for user entity definition and a post_update to change the formatters for user 'access' and 'login' base fields in all entity view displays and all views.
  • There's a workaround in \Drupal\Core\Render\Renderer. Without that it doesn't work. I don't understand how to debug that. Is that a bug in Renderer?

A side-note: I think formatters and widgets should receive (maybe from their $this->fieldDefinition?) a flag that tells formatter if to build normally or lazy the output. For example, the definition for user 'access' base field could be:

  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
    ...
    $fields['access'] = BaseFieldDefinition::create('user_timestamp')
      ->setLabel(t('Last access'))
      ...
      ->setRenderLazy(TRUE);
    ...
    return $fields;
  }

Then the same formatter can handle both cases depending on this bool without needing for a new formatter.

Status: Needs review » Needs work

The last submitted patch, 8: 2650768-8.patch, failed testing.

claudiu.cristea’s picture

Status: Needs work » Needs review
Issue tags: -Needs upgrade path, -Needs upgrade path tests
FileSize
15.5 KB
12.38 KB

Fixed more points, added upgrade path (with test).

Status: Needs review » Needs work

The last submitted patch, 10: 2650768-10.patch, failed testing.

claudiu.cristea’s picture

The IS should reflect #10.

claudiu.cristea’s picture

Issue summary: View changes
Status: Needs work » Needs review
Issue tags: -Needs issue summary update
FileSize
4.94 KB
16.99 KB

Fixed IS. Fixed tests.

Wim Leers’s picture

dawehner’s picture

Assigned: dawehner » Unassigned

Sounds like that indeed, with the formatter being fixed, we would not need those workarounds in the first place.

Wim Leers’s picture

Thanks for confirming. @claudiu.cristea, could you please review the patch on the other issue? You having worked on this patch makes you one of the best people possible to review the other patch :)

claudiu.cristea’s picture

@Wim Leers, @dawehner, I don't think they are the same issue. In this case the 'access' and 'login' values of user are cached forever (because their update doesn't pass trough entity save). The other patch seems to be the opposite. Here's not only about "time ago" (aka interval calculated on current time). I would review the other but I want first that we decide if these are the same issue.

mpdonadio’s picture

Even if this isn't a duplicate, I don't think the approach of adding a new formatter is correct (the same issue will come up lots pf places timestamps). It seems like a too specific solution to a generic problem. On the other patch, we are going down the road of calculating the cache-max age based on what the timeago will present back to the user. Another option I contemplated was whether we actually need a "request_time" cache context, which I think it the correct solution there (and yes, it would kill performance but would guarantee correct data being rendered).

Wim Leers’s picture

In this particular case, why not just allow log in/user creation to indeed invalidate the user entity's cache tag? That'd be the more consistent/simpler solution, wouldn't it?

It seems like these are simply wrong:

  /**
   * {@inheritdoc}
   */
  public function updateLastLoginTimestamp(UserInterface $account) {
    $this->database->update('users_field_data')
      ->fields(array('login' => $account->getLastLoginTime()))
      ->condition('uid', $account->id())
      ->execute();
    // Ensure that the entity cache is cleared.
    $this->resetCache(array($account->id()));
  }

  /**
   * {@inheritdoc}
   */
  public function updateLastAccessTimestamp(AccountInterface $account, $timestamp) {
    $this->database->update('users_field_data')
      ->fields(array(
        'access' => $timestamp,
      ))
      ->condition('uid', $account->id())
      ->execute();
    // Ensure that the entity cache is cleared.
    $this->resetCache(array($account->id()));
  }

Logging in definitely doesn't happen very often. Access does. But:

  public function onKernelTerminate(PostResponseEvent $event) {
    if ($this->account->isAuthenticated() && REQUEST_TIME - $this->account->getLastAccessedTime() > Settings::get('session_write_interval', 180)) {
      // Do that no more than once per 180 seconds.
      /** @var \Drupal\user\UserStorageInterface $storage */
      $storage = $this->entityManager->getStorage('user');
      $storage->updateLastAccessTimestamp($this->account, REQUEST_TIME);
    }
  }

It'll happen at most every 180 seconds.

Not sure at all, just trying to make sure we properly discuss that option, because it'd be by far the simplest.

mpdonadio’s picture

#19 sounds like the proper thing to do, and is one of the whole points of why we introduced cache contexts (so we don't need to manually muck around with invalidating caches).

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.

marcvangend’s picture

Status: Needs review » Needs work

I'm trying to understand what needs to be done here. At least comment #19sounds reasonable to me. But what would that mean for the patch in #13?

Also I doubt if we really need a user_timestamp formatter. If this formatter solves something, can we be sure it is only useful in the context of a user? If not, let's name it after what it does, not what it was created for initially.

The last submitted patch, 4: 2650768-4.patch, failed testing.

marcvangend’s picture

I talked with @claudiu.cristea about this at the Drupal Dev Days. We concluded that the first patch from #4 is exactly (or pretty close to) the solution that is proposed in #19... Or did we miss something, @WimLeers?

The patch from #4 does not apply anymore so it would need a re-roll.

marcvangend’s picture

Here's a re-roll of the patch in #4. It works fine as far as I can see (I mainly tested with the "Member for" timestamp on user/[uid]).

That said I'm still not sure if it's the best solution. It seems wrong to invalidate a cache tag when the data itself didn't change, but only the representation of that data is stale. Thoughts?

marcvangend’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 25: user_timestamp_cache-2650768-25.patch, failed testing.

The last submitted patch, 25: user_timestamp_cache-2650768-25.patch, failed testing.

Wim Leers’s picture

I was specifically referring to logging in and creating users being infrequent actions. For those use cases, invalidating cache tags seems acceptable. So IMO that's a good solution for the "login timestamp" mentioned in the issue title.

For the "last access timestamp", I'd use https://www.drupal.org/developing/api/8/cache/max-age — just specify a max-age that matches: $build['#cache']['max-age'] = Settings::get('session_write_interval', 180);. This will ensure that the last access timestamp is at most 3 minutes old… which is fine, since it's also updated at most every 3 minutes. Worst case, this means it will be 5 minutes and 59 seconds old.

This view is not designed to give a real-time dashboard. It's meant to administer users, and get a sense of which users have never logged in, have not logged in since months, etc. So somewhat stale data seems fine to me.


Of course, the ideal would be to never have stale data for any of this. But AFAIK Views retrieves the last access timestamp in an actual DB query. Which means that it's not possible to cache all fields in each row except that one, and have just that one be queried when displaying the view. That may be the ideal.

IMO this issue needs feedback/guidance from a Views maintainer. I've given my opinion as a de facto cache system maintainer.

Anonymous’s picture

I just came across this bug on one of our sites. It seems to me like if Drupal is going to display a "Member For" and "Last Access", they should be kept current. As-is, it can be rather confusing, and it misled us while we were debugging issues related to user activity earlier in the day.

Right now the page looks like a real-time dashboard, so either the numbers (including "Last Access") should be accurate, or the page shouldn't be displaying them in a format which makes them look real-time but which falls out of date. Would it help perhaps to show the actual last access date & time, rather than simply "time ago"? That might have the related benefit of giving minute-precision to times a day or longer ago, which it doesn't currently show on this page. I'm not super well-versed in this level of caching, though, so I'm not certain if that would work.

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

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

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

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

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

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

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

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

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

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

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

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

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

Version: 8.9.x-dev » 9.2.x-dev

Drupal 8 is end-of-life as of November 17, 2021. There will not be further changes made to Drupal 8. Bugfixes are now made to the 9.3.x and higher branches only. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.15 was released on June 1st, 2022 and is the final full bugfix release for the Drupal 9.3.x series. Drupal 9.3.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.4.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

I think we should open a separate issue to look at moving these fields out of base fields into a separate service. If we were adding them now I don't think we'd have them in the base table at all.

edit: here it is #3299948: Move access and login fields out of the user entity.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.9 was released on December 7, 2022 and is the final full bugfix release for the Drupal 9.4.x series. Drupal 9.4.x will not receive any further development aside from security fixes. Drupal 9 bug reports should be targeted for the 9.5.x-dev branch from now on, and new development or disruptive changes should be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.