The cache is the most effective mechanism we have to improve a page performance.

Generally speaking there are two methods of caching content:

  • Server side
  • Client side

They provide the possibility to reduce the resources used by the servers and serve the content to the client in a faster way.
Basically what we do with the cache is to store the response of a page requested by a client so that the next time someone asks us we show it faster.

The Magento cache library contains a simple reverse PHP proxy that enables page caching (FPC). A reverse proxy acts as an intermediary between visitors and your application and can reduce the load on your server.
However Magento recommends the use of Varnish for production environments, which can store the cache files in the following systems:

  • File system (No need to do anything to use file-based caching)
  • Base de datos
  • Redis

Cacheable and non-cacheable content

Cacheable and non-cacheable terms are used to define when a page has to be cached and when it is not.
Examples of cacheable pages:

  1. Product list:
  2. CMS
  3. Product sheets

Examples of non-cacheable pages:

  1. User account
  2. Cart
  3. Checkout

Public and private content

Inverse proxies serve “public” or shared content to more than one user.
However, most Magento websites generate dynamic and personalized “private” content that should only be served to a user, which presents unique caching challenges. To address these challenges, Magento can distinguish between two types of content:

  1. Public: On the server side is where we store the public information of our stores (which is the same for all visits)
  2. Private: On the client side (browser) is where we store the private information (Customer data, shopping cart, wish list …)

Public content

Taking into account that at the server level is where we manage the public content cache, what can we do to manage page variations or how can we show different content depending on groups of clients, user segments …

Disable the cache on a page

To make a page non-cacheable, it is enough for one of its blocks to have the following directive in the cacheable xml = “false”. Special care with this directive, for obvious reasons.

[cc lang="html"]
<!--?xml version="1.0"?-->
<!--
 ~ @author Interactiv4 Team
 ~ @copyright  Copyright © Interactiv4 (https://www.interactiv4.com)
 -->

[/cc]

You can also disable the cache of a page by adding parameters to the header of a request from a Magento controller (Varnish only):

[cc lang="html"]
<!--?php
namespace Interactiv4\FPC\Controller\Index;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
class Index extends Action
{
   protected $pageFactory;
   public function __construct(
       Context $context,
       PageFactory $pageFactory
   ) {
       $this--->pageFactory = $pageFactory;
return parent::__construct($context);
}
public function execute()
{
$page = $this-&gt;pageFactory-&gt;create();
//We are using HTTP headers to control various page caches (varnish, fastly, built-in php cache)
$page-&gt;setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true);
return $page;
}
}
[/cc]

Set page variations

Many reverse proxies use the URL as a key for cached url records but in Magento the URLs are not unique enough to allow it, which can cause problems of:

  • Collision in the storage of the cache.
  • Unwanted information leaks (for example, website in French partially visible on a website in English, prices for groups of customers visible in public, etc.)

To overcome this problem we use HTTP context variables. Context variables allow the Magento application to serve different content in the same URL based on:

  • Clients group
  • Selected language
  • Selected store
  • Selected currency
  • If a client is connected or not.

Context variables should not be specific for individual users because the variables are used in cache keys for public content. In other words, a context variable per user results in a separate copy of the cached content on the server for each user.

Magento generates a hash based on all context variables

\Magento\Framework\App\Http\Context::getVaryString

The hash and the current URL are used as keys for caching.

This function allows us to generate the X-Magento-Vary cookie that is transferred to the HTTP layer. HTTP proxies can be configured to calculate a unique identifier for the cache based on the cookie and URL.

https://github.com/magento/magento2/blob/2.0/app/code/Magento/PageCache/etc/varnish4.vcl#L63-L68

What we can see in that example vcl, provided by Magento, is that if the HTTP layer receives the X-Magento-Vary cookie, it will take into account the content of this for the generation of the hash for that page.

\Interactiv4\FPC\Plugin\CustomerGenderContextPlugin.php

What would be a use of this functionality, for example in the Dufry project we could establish a variation of the cache of the page based on the ages of the clients, to show products with alcohol or without alcohol.

