For the same reason as posts, making dozens of queries per user is more expensive than making a single one for all users.

Are we even loading these things for all queried users in one go? If not, agreeing wholly; if so, disagreeing based on the above.

This is commonly seen when querying the current user, not necessarily all queried users. Multiple-user usermeta queries is not common.

I think a solution is to allow WP_User to be instantiated with only a whitelisted (and filterable) subset of usermeta being queried. This would be leveraged for querying the current user's user meta, which obviously is loaded into memory.

When a non-whitelisted usermeta key is requested, we should then load ALL usermeta, not just that key, to allow other keys to hit the cache. The whitelist should be large enough to cover core usage, and beyond that, it'll at least keep the memory footprint lower than it is now.

first-last-name.15458.diff​ removes the first_name and last_name properties, since they interfere with the magic methods. Any notices that spring up (I didn't see any) should be dealt with another way.

I think that will be okay, but we should audit core user meta for use of dashes. And instead of relying on the magic method all the time perhaps we should standardize on using a get() or get_meta() accessor so we can handle dashed keys.

Those lines in WP_User::_init_caps() are somehow causing cache pollution. wp_cache_get($id, 'users'), as done in get_userdata(), will sometimes return an object with the wp_xxx_capabilities property set ( where xxx is the current blog id). The value is an empty array. This confuses get_blogs_of_user(), which sees that the property is set and mistakenly thinks the user belongs to the current blog. Since the cache clones objects on set and get this is rather unexpected. Removing the ref assignment avoids the problem.

Those lines in WP_User::_init_caps() are somehow causing cache pollution. wp_cache_get($id, 'users'), as done in get_userdata(), will sometimes return an object with the wp_xxx_capabilities property set ( where xxx is the current blog id). The value is an empty array. This confuses get_blogs_of_user(), which sees that the property is set and mistakenly thinks the user belongs to the current blog. Since the cache clones objects on set and get this is rather unexpected. Removing the ref assignment avoids the problem.

Tracked this down to a bug in a particular memcached backend. The value was being properly cloned from the cache during get but then that clone was assigned right back into the cache. Dumb. Anyhow, not a core bug so disregard the previous comment.

get_user_option fails because the isset magic method doesn't consult meta. I think we should retain this for back compat. Since get_metadata() returns an empty string even if a meta key is not actually set, I introduced isset_metadata() and used it in the isset magic method. So that get_user_option() can once again support meta keys with dashes, I also added is_set() and get() methods to WP_User.

The amount of data stored in the users group in memcached for a sample multisite user was reduced from 22.31K to 0.47K. Multiply that by millions of users for a very nice savings. On users.php with 17 users, the number of trips to the users cache went from ~422 to ~400. (Numbers include local cache fetches, not just memcached trips.) The number of trips to the user_meta cache increased sharply from ~13 to ~464. That is to be expected since we're not duplicating user meta in the users cache any more and must fetch meta separately. The overall cache query time is roughly the same, however. Gets from the users cache are many times faster now that the cache is much smaller.

Various themes, plugins, and even maybe a couple places in core are calling get_blogs_of_user() with a user ID of 0 or null, resulting in the warning noted in the commit message above. Checking for an empty id at the top of the function seems a good idea.

Some sporadic reports of get_blogs_of_user() problems after putting these changes on wp.com.

There have been a few reports of users having difficulty adding other users to their blogs. It appears that in some cases, the issue resolves itself and the user is ultimately added. In some other cases, the user can be removed and re-added to the blog by an SA, which resolves the issue.

The caching appears pretty silly and causes more trouble than it's worth. It also looks like it was still checking for an older version of this cache, which can go. get_blog_details() is already cached, as is get_user_meta() (incl. when requesting all meta keys).

The code for looping through the keys was pretty hairy. New code hasn't been benchmarked yet, but it avoids that regular expression and optimizes string function usage, among other things. I'd be willing to bet this would perform much better than restoring clean_user_cache().

We can also add in a defined('MULTISITE') check before (or after) `$key === $wpdb->base_prefix . 'capabilities', to make sure we're operating on the main blog of a 3.0+ multisite.

I'm open to suggestions on how to better order the various conditions in the loop to ensure that common and fast cases are treated higher up.

Ryan relays that blogs_of_user caching reduced load on WP.com when it was introduced, but it may have been just patching the symptoms. Fixing up the blog details caching in 3.1 and user objects in 3.2 might mean we can yank the caching here.

User objects (before this ticket) could get pretty large. Like 300k large. As long as the user's meta is in cache, though, we'll be loading it but only processing the keys. I doubt looping through even a few hundred meta keys would cause enough load to require caching. I dunno. Worth some serious testing.

#14379 is where we fixed the blogs of user cache invalidation. It used to have a 5 second timeout, meaning we were rebuilding it all the time. All I recall is that this supposedly helped a lot on .com Because of the admin bar, get_blogs_of_user() is called on pretty much every logged in page load. But as nacin suggests, this could have been fixed more so by all of the other cache work done around that time and since.

I poked around .com, looking for get_blogs_of_user() calls that resulted in a cache miss on user meta. All calls were for the current user and the meta cache was always hit. A quick timer_start()/timer_stop() benchmark showed times of 0.004 for the current code and 0.008 for 15458.8.diff. get_blogs_of_user() ) is called 10 times on a typical .com page load for a logged in user. So, the caching does speed things up a bit, but we're not talking about huge times or the possibility of making extra db queries without the blogs_of_user cache.

It looks like the patch for this has introduced some problems for installations that don't use a base_prefix. In WP 3.3, line 699 is causing two problems. First, a PHP warning is thrown by searching an empty string with strpos. Also, the "My sites" menu only contains the first site in that case, ignoring the rest. Checking the strlen of $wpdb->base_prefix before the strpos call fixes both of these, and appears to be what was done before this change was made.