
Problem/Motivation
All pages under the 'jsonapi/*' routes that contain nodes with default includes are not cached. This is because the default include is added dynamically in the 'Drupal\jsonapi_defaults\Controller\EntityResource' dynamic route controller service. The 'getIncludes' method executes after the cache is retrieved from the database, resulting in the cache cid being generated without default includes during the cache retrieval process.
On the other hand, 'getIncludes' executes before the cache is set to the database, and during this phase, the cache cid is generated using default includes added to the request. Consequently, we have a cache stored in the database using the default includes context. However, Drupal always attempts to retrieve the cache with a different cid, one that was generated without the default includes context.
As a result, the only way to obtain a cached response is by adding default includes explicitly as get parameters. This is because the cache stored in the database is under a cid generated using these includes added dynamically as default includes. Consequently, we consistently receive an uncached response for pages with default includes.
Steps to reproduce
- Add default includes to any content type
- Create a page of this content type
- Visit this page using the jsonapi/* route, for instance, jsonapi/node/article/324f078e-e3a0-4e3a-b902-0a76f072bdd2
- Check the cache header in the response
- Reload the page
- Check the cache header in the response
Actual result: After the first visit to the page, the cache status is marked as "MISS" and after reloading the page, the cache status remains "MISS."
Expected result: Following the initial visit to the page, the cache status should be "MISS," but after reloading the page, the cache status is expected to change to "HIT."
Proposed resolution
Override the core QueryArgsCacheContext (cache_context.url.query_args) service to disregard all default includes arguments. Consequently, for all requests to resources with default includes, the cache will be stored under the cid generated without using the default includes context. This modification enables us to obtain cached responses for all resources with default includes.
Comment | File | Size | Author |
---|---|---|---|
#27 | 3402388-27.patch | 21.82 KB | _tarik_ |
#26 | 3402388-31.patch | 21.64 KB | _tarik_ |
#16 | 3402388-default-includes-caching-fix-15.patch | 22.48 KB | ihor_allin |
#15 | 3402388-default-includes-caching-fix-14.patch | 22.34 KB | ihor_allin |
#13 | 3402388-default-includes-caching-fix-13.patch | 19.83 KB | ihor_allin |
Issue fork jsonapi_extras-3402388
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:
- 3402388-pages-with-default
changes, plain diff MR !40
- 3402388-test-only
changes, plain diff MR !39
Comments
Comment #2
ihor_allin CreditAttribution: ihor_allin commentedComment #3
bbralaGreat work, that will save quite some request/cpu time.
Any change you could add a line to the tests for that module? I think using:
$this->assertSession()->responseHeaderContains()
should be usefull for that to see if the cache has been hit if a second request is done.Comment #4
ihor_allin CreditAttribution: ihor_allin commentedComment #5
ihor_allin CreditAttribution: ihor_allin commentedIt seems that the responses in these tests do not contain headers, just a raw JSON string.
Comment #6
bbralaYou need to use:
Or something similar. When looking at the html output in files//simpletest/browser_output
I see cache info. Locally i was not sure how to reproduce the error, i got a hit on the second call.
Comment #7
ihor_allin CreditAttribution: ihor_allin commented@bbrala, thank you for the clear explanation. Updating the patch with the corresponding tests. Passed successfully on my local env
Comment #10
bbralaHmm, gitlab is weird.
But when i run those tests locally, when i do only the test (which should fail right?) then the test is still green. So that test doesn't proove the faulty caching...
Comment #11
ihor_allin CreditAttribution: ihor_allin commented@bbrala do you have default includes added when you test it locally?
Comment #12
ihor_allin CreditAttribution: ihor_allin commentedFixed bug with variable name
Comment #13
ihor_allin CreditAttribution: ihor_allin commentedComment #14
ihor_allin CreditAttribution: ihor_allin commentedFor some reason, locally, I have also passed tests by adding a cache tag check: $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT') after the second request to '/api/articles'. However, at the same time, when I check it by visiting the JSON API route with the resource with default includes, I always receive an uncached response.
Comment #15
ihor_allin CreditAttribution: ihor_allin commentedUpdate patch with minor code styles fixes
Comment #16
ihor_allin CreditAttribution: ihor_allin commentedFixed 500 error for the resources without default includes
Comment #17
_tarik_ CreditAttribution: _tarik_ at DevBranch commentedThanks ihor_allin. The patch in comment #16 works for me.
Comment #18
Ankush_03Hey @bbrala,
Any update on the above fix ??
Comment #19
Ankush_03Comment #20
gaurav_manerkar CreditAttribution: gaurav_manerkar at EPAM Systems commentedComment #23
gaurav_manerkar CreditAttribution: gaurav_manerkar at EPAM Systems commentedReview https://git.drupalcode.org/project/jsonapi_extras/-/merge_requests/40
Comment #24
anybodyComment #25
_tarik_ CreditAttribution: _tarik_ at DevBranch commentedI have found an issue with response caching with this patch.
The isDefaultIncludes method checks only if the request contains included parameters from the jsonapi_extras configuration. So, it can cause problems when the one cached response is used for different includes params.
Say, you have an endpoint for an article that uses 3 taxonomy fields (field_tag, field_type, field_category).
The jsonapi_extras is configured to include by default the field_type field. Then if you send the first request with ?include=field_category this response will be cached and applied to the response without the include param or for ?include=field_tag.
So, instead of checking if the request uses default includes we need to check if the request uses ONLY default includes.
Comment #26
_tarik_ CreditAttribution: _tarik_ at DevBranch commentedThe diff for PR to avoid adding numerous commits to the composer.json.
Comment #27
_tarik_ CreditAttribution: _tarik_ at DevBranch commentedI realized that made a mistake in my previous commit. Here is the correct version of the patch
Comment #28
bbralaTy for taking the time here to fix this up. Changes seem to work as promised <3
Comment #30
bbrala