node_access_grants() is called once per node during a request when node_access is implemented. For most implementations, this is a very cheap request. When implementations of hook_node_access_grants_alter() are expensive, however, the performance of the page suffers massively.

The primary assumption that I am making here is that node_access_grants() will never change within a single request for any given combination of $account and $op. That is what I would like confirmation on from others.

Locally, I am seeing node_access_grants() reduced from ~1.2s to ~60ms. This particular site has some custom checks that are expensive, but the $grants array is the same each time, so it appears there's no reason to call this multiple times.

Issue fork drupal-2199001

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

erikwebb’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, node_access_grants-static-cache.patch, failed testing.

moshe weitzman’s picture

If some implementation of this hook is expensive, that implementation should implement the cache. No sense in complicating core to defend against that, IMO.

erikwebb’s picture

Status: Needs work » Needs review
FileSize
1.41 KB
erikwebb’s picture

@moshe weitzman - I definitely agree. The main reason why I'm submitting this to core is that it seems the basic behavior of calling this on every single node for a request seems fundamentally flawed. Regardless of performance, this function is doing unnecessary work that can be definitively stopped upstream from any custom implementations.

Status: Needs review » Needs work

The last submitted patch, 4: node_access_grants-static-cache-3.patch, failed testing.

Wim Leers’s picture

I don't know the node grants system at all, but it doesn't seem logical that the same grants would apply to all nodes within a given page load, i.e. that the same grants apply to all nodes, given any $account & $op. It sounds like that is specific to your site?

Having read https://api.drupal.org/api/drupal/modules%21node%21node.api.php/function... & https://api.drupal.org/api/drupal/modules%21node%21node.api.php/function... seems to confirm my hypothesis: you can have multiple realms, and the various nodes on a page may belong to different realms, in which case there would be different $grants?

pwolanin’s picture

Version: 7.x-dev » 8.x-dev

@Wim - I think you are looking at the wrong side of the API. This is finding the grants for the given account "Fetches an array of permission IDs granted to the given user ID.", and should be the same every time it's called with the same parameters.

I think this is on the right track - the result should be the same on every call.

In terms of the patch: needs work. In 7+ we generally don't use a $reset param (that's what drupal_static() handles).

Also, bugs to 8.x first, for actually getting fixed.

Wim Leers’s picture

Ok, glad to be wrong :)

erikwebb’s picture

Version: 8.x-dev » 7.x-dev
Status: Needs work » Needs review
FileSize
1.26 KB

@pwolanin - I'll upload a cleaned patch for D7 for reference, then reset to D8 to fix.

erikwebb’s picture

The last submitted patch, 10: node_access_grants-static-cache-9.patch, failed testing.

pwolanin’s picture

Looks better

erikwebb’s picture

Version: 7.x-dev » 8.x-dev
Status: Needs review » Needs work

That looks good enough for me, since it's not being committed now anyhow. I'm re-assigning this to D8.

hefox’s picture

Threw the d7 one onto an openatrium site and it cut down the time a bit

maximpodorov’s picture

Version: 8.0.x-dev » 7.x-dev
Status: Needs work » Needs review
FileSize
2.48 KB

The patch is re-rolled for the current Drupal 7, and the important thing is added - static cache clearing upon node saving. If hook_node_insert() (or similar hook) implementations modify node grant records, this change will be invisible to node_access() without cache clearing.

mxr576’s picture

I've just had the same idea and I'm glad to see that I'm not the only one who thinking about this type of solution to improve the performance of a Drupal site.
Without this patch a view on a site which lists only 8 nodes calls a hook_node_grants() and a hook_node_grants_alter() implementation 8+8 times, with this patch, they only called 1+1!

Why this patch is still in the queue since 2014? On sites with complex RBAC this core fix could cause a huge performance improvement. I started to testing this and I'll give you a feedback if I'll find any issue with the latest patch.

ndobromirov’s picture

As the drupal_static has a default param, no need to duplicate the logic, just add a second param array().
I do not have environment at the moment to re-roll the patch. It will make it 4 lines shorter without any functional changes.

mxr576’s picture

Nice catch, totally true.

Anybody’s picture

Well I think this issue is still very important. Has someone been using this in production for a longer period of time?
I could imagine out of memory issues for very large permission tables. Any problems or updates on this?

mxr576’s picture

Using this on several production site with different hosting providers and PHP versions without noticing any problem.

Anybody’s picture

I finally solved my performance problems using this project: https://www.drupal.org/project/content_access_booster
It might be interesting for other users too, having performance problems because of access permissions.

Rob230’s picture

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

I think the way hook_node_grants() is called doesn't make any sense. It is passed a user, but is called per node. If there are 100 nodes on the page every module's implementation of hook_node_grants() will be called 100 times for the same user, and get the same result.

Surely it should be called once for the user? Or if not, then it should at least be statically cached as per this issue. The result doesn't vary per node, so why is it being called for every node?

As it is, I think I'll just have to statically cache my own implementation of hook_node_grants()... and probably apply additional caching beyond that, as the realms I have defined are unlikely to change unless the user is updated.

daniel.nitsche’s picture

Example of static caching hook_node_grants I've found in contrib: https://www.drupal.org/project/group/issues/3029849

