After it was theoretically possible to update to 2.0-rc1 with the complete ecosystem catching up i wanted to give it a shot and run into some trouble regarding performance and cacheability.
In general the api is used for serving public content so:
- API publically available
- No oauth
- Anon users are able to view content / media ...
Setup before the update:
- drupal/core: 8.6.2 (Patch1 : 2915792)
- drupal/jsonapi 1.23
- drupal/jsonapi_extras 2.10
- drupal/decoupled_router 1.1
- drupal/consumer_image_styles 2.0-rc6
Performance before update:
- Uncached single entity: ~ 1.300ms
- Cached single entity: ~ 30ms
Setup after update:
- drupal/core: 8.6.2 (Patch1 : 2915792, Patch2 : 2825812)
- drupal/jsonapi 2.0-rc1
- drupal/jsonapi_extras dev-2.x (because of composer ,Patch 3: 3007091)
- drupal/decoupled_router 1.1 (Patch4: 3003019)
- uninstalled consumer and consumer_image_styles as it's not yet compatible with 2.0-rc1 and the patch from core suits my needs.
Performance after update:
- Uncached single entity: ~ 1.100ms
No caching possible. I'm assuming there is some cacheability meta data leak somewhere which prohibits caching (No X-Drupal-Cache Header) and dynamic page cache (No X-Drupal-Dynamic-Cache Header)
Here's what i tried:
- Removing the patches 2 and 3 did not change anything.
- Uninstalling jsonapi_extras did something as at least the X-Drupal-Dynamic-Cache Header was there again
I know this is not enough useful informations but at least something changed there fundamentally. I can't think of an API without decoupled_router, jsonapi_extras and image_styles for my use case which is a normal 100% decoupled information website with drupal as a backend.
Perhaps it's worth to create an integration testing module with the complete decoupled drupal eco system to see no regressions are introduced in the needed modules?
If you need anything from my side let me know.
| Comment | File | Size | Author |
|---|---|---|---|
| #27 | 3009596-27.patch | 3.93 KB | wim leers |
| #27 | interdiff.txt | 1.67 KB | wim leers |
| #16 | 3009596-16.patch | 1.84 KB | gabesullice |
| #18 | interdiff.txt | 3.13 KB | gabesullice |
| #18 | 3009596-18.patch | 3.81 KB | gabesullice |
Comments
Comment #2
gabesullice@yobottehg, thanks for testing this out and for creating a detailed issue!
Comment #3
wim leersThanks a lot for trying 2.0-RC1! 🙏 🎉
JSON API 2.x is less forgiving towards code that's not handling cacheability correctly. Related: #2948666: Remove JSON API's use of $context['cacheable_metadata'], #2940342: Cacheability metadata on an entity fields' properties is lost, #2984964: JSON API + hook_node_grants() implementations: accessing /jsonapi/node/article as non-admin user results in a cacheability metadata leak, #3005826: Follow-up for #2984964: JSON API + hook_node_grants() implementations: count queries still result in cacheability metadata leak. Also: I'm very pleased that some people do pay attention to cacheability! 😄 Most people seem oblivious to this.
4 questions:
hook_node_grants()?X-Drupal-CacheandX-Drupal-Dynamic-Cacheresponse headers look like then?P.S.: you don't need #3007091: Performance issue in ConfigurableResourceType to make JSON API Extras install via composer, but #2994885: JSONAPI Version Requirements prevent installation via Composer. You probably meant that issue?
Comment #4
wim leersAlso:
15% faster, nice :)
Comment #5
yobottehg commentedThanks for the fast response.
No. Only some modules looking for it (linkit for example)
No. Just a patch which changes a link enhancer a bit because of 2942851
I know. I have dev-2.x because of this and the patch because i have the same problem like in the issue.
x-drupal-cache: UNCACHEABLE (request policy)
x-drupal-dynamic-cache: UNCACHEABLE (response policy)
I'll take a look now by uninstalling module after module.
Comment #6
yobottehg commentedComment #7
yobottehg commentedAll requests go to a simple endpoint
1. Removing the entity override for a jsonapi endpoint does not change the caching
2. Removing all patches for jsonapi_extras does not change anything
3. Removing collections count does not change anything
4. Uninstalling jsonapi_extras resolves this somehow (Not uncacheable because of policy)
First request:
X-Drupal-Cache: MISS
X-Drupal-Dynamic-Cache: MISS
and the second request:
X-Drupal-Cache: MISS
X-Drupal-Dynamic-Cache: HIT
But never X-Drupal-Cache: HIT even after 10 Requests
5. After uninstalling module for module (jsonrpc, jsonrpc_core, decoupled_router, openapi, openapi_ui, rest, webform_rest, hal, require_login, decoupled_preview_links, http_response_headers, schemata, schemata_json_schema, simple_sitemap, unpublished_node_permissions ...) i could not get the normal page_cache to work. dynamic_page_cache seems to work so.
So there could be 2 problems here, one for jsonapi_extras and one for jsonapi.
Any suggestions on how to proceed?
Comment #8
yobottehg commentedunassigning for feedback.
Comment #9
yobottehg commentedAdditional info:
The same can be observed on this simplytest.me environment (only jsonapi enabled):
https://dmbxx.ply.st/jsonapi/node/article/5ffc3d0b-386c-4d37-93fd-3b594b...
dynamic page cache hit, page page miss.
Comment #10
wim leersI cannot reproduce #7: I can get
Dynamic-Page-Cache: HITeven withjsonapi_extrasinstalled.However, I can reproduce #9. The reason is that when looking up a Page Cache entry, the request format is always still
html, since we're so early in the request/response processing system. But when a Page Cache entry gets written, the format isapi_json. Related: #2983178: Using URL query parameters on JSON API routes disallowed by the JSON API spec now result in "400: Bad Request" responses + #2933062: [>=8.5.4] Spec Compliance: Return 400 for unrecognized/unsupported query parameters. Needs further investigation.Comment #11
yobottehg commentedI also tried this again on a clean install and can also not reproduce. This has to be some side effect on some other module in combination with jsonapi_extras. I'll investigate further.
Comment #12
wim leersThank you! 👌
Comment #13
wim leersRetitling per #10+#11, tightening scope to only the Page Cache aspect.
Comment #14
yobottehg commentedi could not investigate further and i'm away for 1 week so unassigning.
Comment #15
gabesulliceI've narrowed this down to #2987205: FormatSetter doesn't set the format to `api_json` when accessing just `/jsonapi`.
@Wim Leers comment in #10 was the crucial clue. We used to have some middleware that set the
api_jsonformat on the incoming route.I used
git bisectto find the commit where that was removed, then confirmed that page cache was alwaysMISSafter that commit, but not before.I'm looking into why it was removed and how to restore it now.
Comment #16
gabesulliceThis adds a regression test that reproduces the issue.
Comment #18
gabesullice@Wim Leers was spot on with his diagnosis in #10. There are only two ways to make this work. Restore the
?_format=api_jsonroute requirement or set it ourselves based on something else. I think we all agree that we do not want to bring back the_formatquery parameter. Blech. So that means the desired format needs to be detected. This is what we did before #2987205: FormatSetter doesn't set the format to `api_json` when accessing just `/jsonapi`. However, we removed that service because it was unreliable and used complicated/broken code to do the detection.AFAICT, the only reliable mechanism we have to determine that the client intends to get a JSON API response is the
Content-Typeheader. This header is required by the spec, so it should be on every incoming request.Unfortunately, relying on
Content-Typemeans that the format can't be detected when requesting JSON API in the browser. At first, I thought this meant it was a no-go. After thinking about it more, what's the big deal, really? The browser is an edge case is only used for quick debugging. Requiring theContent-Typeheader will work just fine for Postman,curland anything that can send theContent-Typeheader. It fixes this cache bug and also formats 404s in JSON for all the "real" clients. Only the browser will get HTML 404s.I think that's the best solution. It's simple and reliable and solves the 95% case. @Wim Leers?
Comment #20
gabesullice@mortona2k helped me troubleshoot some things related to this issue in Slack.
Comment #21
wim leersWFM. This will cause some disruption. But not for anybody who's already using "proper" tools like Postman etc. I suggest a CR where we explicitly suggest this.
Then it also means we're finally fully spec-compliant.
…
OTOH (devil's advocate): is it really that bad to automatically make all
/jsonapirequests have this format?IMHO ship with this, unless there's too much backlash, then we can still add base path-based format setting later.
Comment #22
gabesulliceI think I could have been a little more clear. With this patch you can still get JSON:API responses without the
Content-Typeheader, even in the browser as long as the URL matches a JSON:API route. So,/jsonapi/article/page/missingstill works. But/jsonapi/misspelled/page/uuidwill not unless you have the header.So we're not technically fully spec compliant, but we're closer. And I think in most cases the browser will still work just fine.
What were accepting is just that the page cache won't work without the
Content-Typeheader and missing routes without it will fall back to HTML. But with it, everything works better than it ever did!What we could do, is make this more strict and actually have an error if the request doesn't have the header linking to documentation about using a "proper" tool.
I'm fine with that also.
Comment #23
jibranFWIW, page cache middleware is not always available, e.g. you are using external page cache and you disabled the page_cache module. Instead of adding this middleware all the time we should only add it when the page_cache module is enabled. Correct me if I'm wrong, I think, external page cache doesn't care about this.
Comment #24
gabesullice@jibran, I don't think we only want this when Page Cache is enabled.
I think it's good to have on all the time. If we don't have it and a user visits
/jsonapi/node/misspelled/{uuid}then the 404 response will be formatted in HTML rather than JSON, for example. Not that big a deal, but it can't hurt to have this middleware can it?Comment #25
e0ipsoWith the years I have shifted perception on this. I think that content type negotiation based on path detection is not a good practice. Specially when that path is variable. Specially when we don't have a way to negotiate legit 404's and route level 403's.
This patch is my preferred approach.
I'm seeing many mentions to
Content-Typewhere it should beAccept. To be 100% clear, we want to negotiate requests with anAcceptheader so the response contains theContent-Typeheader.Additionally, unsafe requests with a body should contain a
Content-Typeheader to tell the server how to deserialize the body. This has no effect on theContent-Typeheader of the response.Comment #26
gabesulliceAh, thanks @e0ipso! I misread the spec:
What I missed was "Clients must send all data". I read it as "send all requests".
So, this patch needs work, because it's looking for the
Content-Typeheader.Also, this will be a BC break since the spec does not require all requests to have the
Acceptheader.I'm fine with that however.
Comment #27
wim leersAlright, even better! 👏
I noticed that too, but I thought it was pretty clear what Gabe meant :)
Me too. Every single example request at https://jsonapi.org/format/ explicitly has that request header too btw! So it's definitely a SHOULD-HAVE already.
Comment #28
gabesulliceComment #30
gabesulliceBam!
Comment #31
wim leershttps://www.drupal.org/node/3013494
Comment #33
rpayanmMoving to Drupal core's issue queue.
I'm working on https://www.drupal.org/project/drupal/issues/3122113