Problem/Motivation
In a recent profiling run (http://rpubs.com/msonnabaum/22598), we saw slowness in node_type_get_types() where a lot of time is spent in \Drupal\Core\Entity\Entity::getEntityTypeFromStaticClass. The page which generated this data has a table View so it didn't benefit from render cache (yet).
Proposed resolution
getEntityTypeFromClass() is moved to EntityManager class and simplified. EntityType class now has getOriginalClass() method.
Remaining tasks
None
User interface changes
None
API changes
Node types have traditionally been able to opt out of the standard node_access() behavior by setting the node_permissions_$type variable to 0 during hook_install. This feature has been removed. Any modules using this feature are encouraged to create own entity type where this control and more are readily available.
Comment | File | Size | Author |
---|---|---|---|
#40 | 2307681-40.patch | 31.61 KB | moshe weitzman |
#32 | entity-type-from-class-2307681-32.patch | 31.62 KB | cbr |
#4 | 53ce747b61426.getEntityTypeFromStaticClass.xhprof.gz | 108.08 KB | msonnabaum |
Comments
Comment #1
moshe weitzman CreditAttribution: moshe weitzman commentedAn XHProf of just this method - https://photos-1.dropbox.com/t/0/AAChEqNf6AXHw2a_i_LFDDIsG1PtKmaMD8kOTAH...
Comment #2
BerdirI guess when we added this we said it's not going to be called very often - ouch.
Looks like the calls are coming from node_permissions_get_configured_types(), which is called from node_node_access(). Which seems to be a pretty weird feature, I don't think that's documented in config schema? Or tested?
Anyway, it would be relatively easy to add a static cache to that method, with the risk that in some very rare scenarios (one of those I just looked at today) could mess things up.
We should possibly also statically cache node_permissions_get_configured_types(), because calling NodeType::loadMultiple() 150 times isn't going to be free. especially without config entity static cache.
Can you share what exactly was profiled or provide access to the full xhprof report?
Comment #3
BerdirAnother thing that might help a bit is removing the additional logic that we added to more explicitly deal with multiple exact matches on the entity class/type, which @fago wanted. Before, we returned on the first exact match, now we throw an exception if there's more than one, so we always have to loop over all of them.
Comment #4
msonnabaum CreditAttribution: msonnabaum commentedAttaching full xhprof. Not sure if this is the same run, but it's a similar one.
We could easily throw a cache on it, but I'd love to see if we could just simplify the whole process. That's a hell of a lot of work for what it's doing.
Comment #5
moshe weitzman CreditAttribution: moshe weitzman commentedLooking at node_permissions_get_configured_types, can we just say that all node types should be managed by permissions. If you want different behavior, you should create your own entity type. I feel like this function carries forward a bunch of old Drupal baggage.
Having said that, we still need to speed up node_type_get_types(). I can add a static cache there (and add a $reset param to the function?). I'd need some guidance on other solutions.
Comment #6
Berdir+1 to removing node_permissions_get_configured_types(), which will kill the calls to node_type_get_types() as far as the profiled page is concerned.
I'm not sure about simplifications for the method. As said, one approach would be to remove the nice exception for multiple exact matches, which actually breaks stuff now.
Another is that we now have enough usage of those to try and go back to msonnabaum's initial classname conversion approach to see if that works. Would imply that a bunch of classes (Like Term and Vocabulary) will need to override that method, which is something we could live with to get a performance boost.
Comment #7
Damien Tournoud CreditAttribution: Damien Tournoud commentedAlternatively, the bulk of the logic in
Entity::getEntityTypeFromStaticClass()
could be move to the entity manager (ie. something likeEntityManagerInterface::getEntityTypeFromClass($classname)
), where we could easily add caching.Comment #8
BerdirMoving it to the entity manager sounds like an interesting idea too.
This removes that function. Unsurprisingly, doesn't look like we have test coverage for this. Maybe this should be done in a separate issue?
Comment #9
BerdirComment #10
msonnabaum CreditAttribution: msonnabaum commentedI had the exact same thought as #7.
I'd still prefer simplifying the process if possible, but if we can't, breaking it up and moving most of it to entity manager makes sense to me.
Comment #11
BerdirSomething like this?
- Moved the method and added static caching.
- I also fixed a problem with the method while I was at it if you have multiple subclasses *and* an exact match that is not returned first.
- The unit tests need to be cleaned up, most of it should probably move to EntityManagerTest, only the part about passing the correct class name around needs to be kept in EntityTest. Just made them not fatal for now, easier to see if other tests have a problem with this, but I doubt that.
Comment #13
msonnabaum CreditAttribution: msonnabaum commentedIt is much better on the EntityManager, but this method still feels very complex. It could be broken up some, but it's unclear to me why we even need to do some of these checks.
What is the scenario where this would actually happen? Why is it not ok to just return once a match is found?
Comment #14
msonnabaum CreditAttribution: msonnabaum commentedAlso, if I'm reading this right, one invocation of this method will autoload all defined entity classes because of those is_subclass_of calls. I'd like to make sure that check is absolutely necessary before we accept that.
Comment #15
Damien Tournoud CreditAttribution: Damien Tournoud commentedAlso, don't we have most or all of this information from the annotation discovery anyway?
Comment #16
Damien Tournoud CreditAttribution: Damien Tournoud commentedSo the use case for the
is_subclass_of
is to be able to swap out an entity type classMyEntityType
with a child classMyEntityTypePatched
without breaking direct calls toMyEntityType::load()
in existing code.That really an information that we should add to the entity type discovery information.
Comment #17
Berdir#13: The reason this could happen is if you dynamically expose multiple entity types with the same class, see https://www.drupal.org/project/eck as an example that could do it like this in 8.x. It was not in the original implementation, but @fago was strongly for adding it, and I just wanted to get it in, so I included that. It will never work for those, but without it, it will just pick the first, while that code will force an exception.
#16: Hm, now that we have EntityType as an object for this information, we could possibly have a getOriginalClass() method and the class would ensure that the originally set class would be returned there? Yes, then we might be able to get rid of this logic..
Comment #18
BerdirWhat do you think about this approach?
Also updated the unit test coverage so that the separate methods are separately cached, removed a lot of complexity from EntityUnitTest, as we now only need to test smaller snippets.
Comment #19
msonnabaum CreditAttribution: msonnabaum commentedThis is certainly a big improvement. It still feels like the check for duplicates belongs elsewhere, but I can live with this if necessary.
Comment #20
BerdirYeah, I'd be fine with removing that too, but @fago explicitly requested it and I didn't want to go behind his back on this one.
Comment #21
cbr CreditAttribution: cbr commentedFixed a issue where the subclass entities could not be loaded anymore, tested on a live environment.
Now allowing the actual class name as well, this makes sense right?
New unit test also included in the patch.
Comment #23
moshe weitzman CreditAttribution: moshe weitzman commentedPatch now fails to apply. Will you reroll, @ChrisjuhB?
Comment #24
cbr CreditAttribution: cbr commentedreroll
Comment #25
cbr CreditAttribution: cbr commentedneeds review
Comment #26
cbr CreditAttribution: cbr commentedreroll needs review
Comment #29
moshe weitzman CreditAttribution: moshe weitzman commentedFrom https://qa.drupal.org/pifr/test/839273
Comment #30
cbr CreditAttribution: cbr commentedChanged randomName() to randomMachineName().
Comment #32
cbr CreditAttribution: cbr commentedReroll
Comment #33
moshe weitzman CreditAttribution: moshe weitzman commentedOK, thanks for making tests more specific. Looks like everyone has been satisfied. If I'm wrong, feel free to change status.
Comment #34
BerdirWe need a change record for the removal of the configurable permission stuff (it would not work right now anyway without the schema and anything, but still) - if we want to do this here.
Comment #35
alexpottYep let's get a draft record done for the removal of node_permissions_get_configured_types(). Also the issue summary seems out of date.
Comment #36
fagoyeah, moving this to the EM makes sense. I also like how the patch moves to keeping the original class - that makes the detection more robust already.
Where would you put it then instead? I think we either need to be sure that the returned entity type is correct by checking as we do now, or document to require a unique entity class by entity type and rely on that. I still think that just silently returning a wrong entity type is a WTF and the wrong thing to do.
Comment #37
moshe weitzman CreditAttribution: moshe weitzman commentedComment #38
moshe weitzman CreditAttribution: moshe weitzman commentedCreated draft CR and updated issue summary
Comment #39
alexpottDrats!
Comment #40
moshe weitzman CreditAttribution: moshe weitzman commentedFortunately not much decayed. Just two use statements conflicted.
Comment #41
alexpottCommitted 9d77777 and pushed to 8.0.x. Thanks!
Comment #43
moshe weitzman CreditAttribution: moshe weitzman commentedThx. I just published the CR. Hope thats the right workflow.
Comment #44
larowlanThis broke phpunit code coverage reports - see
#2324293: PHPUnit code coverage broken