Potentially facing a similar problem with the view_unpublished module: https://www.drupal.org/project/view_unpublished/issues/3097251

catch’s picture

Version: 8.9.x-dev » 8.8.x-dev
Priority: Normal » Major
Status: Needs review » Needs work
Issue tags: +Needs re-roll

This is still valid against 8.x/9.x and should still happen. 8.x code has not actually changed that much so should be a straightforward re-roll for someone.

apaderno’s picture

Issue tags: -Needs re-roll +Needs reroll
Neslee Canil Pinto’s picture

Status: Needs work » Needs review
FileSize
997 bytes

Rerolled the patch, please review

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.

BR0kEN’s picture

Version: 8.9.x-dev » 9.1.x-dev
Issue tags: -Needs reroll
FileSize
1.21 KB

Re-roll for 9.1.x.

  1. I've changed the structure from $cache[$uid][$op] to $cache[$op][$uid] because my tests shown better memory use with this structure.
    [catuser@ec2 public]$ php -v
    PHP 8.0.2 (cli) (built: Feb 11 2021 18:25:29) ( NTS )
    Copyright (c) The PHP Group
    Zend Engine v4.0.2, Copyright (c) Zend Technologies
        with Xdebug v3.0.3, Copyright (c) 2002-2021, by Derick Rethans
    [catuser@ec2 public]$ php test1.php
    [$uid][$op] needs 1981 KB of RAM
    [$op][$uid] needs 1593 KB of RAM
    
    BR0kEN@Macintosh:~/projects/drupal/docroot (9.1.x) $ php -v
    WARNING: PHP is not recommended
    PHP is included in macOS for compatibility with legacy software.
    Future versions of macOS will not include PHP.
    PHP 7.3.24-(to be removed in future macOS) (cli) (built: Dec 21 2020 21:33:25) ( NTS )
    Copyright (c) 1997-2018 The PHP Group
    Zend Engine v3.3.24, Copyright (c) 1998-2018 Zend Technologies
    BR0kEN@Macintosh:~/projects/drupal/docroot (9.1.x) $ php test1.php
    [$uid][$op] needs 1983 KB of RAM
    [$op][$uid] needs 1594 KB of RAM
    

    The test I used:

    <?php
    
    $nids = [1, 4];
    $uids = \range(1, 1000);
    $ops = [
      'view',
      'edit',
      'delete',
    ];
    
    $variants = [
      '[$uid][$op]' => [],
      '[$op][$uid]' => [],
    ];
    
    foreach ($uids as $uid) {
      foreach ($ops as $op) {
        $variants['[$uid][$op]'][$uid][$op] = $nids;
        $variants['[$op][$uid]'][$op][$uid] = $nids;
      }
    }
    
    $i = 0;
    foreach ($variants as $name => $data) {
      $file = "./variant$i.php";
    
      file_put_contents($file, implode(PHP_EOL, [
        '<?php',
        sprintf('$a = %s;', var_export($data, TRUE)),
        "printf('$name needs %d KB of RAM%s', memory_get_usage() / 1024, PHP_EOL);",
      ]));
    
      print shell_exec("php '$file'");
      unlink($file);
      $i++;
    }
    
  2. Another change is about calling $account->id(), which can be done just once instead of repeatedly executing the function that obviously returns the same result.

Status: Needs review » Needs work

The last submitted patch, 29: 2199001-29.patch, failed testing. View results

BR0kEN’s picture

Status: Needs work » Needs review
FileSize
4.42 KB
2.89 KB

#29 with adjusted tests.

BR0kEN’s picture

FileSize
4.49 KB
2.04 KB

#31 with a reduced code repetition.

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

Drupal 9.1.10 (June 4, 2021) and Drupal 9.2.10 (November 24, 2021) were the last bugfix releases of those minor version series. Drupal 9 bug reports should be targeted for the 9.3.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

BR0kEN’s picture

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.

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.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update, +Needs Review Queue Initiative

This issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.

This could use an issue summary update using the default template for steps to reproduce, proposed solution, and remaining tasks. To help the committers later.

Liam Morland’s picture

Version: 9.5.x-dev » 11.x-dev
Status: Needs work » Needs review
FileSize
4.49 KB

Re-roll

needs-review-queue-bot’s picture

Status: Needs review » Needs work

The Needs Review Queue Bot tested this issue.

While you are making the above changes, we recommend that you convert this patch to a merge request. Merge requests are preferred over patches. Be sure to hide the old patch files as well. (Converting an issue to a merge request without other contributions to the issue will not receive credit.)

Liam Morland’s picture

Status: Needs work » Needs review
Anybody’s picture

Thanks for the reroll @Liam Morland! Code LGTM! Could you perhaps provide it as MR so the tests can run and we can hopefully finish this soon?

Still has a "Needs issue summary update" tag.

Liam Morland’s picture

It is a MR; see #40.

Anybody’s picture

Sorry @Liam Morland shame on me, I didn't see #40 just #38 -.- Thank you!

smustgrave’s picture

Status: Needs review » Needs work

Have not tested but see it was previously tagged for issue summary which still appears to be needed. Recommend using standard issue template even if the section doesn't apply, leaving N/A helps reviewers more then it missing.