Make WordPress Core

Opened 14 years ago

Closed 12 years ago

#14992 closed defect (bug) (worksforme)

When Object Caching is enabled switch_to_blog causes issues with some functions

Reported by: simonwheatley's profile simonwheatley Owned by:
Milestone: Priority: high
Severity: major Version:
Component: Multisite Keywords:
Focuses: Cc:

Description

When you use switch_to_blog with an Object caching plugin installed you get incorrect links from home_url and therefore also get_permalink.

To reproduce:

  1. Setup a WP Network using sub-directories with at least two sites
  2. Install an Object caching plugin
  3. Assuming that get_option has been called earlier, thereby loading the cache with alloptions, use switch_to_blog then get_permalink
  4. get_permalink returns a URL based on the site which was active before you used switch_to_blog

The problem seems to be that the Object cache caches alloptions, but these aren't refreshed or switched when switch_to_blog is used (at this point a new set of options should be made available).

I'm using this code to get around it (this is not a patch, obviously):

/**
 * Hooks the WP MS action switch_blog to switch some caches cache when a blog
 * is switched to.
 *
 * @param int $blog_id The ID of the blog which has been switched to 
 * @param int $prev_blog_id The ID of the blog we were in before switching 
 * @return void
 **/
function kcostb_switch_blog( $blog_id, $prev_blog_id ) {
	// Save the previous alloptions cache
	$prev_alloptions = wp_cache_get( 'alloptions', 'options' );
	// Do we have a previously cached alloptions for the new blog? If so,
	// replace the current alloptions cache otherwise delete it.
	if ( $alloptions = wp_cache_get( "alloptions_$blog_id", 'options' ) )
		wp_cache_replace( "alloptions", $alloptions, 'options' );
	else
		wp_cache_delete( 'alloptions', 'options' );
}
add_action( 'switch_blog', 'kcostb_switch_blog', null, 2 );

Questions:

  • Should this be solved by switching caches (as my mini plugin does)?
  • Should we simply delete the alloptions cache when switch_to_blog is used?
  • Are there any other areas affected similarly?

Happy to produce a patch if the first two questions could be advised upon.

Could be related to #12040

Change History (19)

#1 @scribu
14 years ago

I also ran into this problem.

I think switching caches is the way to go, but only for one level of switching.

#2 @scribu
14 years ago

  • Severity changed from normal to major

Actually, switch_to_blog() should remain cheap to call. For instance, I'm using switch_to_blog() in one case to loop through all the sites in the network.

So, I think we should best leave this to be handled on a case-by-case basis.

Also, we should probably add this in the functions documentation. See #14953

#3 @hakre
14 years ago

Switch to blog is database only. Object Cache is not database.

Strictly spoken, this is not a bug.

As scribu already reported, this function is often not properly understood and therefore probably misused in some places. And as this ticket implies some of us might need more functionality in switching blogs, not only for the database context but also for the object-cache.

I suggest to refactor switch_to_blog into switch_to_blog_database() and deprecate the older one/change it's use to switch every context available for a switch (e.g. database and then probably soon objectcache).

Then other developers can implement new and needed functionality in similar named functions like switch_to_blog_objectchache().

Just a raw idea:

function switch_to_blog($blogid) {
  $blogid = normalize_blogid($blogid);
  if (!is_valid_blogid($blogid))
    return false;
  current_blogid = get_current_blogid();
  if (!switch_to_blog_database($blogid)) {
    return false;
  }
  if (!switch_to_blog_objectcache($blogid)) {
    switch_to_blog_database($current_blogid);
    return false;
  }
  return $blogid;
}

In contrast to my argumentation in #14953, I think that the object-cache as it is something quite related to the database, should be able to switch as well and it should be useful to provide that functionality as well ASAP. I wonder how wordpress.com is dealing with such issues, assuming that they should have a setup when this comes into play. Maybe some wpcom devs can provide some useful feedback.

#4 follow-up: @scribu
14 years ago

Related: #15361

#5 in reply to: ↑ 4 @prettyboymp
14 years ago

Replying to scribu:

Related: #15361

I believe both of these issues could be solved if the calls to wp_cache_init() are replaced with wp_start_object_cache() and the different cache providers correctly implement a wp_cache_reset() function that handles the reset accordingly, whether that is swithing to a new cache server, clearing local cache, or just doing nothing.

#6 @prettyboymp
14 years ago

  • Cc mpretty@… added

#7 follow-up: @Denis-de-Bernardy
14 years ago

Imho, this would be a bug in the object cache implementations. They should key things based on the blog ID, i.e.:

$this->cache[$blog_id][$key]

vs:

$this->cache[$key]

The built-in, memory-based object cache should do the same to set the example.

#9 @scribu
14 years ago

Related: #15548

#10 in reply to: ↑ 7 ; follow-up: @prettyboymp
14 years ago

Replying to Denis-de-Bernardy:

Imho, this would be a bug in the object cache implementations. They should key things based on the blog ID, i.e.:

$this->cache[$blog_id][$key]

vs:

$this->cache[$key]

The built-in, memory-based object cache should do the same to set the example.

So at this point, we just need a core members decision whether we want to tackle this by making it a requirement for cache implementations to also key by blog_id and lose the wp_cache_reset() and wp_cache_init() calls after the initial load OR if we just call a wp_cache_reset() method if it exists when the blog_id changes.

#11 in reply to: ↑ 10 @Denis-de-Bernardy
14 years ago

Replying to prettyboymp:

So at this point, we just need a core members decision whether we want to tackle this by making it a requirement for cache implementations to also key by blog_id and lose the wp_cache_reset() and wp_cache_init() calls after the initial load OR if we just call a wp_cache_reset() method if it exists when the blog_id changes.

My understanding is the decision will wait until 3.2...

http://core.trac.wordpress.org/ticket/15361#comment:9

#12 @markjaquith
14 years ago

  • Keywords 3.2-early added
  • Milestone changed from Awaiting Review to Future Release
  • Priority changed from normal to high

I think you all are on the right track. We don't want to do it by default... expensive to flush all that. We can discuss the various options in 3.2 early!

#13 @willmot
14 years ago

  • Cc willmot added

#14 @simonwheatley
14 years ago

+1 for Denis-de-Bernardy's suggestion, but with reluctance as it's going to take some time for the proposed changes to filter down to third party object cache plugin providers.

#15 @scribu
14 years ago

The memcached implementation already adds the blog id as a prefix:

http://plugins.trac.wordpress.org/browser/memcached/trunk/object-cache.php?rev=257064#L249

#16 @simonwheatley
14 years ago

With a persistent Object Cache (i.e. a memcached implementation):

  1. Create a post ID 10 in blog 2
  2. From site 2, switch to blog 1
  3. Get post 10
  4. Restore current blog
  5. Go to site 2 and amend post 10

WP will have automatically flushed the post 10 in the object cache for blog 2, but it is persisting in it's previous state in the object cache for blog 1.

I can't think of a sane solution to this problem, I think we just have to wait for the cache in blog 1 to expire. Caveat Auctor, I guess.

Separately: There is a way to add the $blog_id to W3TC Object Cache using the built in W3TC hooks, see this forum post.

#17 @RogerTheriault
13 years ago

It seems the switch / restore code doesn't always call wp_cache_init() to recreate the $wp_object_cache, so the key can be incorrect in certain cases. Hooking to switch_blog and doing something like
$wp_object_cache->blog_prefix = $blog_id . ':';
might help, in addition to the original poster's recommendation.

#18 @tollmanz
12 years ago

  • Cc tollmanz@… added

#19 @wonderboymusic
12 years ago

  • Keywords dev-feedback 3.2-early removed
  • Milestone Future Release deleted
  • Resolution set to worksforme
  • Status changed from new to closed

Using Memcached Object Cache

This works for me right away in trunk:

php -r "require 'wp-load.php'; echo get_permalink(1); switch_to_blog(2); echo get_permalink(1);"

The more advanced use case also works right away:

// post with ID #10 exists in both blogs

wp_insert_post( array( 'post_title' => 'Cash Money', 'ID' => 10, 'post_status' => 'publish' ) );
switch_to_blog( 2 ); 
wp_insert_post( array( 'post_title' => 'Money Cash', 'ID' => 10, 'post_status' => 'publish' ) ); 
switch_to_blog( 1 ); 
get_post( 10 ); 
restore_current_blog(); 
wp_update_post( array( 'post_title' => 'Cache Money', 'ID' => 10 ) ); 
$z = get_post( 10 );
echo $z->post_title . PHP_EOL;
switch_to_blog( 1 ); 
$z = get_post( 10 );
echo $z->post_title . PHP_EOL;
switch_to_blog( 2 ); 
$z = get_post( 10 );
echo $z->post_title . PHP_EOL;
exit();

I think these problems pre-date all of the switch_to_blog work Ryan has done recently: [21485], #21595, [21628], [21403], #21434

Note: See TracTickets for help on using tickets.