wp-e-commerce / WP-e-Commerce

WP eCommerce - The most popular independent eCommerce platform for WordPress
https://wpecommerce.org
GNU General Public License v2.0
214 stars 216 forks source link

Documentation for caching and WP e-Commerce - W3TC, Varnish etc #248

Open ablears opened 11 years ago

ablears commented 11 years ago

Caching is so important and with WP e-Commerce there are a number of gotchas that can cause problems. I'd love to see documentation covering this.

For instance, with W3 Total Cache, enabling database caching causes problems with the cart widget, whilst enabling the object cache in 0.9.2.8 causes the 'Oops, there is nothing in your cart' message on the checkout page. Rather than disabling the object cache entirely I'd like to know if I can add something to the 'Non-persistent groups' option to exclude certain objects.

Similarly with Varnish what exactly are the cookie exceptions for wp e-Commerce's customer meta API?

Having some documentation around this would really help us get the best performance from WP e-Commerce sites.

instinct commented 11 years ago

Is there a detailed issue associated with this problem?

I'd be happy to commission somebody to tackle this.

d

On 26/02/2013, at 1:28 PM, ablears notifications@github.com wrote:

Caching is so important and with WP e-Commerce there are a number of gotchas that can cause problems. I'd love to see documentation covering this.

For instance, with W3 Total Cache, enabling database caching causes problems with the cart widget, whilst enabling the object cache in 0.9.2.8 causes the 'Oops, there is nothing in your cart' message on the checkout page. Rather than disabling the object cache entirely I'd like to know if I can add something to the 'Non-persistent groups' option to exclude certain objects.

Similarly with Varnish what exactly are the cookie exceptions for wp e-Commerce's customer meta API?

Having some documentation around this would really help us get the best performance from WP e-Commerce sites.

— Reply to this email directly or view it on GitHub.

JeffPyeBrook commented 11 years ago

I have been doing some looking into this. I see the same symptoms in our system. The underlying cause seems to be our use of transients to store the customer meta data.

Transients were initially intended to store long lived results of complicated (expensive) operations. There is no guarantee that they are persistent. The default WP implementation stores the transients using a mechanism that is both shared and persistent. Unfortunately that means some code has become reliant on the shared and persistent behavior.

If you add an OP-Code cache like APC to a WP configuration, and add a caching plugin like W3 Total Cache you start to run into problems. When the cache is available transients go into the object cache, not the DB.

FIrst issue is that APC's cache is typically not shared between different CGI processes. I say typically because some hosting environments do the necessary work to create a shared cache. Your common shared or VPS hosting environment is not configured to allow a cache to share it's memory between processes. In some cases it may not be permitted. So what ahppens is a user comes into a WPEC site, adds something to the cart. Customer meta is stored into the APC cache of PROCESS A. Next page request is served by PHPCGI PROCESS B. It looks for the transient in it's APC cache. Not there, tells visitor cart is empty.

I ran into the second issue just the other day while investigating the first issue. When a site get's really busy the object cache fills up. Not shock there. When it get's to a certain utilization or fragmentation stuff starts getting flushed. It even appears that at some point of stress the whole cache is dumped. So we send out a blast email, lot's of people visit and start adding stuff the their carts. t's what we want. Then we hit some threshold and the APC cache is cleared. That means everybody just lost their cart.

Even if no op code cache is in use, I am also a little concerned about the several thousand customer meta records that can live in my options table after a busy period on our site.

Perhaps we can discuss an alternate store for the customer meta?

I'm doing 2 things on our site to address the issue. First is trying to remember enough about Apache and CGI to enable the APC cache to be shared between processes. If I can't figure it out I might look into other caches. Second is I am creating a "visitor" class to persistently hold meta information associated with semi-anonymous users. If it continues to work well I might add the wpec customer meta as meta into the visitor data.

benhuson commented 11 years ago

@SparkleGear As an interim solution, is there a way to exclude transients from being cached by W3TC while still allowing it to cache other objects?

I notice in the W3TC object cache settings there's a field for Non Persistent Groups that should not be cached but I guess that would not cache transients at all, not even in the DB?

Reading up about transients, I don't think they are really the best way to store basket data - you don't want to risk basket data just being lost..

Did you have any thoughts on an alternate store for the customer meta?

JeffPyeBrook commented 11 years ago

Caching of transients in general is not the problem, it's actually a good thing to do. It's the wpsc_customer_meta stuff that needs to be adjusted. If you want to enable the object cache it might be worth adjusting the code to store the customer meta in the options table with wp api. it's a temporary hack at best.

It might be better to try leaving the object cache off, and use the db cache? If you try this report back what works.

My visitor class is storing the visitor meta in the user table. That way when the visitor goes from anonymous to identified it's easy to retain learned information. I have also thought about the idea of creating a single post using a custom post type and storing the customer meta as post meta on that one post. Of course that storage mechanism would be hidden by a class interface. For some reason I don't feel like a new table is justified for this?

benhuson commented 11 years ago

Yep, I understand that caching transients is a good thing. Was just wonder if there was a way to 'turn that off' as a quick dirty hack. Will try the DB cache instead for the moment and see if that works - might cause an issue with other things like the cart issue mentioned above.

JeffPyeBrook commented 11 years ago

Might be worth trying to add "transient" and "site-transient" to the "Non-persistent groups" option in W3TC, also need to confirm that they are not in the "Global Groups" setting.

I don't think this is a long term fix for me because APC would become memory hog, at 64MB per PHP CGI process that would use about half of the memory I typically configure on my VPS.

benhuson commented 11 years ago

No, I agree that's not a long term fix.

JeffPyeBrook commented 11 years ago

From 4am to Noon I tried to get APC to use shared memory on my VPS, no go.

So my short term fix is to install memcached. It took about an hour to get up and running but transients and other cached objects are now being shared between all of my PHP CGIs. I left APC installed as the opcode file/cache, but took it's size down to slightly more than the maximum utilization shown in apc.php.

Onto the more relevant problem, where should we store the customer meta so that when the cache is flushed people don't lose their carts? I kind of like the idea of using the post meta table, that would make all of the customer meta cached without extra work. But some might consider it a somewhat obfuscated way to save data.

JeffPyeBrook commented 11 years ago

@benhuson I just reported a bug to W3TC for object caching. I was able to create a reproducible test case of setting a transient and then recalling it a short amount of time later finding it was not available. I replaced the object-cache.php with the memcached object-cache.php from the WP plugin repository and the problem goes away.

I also have been running an experiment storing some session meta as user meta attached to user id 1 (admin). It's working pretty well. It is cached without doing extra work,and easy to cleanup. Although it does have a somewhat hackish feeling. It suspect it will have the same performance characteristics as storing the data as post meta on a well known post id.

ace-dent commented 11 years ago

@SparkleGear - Although security conscious Admins like to remove user id '1' ;)

benhuson commented 11 years ago

For the record trying to add "transient" and "site-transient" to the "Non-persistent groups" seem to make the basket expire on every page load for me.

lunarman9 commented 11 years ago

What about going oldschool and storing customer_meta in delimited text in cookies that expire after, say, 30 minutes? Just reset the expiry on every request. Safe and secure, and would work independently of hosting company and DB load-balancing.

JustinSainton commented 11 years ago

Because we store the $wpsc_cart object serialized in customer meta, cookie storage is not a possible option, due to the limitations of cookie storage.

http://stackoverflow.com/questions/640938/what-is-the-maximum-size-of-a-web-browsers-cookies-key

The limit of 4KB (roughly) would be easily exceeded, I imagine.

lunarman9 commented 11 years ago

What about spinning out additional cookies when they get too full? Unusual, but elegant if programmed properly.

Implementing a guaranteed-persistent "caching" system outside of the WP Transient API would be a lot of work - it would require rigorous offline management (which most hosting facilities would not accommodate), could kill a website with lots of traffic, and would need something fancy to manage load-balancing.

JeffPyeBrook commented 11 years ago

I have been able to get something working in our site that might work if I can generalize it. In the implementation, the visitor data is being put into the user table as an anonymous visitor. At an appropriate time the information is removed. I think a better implementation would be a non-public custom post type 'visitor', using post meta for the attributes. Post and post meta caching is pretty good in WP and it would give the best performance when using an object cache, but would also work when not using an object cache.

My high water mark for the wpsc transients is 7892 stored in the database at any one time. I tracked it using the set transient and delete transient actions, and looking at the options table. This had a big impact on my site performance. The short term fix for me was to implement memcached. It is stable enough that the transients aren't getting lost when php or apache processes die a premature death, so lost carts aren't a frequent issue. And most importantly it keeps the transients out of the database.

JustinSainton commented 11 years ago

Because @garyc40 developed this API, I'm going to ask him to take a look at this for 3.9.0. @SparkleGear, if you'd like to be the testing guinea pig, that could be really helpful, you seem to be pretty knowledgeable with different caching mechanisms.

JeffPyeBrook commented 11 years ago

I would be happy to help. If you need the testing try to give me a couple of weeks heads up so I can regression test our sites to the latest bits. Last couple of revisions have needed a couple works to work out the issues.

A suggestion... One technique that is working well for me in speeding up my site is the create a custom post type that is not visible to users. Data I want to store is attached as post meta to this post type. THe post meta caching inside WP when using an object cache or not is pretty decent, so your don't need a lot of tuning. It's a good persistent storage mechanism

The advantage over using transients is that nothing is stored in the options table when transients are set. Many plugins try to cache data directly in the options table. THis can cause problems as the size of the autoload rows grows and has to be loaded on every page request.

An example in our store is where we store/cache variation related information on a custom post type we called an "article". Articles are just a CPT with a size and color variation taxonomy terms set on them. We put size charts, colors, google product attributes, color thumbs and current inventory information as meta onto the CPT. Works well for in our app.

instinct commented 11 years ago

Can you flick me a note on dan@instinct.co.nz - it'd be good to chat some more about what you guys do :)

Sent from my iPhone

On 5/05/2013, at 7:51 AM, SparkleGear notifications@github.com wrote:

I would be happy to help. If you need the testing try to give me a couple of weeks heads up so I can regression test our sites to the latest bits. Last couple of revisions have needed a couple works to work out the issues.

A suggestion... One technique that is working well for me in speeding up my site is the create a custom post type that is not visible to users. Data I want to store is attached as post meta to this post type. THe post meta caching inside WP when using an object cache or not is pretty decent, so your don't need a lot of tuning. It's a good persistent storage mechanism

The advantage over using transients is that nothing is stored in the options table when transients are set. Many plugins try to cache data directly in the options table. THis can cause problems as the size of the autoload rows grows and has to be loaded on every page request.

An example in our store is where we store/cache variation related information on a custom post type we called an "article". Articles are just a CPT with a size and color variation taxonomy terms set on them. We put size charts, colors, google product attributes, color thumbs and current inventory information as meta onto the CPT. Works well for in our app.

— Reply to this email directly or view it on GitHub.

garyc40 commented 11 years ago

See this discussion about meta starting from this comment: https://github.com/wp-e-commerce/WP-e-Commerce/issues/400#issuecomment-17860657

I'll post a pull request for this issue using WP core meta API approach with a separate table for anonymous customer meta. This should solve the cache issue you're having with separate processes.