Add JSON:API performance tests

Created on 4 April 2024, 11 months ago

Problem/Motivation

Spin off from Introduce a list of "common cache tags" to reduce lookup query amount Active .

We should add performance test coverage of JSON:API, currently this will need to be FunctionalJavascript testing which isn't technically needed for JSON:API but should work as long as it's for GET requests.

We could also add a FunctionalTest version of PerformanceTestBase using PerformanceTestTrait which skips all the chromedriver-specific logic, this might require a bit of moving things around, but not too much hopefully.

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

📌 Task
Status

Active

Version

11.0 🔥

Component
PHPUnit 

Last updated about 8 hours ago

Created by

🇬🇧United Kingdom catch

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @catch
  • 🇧🇪Belgium wim leers Ghent 🇧🇪🇪🇺

    +1 😄

    Although I wonder how representative this would be of the real world, where JSON:API performance problems likely only appear on sites with either a huge number of (content) entities or very complex (content) entity types (i.e. with lots of fields on them).

  • 🇨🇭Switzerland berdir Switzerland

    🐛 RoutePreloader loads a lot of routes Active is a good example of something that impacts JSON:API. It might not have the same impact as a complex and slow filters on a very large database but in its current state, it would add 10 or so queries to entity types, even single ones with a bunch of reference fields.

  • Merge request !11168Add initial jsonapi performance test → (Open) created by berdir
  • 🇨🇭Switzerland berdir Switzerland

    There's a german saying that's basically "Trust is good, control is better". As always with the performance tests, they're full of interesting things.

    I added a test that visits a json api resource (node detail page) on a cool cache (frontpage visited, but no jsonapi route yet), then one with a warm dynamic page cache cache, then invalidates that node and visits the page again.

    The most obvious thing that I saw is that there are around 9 uncached loadbyUuid lookup queries for the same UUID, didn't look into that yet but that seems like a very obvious target for optimizations, especially considering that uuid entity queries are really inefficient as they join the data table for absolutely no reason at all (existing open issues).

    The route lookups are as expected, 6 separate route lookups that are then separately cached, and with the current implementation in [##3503843] would no longer be cached. Note that it's 8 data cache lookups (1 for the route match, then 7 route preloads, including the full frontend route preload even though that actually excludes jsonapi routes. I think my suggestion in the other issue to keep track of routes by format could be interesting as we could keep all of them in discovery cache.

    Another thing is the resource type caches, those could be candidates for the discovery or bootstrap bin instead of default?

  • Pipeline finished with Success
    28 days ago
    Total: 325s
    #420419
  • 🇨🇭Switzerland berdir Switzerland
  • 🇬🇧United Kingdom catch

    Add static cache for loadEntityByUuid function to store uuid-id pairs in memory Active is already open for those uuid lookups, but no MR there yet.

    When looking for that I also found #2878673: Cache loadEntityByUuid() calls for block_content .

  • 🇬🇧United Kingdom catch

    The most obvious thing that I saw is that there are around 9 uncached loadbyUuid lookup queries for the same UUID, didn't look into that yet but that seems like a very obvious target for optimizations

    Yeah I wonder why we would ever even need to do that unless the request itself has the uuid and we need to resolve from that. Wonder if something is going id -> uuid -> id.

  • 🇨🇭Switzerland berdir Switzerland

    The request/route is based on the UUID, so one UUID lookup is expected, but no idea why it's then doing it again so many times, looks like something during normalization does that. Should be pretty easy to find, a static cache would hide that, but maybe we can also prevent it from happening.

    And yes, block_content was done as a cache collector under the assumption that there's a somewhat limited amount of block_content blocks placed as regular blocks. that doesn't scale and I think a 1:1 cache isn't that useful either.

  • 🇨🇭Switzerland berdir Switzerland

    \Drupal\jsonapi\ParamConverter\EntityUuidConverter() is the problem, and the fact that it does the upcasting when generating routes, so we do that once for every route that contains the node UUID.

    It also doesn't even go through \Drupal\Core\Entity\EntityRepository::loadEntityByUuid() at the moment, so a static cache won't help without changing that. We should be able to change that though.

    And on a collection with 50 nodes, it's going to do that with 50 different UUID's.

  • 🇬🇧United Kingdom catch

    One general thing - when I originally added PerformanceTestTrait and the performance test base class I had good intentions of adding functional and kernel variants of it, but obviously haven't done that yet and not sure there are even issues. This issue is one where we really don't need a real browser to do the testing.

    I don't think that should block this issue, but it might be time to open issues for kernel and functional variants, and then we could convert this over when those base classes are available to use.

  • 🇨🇭Switzerland berdir Switzerland

    Yes, would make sense to not having to go through a real browser here. I was considering for a second if I should try to use the trait on a functional test and expected some kind of problem when using that to access non-HTML pages, but it worked so I just went with that.

    FWIW, I think it makes sense to get this committed without any improvements first, and then we can use the test in issues like Add static cache for loadEntityByUuid function to store uuid-id pairs in memory Active to show the improvements.

Production build 0.71.5 2024