Skip to content

Cache API: Cache non-existent users in WP_User::get_data_by() to prevent duplicate queries#11632

Open
MarcinDudekDev wants to merge 1 commit intoWordPress:trunkfrom
MarcinDudekDev:trac/46388
Open

Cache API: Cache non-existent users in WP_User::get_data_by() to prevent duplicate queries#11632
MarcinDudekDev wants to merge 1 commit intoWordPress:trunkfrom
MarcinDudekDev:trac/46388

Conversation

@MarcinDudekDev
Copy link
Copy Markdown

Fixes https://core.trac.wordpress.org/ticket/46388

Problem

When get_userdata() (or WP_User::get_data_by('id', $id)) is called for a non-existent user ID, it always executes a database query. If the same non-existent ID is looked up multiple times — for example, through repeated calls to get_userdata(), get_avatar(), or other functions that resolve user data — each call results in a duplicate SELECT * FROM wp_users WHERE ID = ... query.

Solution

Cache non-existent user IDs in a notusers array within the users object cache group, following the established notoptions pattern from the options API (get_option()).

WP_User::get_data_by() (when $field === 'id'):

  1. Before querying the database, check the notusers cache. Return false immediately if the ID is cached as non-existent.
  2. After a database miss, add the ID to the notusers cache.

update_user_caches():

  • When a user is created or updated, remove their ID from the notusers cache to ensure stale negative-cache entries are invalidated. This addresses the concern raised in comment:1 about cache invalidation on user creation.

The users cache group is already registered as a global group in multisite (wp_cache_add_global_groups()), so the notusers key inherits that scope correctly.

Tests

Four new tests in tests/phpunit/tests/user/getDataBy.php:

  • Verifies second call for a non-existent user triggers 0 DB queries.
  • Verifies non-existent user ID is added to notusers cache after first miss.
  • Verifies notusers cache is invalidated when update_user_caches() is called.
  • Verifies existing user IDs are never added to notusers.

AI assistance: Yes
Tool(s): Claude Code (Anthropic)
Model(s): Claude Sonnet 4.6
Used for: Implementation and test authorship; reviewed and verified by contributor.

@github-actions
Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props myththrazz.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

…ent duplicate queries.

When get_userdata() or WP_User::get_data_by('id', $id) is called for a non-existent
user ID, the result is now cached as a per-ID key ('notuser_$id') in the 'users' cache
group. Subsequent calls return false immediately without hitting the database. The cache
entry expires after one day as a safety net for persistent object cache backends.

The cache is invalidated by clean_user_cache(), which is the canonical invalidation
path called by both wp_insert_user() and wp_update_user(). When called with a plain
integer (as wp_insert_user() does), the notuser key is cleared *before* constructing
a new WP_User internally, preventing a self-referential cache miss.

Using per-ID keys (rather than a shared array) avoids unbounded memory growth under
persistent object cache backends and eliminates read-modify-write race conditions.

Fixes #46388.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant