Tuesday, July 24, 2012

PHP offloading for better performance

So you recently launched your PHP application (read Magento) just to realize that it is really slow even though you have state of the art hardware, software and it is configured to kick ass. You decided to install varnish to help full page caching and your CDN, which it only has gzipped merged and minified assets, to serve content faster now your site seems to be flying but New Relic still reveals so many bottlenecks specially for the fillers you needed to keep some dynamic content rocking.

You decide to run promotion that would bring 20K plus customers and decided to add more servers, virtual machines and the cloud can be so awesome. Soon to realize that your site is now really slow specially when checking out and some time later your site is DOWN!!! WHY!?!!!
Because your reverse proxy is not serving all of the customers let alone is not serving the whole content from cache as it has to fetch some from the server, more on this later. So believe it or not, your server is still getting hammered and might be even worse than before.

Reverse proxies are really awesome but if applied to a inefficient application, it will come back to hunt you and be your worst nightmare.
Caching is supposed to be good but nowadays it has become the best way to hide all of the snafus and foobars in the application.

So what is the problem? For once almost everyone does development one way through the programming language they know and rarely, if any, venture or dare to think of a possible world outside of that realm. Aside from the copy paste dogma of course. 
One problem is that most people tend to try to keep the dynamic content in the page through a hybrid combination of fresh content (retrieved from the server) and cached content. Most caching systems have developed sophisticated algorithm to make it work which adds a new layer of complexity.
Let's go back to your PHP application, one of the areas that usually needs to be hole-punched is the header because it contains the cart widget, the account (log-in or out) and wishlist links. But do we really need to hole-punched it? Nop!

PHP offloading is a very useful technique to improve performance, it refers to efficently distribute the workload among all the servers or other programming languages in the sytem. Also known as Operation Driven Development (ODD) where the type of operation dictates in which server or programming language the task it will take place.

So let's take the cart widget and see how we could do it without having to even think about hole-punching:

Every time an operation that involves the cart object takes place, this one gets saved. This means when you remove, add or update an item in your shopping cart you get this event checkout_cart_save_after so why don't we create a cookie that contains off all the items information?

Snippets below, I assume you do Magento development:


In your module etc/config.xml add:
 
        
            
                
                    
                        phpoffloading/observer
                        cartUpdate
                    
                
            
        
        
            
                
                    phpoffloading.xml
                
            
        
    
Replace phpoffloading with the name of your model class.
Now in your observer add:
    /**
     * Process all the cart / quote updates to ensure we update the cookie correctly
     * @param type $observer 
     */
    public function cartUpdate($observer) {
        // using singleton instead of the event to use the same function for different events
        $cart = Mage::getSingleton('checkout/cart');
        $quote = $cart->getQuote();

        /**
         * @var $totals needed for the subtotal 
         */
        $totals = $quote->getTotals();
        /**
         * @var $checkouthelper used for translation and price formatting 
         */
        $checkoutHelper = Mage::helper('checkout');
        $items = (int) $cart->getSummaryQty();
        if ($items > 0) {
            $cartInfo = array(
                'subtotal' => $checkoutHelper->formatPrice($totals['subtotal']->getValue()),
                'items' => $items,
                'itemsmsg' => $items == 1 ? "item" : "items",
                'viewcart' => $checkoutHelper->__('View Cart')
            );
            $cartInfo = json_encode($cartInfo);
             $this->setCookie('cart_cookie', $cartInfo);
        } else {
            $this->deleteCartCookie();
        }
        return $this;
    }
In your template:
You have no items in your cart.
You can have this javascript either on the same template or add it as an external file:

Every time a page gets loaded now instead of going to the server the content will remain in the browser and you don't need to request content from the server again. This is a very small change how much improvement do I get? Say you go from 750ms to 2ms in the server response time what would you say? This is because varnish 1-) doesn't need to understand what ESI is and 2-) all of the content is served from cache. This doesn't apply solely to varnish but to Magento's own full page caching solution.


This approach works fine for: wishlist, logged in/out but it doesn't work quite well with recently viewed items. For that we'll do a follow up article.

No comments: