The Best Creative Minds – Zynovo

Magento is our forte!

Zynovo is a full-service digital commerce agency, focused on end-to-end implementations of the most flexible enterprise-class commerce platform to help online merchants fulfill their business and e-commerce goals in a way that is both economical and efficient. We provide strategic planning, design, development and post-launch maintenance.

Mon – Fri: 09:00 am – 06:00 pm
Contact +1 (818) 743-2444
Follow

Magento 2 Full Page Cache Segmentation

This post gives an overview over what the full page cache segmentation is, what the system Magento 2 provides for it, and how we as developers can use FPC segmentation.
Then it addresses a common misconception that I’ve had myself and that I’ve also encountered in others.
I hope this post will save you or someone else the trouble of going through the same discovery, again.

 

What is it?

The FPC can be part of the PHP application (i.e. the “built in” FPC) or it can be an external reverse proxy (i.e. Varnish).

 

“FPC Segmentation” is that multiple versions of a page are cached. Then, for any given request, the correct version of that page is pulled out of the cache and delivered to the client.

 

Because the FPC is not necessarily part of the PHP application, the application environment can not be used to make the decision what version of a given page is delivered from the cache.

Only the information in the HTTP request may be used, that is, the URI, query parameters, the HTTP headers including cookies and some underlying transport layer information (mainly the client IP address).

 

An example use case for FPC segmentation could be a module that allows visitors to customize a shop based on their color preferences. Products are displayed in a visitors preferred color palette.

 

This means any given product URL will will contain different HTML based on the visitors setting.
The FPC can no longer use only the URL to determine the cache record to fetch, it also needs to take the visitors preference into account.

Storing the color preference in the visitor session will make it available to PHP and as such might be used with the built in FPC, but it is not sufficient for Varnish.

A visitors preference has to be stored as a cookie instead.

 

How does Magento 2 do FPC segmentation?

When Magento generates a page the response can be enriched with meta information about if and how it may be cached.
This is done via the shared instance \Magento\Framework\App\Http\Context.

 

When a page response is generated by Magento, this object is responsible for generating a cookie value that identifies the page variation.


// At the time the stack roughly looks like this:
// 
// \Magento\Framework\App\Bootstrap::run()
// \Magento\Framework\App\Response\Http::sendResponse()
// \Magento\PageCache\Model\App\Response\HttpPlugin::beforeSendResponse()
// \Magento\Framework\App\Response\Http::sendVary()

public function sendVary()
{
    $varyString = $this->context->getVaryString();
    if ($varyString) {
        $sensitiveCookMetadata = $this->cookieMetadataFactory->createSensitiveCookieMetadata()->setPath('/');
        $this->cookieManager->setSensitiveCookie(self::COOKIE_VARY_STRING, $varyString, $sensitiveCookMetadata);
    } elseif ($this->request->get(self::COOKIE_VARY_STRING)) {
        $cookieMetadata = $this->cookieMetadataFactory->createSensitiveCookieMetadata()->setPath('/');
        $this->cookieManager->deleteCookie(self::COOKIE_VARY_STRING, $cookieMetadata);
    }
}

 

This variation string is also used when generating the cache ID for the built in cache, and the same logic is also present in the varnish VCL configuration.

 

This means, whenever \Magento\Framework\App\Http\Context::getVaryString() returns a different value, a new cache record for a page will be used.

 

How can we create a new segment?

The string the Http\Context::getVaryString() method returns is a hash of a several pieces of data. In a native Magento installation the FPC is segmented by customer group, store view, currency and customer login status.
We as developers can add additional data to the values that are used as the source of the hash using Http\Context::setValue($name, $value, $default).

 

The $name is a string identifier that uniquely identifies our cache segment (for example COLOR_PREFERENCE).

 

The $value is the value for the generated page (for example light-green). The value is not limited to strings, but it has to be serializable with json_encode().

 

The $default argument is the value that should be assumed for first time visitors, that is all guests.

 

In the devdocs the recommended way to set additional values on the Http\Context is via a beforeGetVaryString() plugin.
Personally I don’t think this is a good idea; I generally would not expect an object to be mutated when calling a getter method.

Also the DepersonalizePlugin will have removed session values at the time the response is sent, which in turn would force us developers to hack around that limitation. Instead I recommend setting the addition value on the Http\Context before the page is rendered.


$colorPreference = $this->session->getColorPreference();
$defaultColorPreference = '';
$this->httpContext->setValue('COLOR_PREFERENCE', $colorPreference, $defaultColorPreference);

 

That is all that is required to create a new full page cache segment.

 

Caveats with FPC segmentation

It might be obvious, but sometimes it might still be tempting to create per-customer FPC segments, e.g. by adding the customer ID to the Http\Context.
This is a bad idea, as it causes a cache miss for every customer on every page until they have visited a page, defeating the purpose of the FPC.
It also bloats the cache, as a separate copy of each page is stored for every customer.

 

There also is another, less obvious, caveat.
I’ve made the mistake myself, and also have seen others, to try to segment the FPC cache for first time visitors.
A use case would be to display a different theme depending on a visitors country based on a GeoIp lookup result.
This is not possible using the FPC cache segmentation architecture without customization, because the full page cache uses the X-Magento-Vary cookie to determine what page to serve from the cache, and first time visitors do not have that cookie. It’s a chicken and egg problem.

 

To set the cookie, a request needs to reach the Magento application, which in turn means the request must not be served by the FPC.
However, serving all first time visitors directly by Magento and not out of the FPC violates the basic idea of the FPC.

 

The right solution to this dilemma depends on the use case.
Ideally the site can be planned in such a way that all first time visitors see the same page.
Alternatively the feature must be implemented using client side rendering with JavaScript and Ajax (note that customer sections can not be used for first time visitors either, but that is a different story for a different blog post).

Vinai is a developer and an open source enthusiast since 1998. He is also a Instructor for the official Magento U developer trainings by Magento, and a member of the Magento Certification Advisory Board and was one of the first to pass the MCD+ exam, and conference speaker. He is also the creator of the developer screencast service https://www.mage2.tv. You can read more about him at http://vinaikopp.com/about.