Invade public content

The content can be erased immediately after an entity changes. Magento uses IdentityInterface to link entities in the application with cached content and to know what cache to delete when an entity changes.
To make our modules use this system we have to do the following:

  1. The model that our entity manages has to implement the following interface.

Magento/Framework/DataObject/IdentityInterface.php

  1. The block that shows content of our entity and that uses the previous model also has to implement the previous interface

We can use the category entity to see how it works. If we go to the category model, we see that it uses the function getIdentities:

/Magento/Catalog/Model/Category.php

And this method is referenced in the block that paints the category.

/Magento/Catalog/Block/Category/View.php

Magento uses cache tags to create links. The performance of caching directly depends on the number of labels per cache record, so you should try to minimize the number of labels and use them only for entities that are used in production mode. In other words, do not use the invalidation for actions related to the configuration of the store.

But in the end what do they do? These methods are used by Magento to set the X-Magento-Tags cookie in the header of each request.

This header is used in the process that generates and stores the cache. We can see the process in the following file:

Magento/Framework/App/PageCache/Kernel.php::process()

Important to keep in mind is that the Identities are for the FPC. For the cache of the blocks we must continue implementing the cache_tags and cache_lifetime.

If we look at the next class, we see that Varnish also takes into account this cookie when generating the ESI Blocks.

Magento\PageCache\Controller\Block\Esi.php

Apart from implementing this interface in our modules we also have to take into account how we can use plugins to modify the generation of the Tags of the Magento blocks in order to add logic to the invalidation of caches.

Checklist of cacheable page

  • Pages use GET requests
  • Pages only render cacheable blocks
  • The pages are processed without confidential private data.
  • The specific functionalities for both the current session (client) and the page must be written using JavaScript (for example, the list of related products must exclude the items that are already in the shopping cart)
  • Models and blocks have to be correctly identified for invalidation support
  • We will create context variables if we want to show different public content information using the same URL

Page checklist not cacheable

  • Use POST requests to modify the status of Magento (for example, adding shopping cart, wish list, etc.)
  • Blocks that can not be cached should be marked as non-cacheable in the design. However, keep in mind that adding a non-cacheable block to a page prevents the full-page cache from caching that page.
  • Drivers that do not use designs must set HTTP headers without cache

Private content

As private content is specific to each user, it makes sense that it is managed by the client side or browser. Magento uses the customer-data javascript library to store user content in the browser’s storage space, invalidate private data using customizable rules, and synchronize data with the server.

Magento/Customer/view/frontend/web/js/customer-data.js

Create a private content source

If we want to show private content of the users in any of our modules we must create a new “Section source”.

Magento recommends using the following nomenclature in order to standardize all the Customer Source Section.

Vendor/ModuleName/CustomerData

The classes that we create to show the user’s content should implement the following interface and make use of the getSectionData method

Magento\Customer\CustomerData\SectionSourceInterface

We will have to define said Data source in our file di.xml

magento/module-catalog/etc/frontend/di.xml::46

This configuration links to the DataSource file that will be used in our template.

\Magento\Catalog\CustomerData\CompareProduct

Render private content

Once we have defined the data Source we need a block and a template to show the private content. This private content will be rendered using the Ui component.

The private content will be replaced using placeholders in the blocks (using the Knockout syntax). To let Knockout know the origin of the private data that you have to insert in our template, we will have to specify the following element data-bind = “scope: ‘compareProducts'”, which marks the link with our compareProducts declared in the file di.xml , in the html.

module-catalog/view/frontend/templates/product/compare/sidebar.phtml

From this phtml we can see how calls are made to the DataSource data defined in compareProducts.

<a data-bind="attr: {'href': compareProducts().listUrl}"

Invalidate private content

To be able to invalidate the cache of the blocks that generate private content we will have to configure the file sections.xml

magento/module-catalog/etc/frontend/sections.xml

This file defines a series of POST and PUT requests that invalidate the cache of the block used by the DataSource compareProducts

(Do not miss our posts #Codehacks)