[wp-trac] [WordPress Trac] #46249: REST API Performance Issues: wp/v2/posts + _embed / count_user_posts()
WordPress Trac
noreply at wordpress.org
Wed Feb 13 21:57:00 UTC 2019
#46249: REST API Performance Issues: wp/v2/posts + _embed / count_user_posts()
--------------------------+-----------------------------
Reporter: iCaleb | Owner: (none)
Type: defect (bug) | Status: new
Priority: normal | Milestone: Awaiting Review
Component: REST API | Version: trunk
Severity: normal | Keywords:
Focuses: rest-api |
--------------------------+-----------------------------
**tl;dr**: A REST API request such as `wp/v2/posts?per_page=100&_embed=1`
will result in 200 calls to `count_user_posts()` which results in 200
uncached queries being ran.
== Issue Description
As a quick recap, the `_embed` parameter tells the api request to include
the response of all linked resources. Can read about this here:
https://developer.wordpress.org/rest-api/using-the-rest-api/linking-and-
embedding/
So when making a request like `wpdev.test/wp-
json/wp/v2/posts?per_page=10&_embed=1`, you end up with 10 post objects
and you also have 10 author objects embedded within each post. The author
objects are essentially embedded via an extra internal rest api request
and it's treated mostly like it would be if it were requested
individually.
The performance issue is related to the author object being included in
the response. More specifically,
`WP_REST_Users_Controller::get_item_permissions_check` is run for every
item. This calls `count_user_posts()` to ensure the user has posts before
making the profile publicly accessible via the API. This function is an
uncached query in WP, and results in a query that looks something like
this:
`SELECT COUNT(*) FROM wp_posts WHERE ( ( post_type = ? AND ( post_status =
? ) ) OR ( post_type = ? AND ( post_status = ? ) ) OR ( post_type = ? AND
( post_status = ? ) ) OR ( post_type = ? AND ( post_status = ? ) ) OR (
post_type = ? AND ( post_status = ? ) ) OR ( post_type = ? AND (
post_status = ? ) ) OR (post_type = ? AND ( post_status = ? ) ) ) AND
post_author = ? ?`
On the site this become a problem, this query was taking near 1 second to
complete. Alone, that's not a huge issue. But it does become a big problem
due to how many times this query is run per request. This is because for
every 1 post in the feed , there are actually 2 calls to
`count_user_posts()` when using `_embed=1`. So the 10 per_page request
above will result in 20 of these queries. 100 posts will result in 200 of
this query (!!!).
So why is this function called twice per one post in the response? The
first call is standard due to making an internal request for the resource,
such as `wpdev.test/wp-json/wp/v2/users/1`. The second call is due to the
`rest_send_allow_header` method hooked onto `rest_post_dispatch`. This is
[apparently by design](https://github.com/WP-API/WP-API/issues/2400).
== Relevant WP core locations:
- WP_REST_Users_Controller::get_item_permissions_check
https://github.com/WordPress/WordPress/blob/56bb62543d485f491f7f614c827638e426019d93
/wp-includes/rest-api/endpoints/class-wp-rest-users-
controller.php#L387-L406
- count_user_posts() uncached query:
https://github.com/WordPress/WordPress/blob/50ddffbac37fdae9298d1f11fe1dd7b8535fc839
/wp-includes/user.php#L379-L399
- rest_send_allow_header() extra permission check:
https://github.com/WordPress/WordPress/blob/2785313bf2a8e46e52863d476d68951814a6fbbf
/wp-includes/rest-api.php#L635-L669
== Possible Solutions
1) Improve `WP_REST_Users_Controller::get_item_permissions_check`.
- When the request is being embedded with a posts request, do we even need
to run the `count_user_posts()` query since they are clearly an author on
a post?
- Could maybe have a cache per author, even if it's just per request.
- Simplify the query to do some quicker queries first. (for example, check
if that user even has a post in the DB, which should use the post_author
index and be quick).
- Perhaps utilize user meta and query for that?
2) Add a filter to allow for overriding this logic
If it isn't possible to address this in core (though I feel there should
be some options for us), could we get a filter added to this method to
allow for some overriding of the logic? Namely replacing the uncached
`count_user_posts()` function with a cached version, though there are
likely even better enhancements available.
--
Ticket URL: <https://core.trac.wordpress.org/ticket/46249>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list