Author - Pradip Shah

Magento and graphql : A slowdown saga

PWA, Magento and graphql : A slowdown saga

Note : I write this article as Magento 2.4.5 is released and fixes some major performance issues that have resulted in the dream of headless and PWA go sour for many early adopters of technologies like PWA Studio and Vue Storefront 2.

PWAs and headless came with the promise of fast navigation and have had a rough ride at best, especially with Magento backends. Early PWAs like Vue Storefront 1 were implemented using Magento Rest API with a api middleware in nodejs that used elastissearch to store cached version of magento catalog for faster access. Magento implemented Graphql to avoid the need for the rest api. Use of direct rest api is ill-advised without a middleware as it exposes a Magento admin token. A middleware would sanitize the request possibly by checking a cookie and also limit the range of queries run. Graphql prevented the use of admin tokens and instead relied on frontend cookies for authentication.Magento’s REST API also results in large datasets. Graphql has a powerful, standardized query syntax to theoretically present a path breaking solution to build Magento headless around. However, early implementations did not fully cover the entire magento data model – Magento even advised the use of REST api when graphql was not sufficient. And when real world websites with large builds and catalogs were deployed, performance issues emerged.

Graphql are ajax calls in Magento that use sessions. In a typical hit processed by Magento, a session is opened, the hit is processed and then the session is closed. (A hit here is either a frontend hit or an ajax call).

Sessions and race conditions

Graphql are ajax calls in Magento that use sessions. In a typical hit processed by Magento, a session is opened, the hit is processed and then the session is closed. A hit is either a frontend hit or an ajax call.

Magento can store information in sessions – cart data for example. In order to prevent 2 hits from writing contents to the session simultaneously – called a race condition, sessions are locked for the duration of the processing a hit. (Take for example a checkout flow and let us assume that the address and payment info is written to session. You would want to ensure both get written. If both “raced”, only one piece of information would be written). Magento identifies an internal session by setting a cookie in the visitors browser. Each hit carries the cookie allowing magento to identify the session. Since a session is unqiue for a visitor, so Magento can process process only one hit per visitor at any given time.

With ajax in general and graphql in particular, many simultaneous hits may go to the server. Magento, through the session locking mechanism, queues them up. The hits are not guaranteed to be processed in the order they were received or any order in particular.

A typical session content in redis looks like

hgetall sess_eba681783f7000bc668cc45005aeaca4
1) "lock"
2) "0"
3) "writes"
4) "5"
5) "data"
6) "_session_validator_data|a:4:{s:11:\"remote_addr\";s:14:\"116.58.200.181\";s:8:\"http_via\";s:0:\"\";s:20:\"http_x_forwarded_for\";s:0:\"\";s:15:\"http_user_agent\";s:117:\"Mozilla/5.0 (Linux; Android 10; RMX2030) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Mobile Safari/537.36\";}_session_hosts|a:1:{s:10:\"host.com\";b:1;}default|a:0:{}customer_base|a:2:{s:11:\"customer_id\";N;s:17:\"customer_group_id\";N;}checkout|a:0:{}"
7) "wait"
8) "0"
9) "pid"
10) "10.0.2.197.host.com|3735"
11) "req"
12) "POST host.com/graphql"

Most of these entries are used internally by the redis session module. Entry 6 is the main session, stored in php serialized format.

If each hit takes only a few milliseconds and the number of ajax calls are not high, the overall response time may not perceptibly slow the site.

But, if even one ajax hit takes time, the page load can slow down. If a customer opens multiple tabs or refreshes the page, the slowdown may be more severe, sometimes leading to errors.

Here is an example. We are analyzing server logs based on IP. Lines 1 and 2 indicate the same link in google was clicked twice – possibly in 2 tabs on the browser. As the page loads we see graphql’s being loaded. The last 2 items are an indication of the problem.

The 2nd last hit is the slow hit – it took 10 seconds to return a result as the item was not in cache. The problem in the last hit was that it was waiting for the 2nd last hit to be processed, although in all probability it was in cache. If the number of items in this queue grows above 6 – for example if a visitor opens tabs and accesses urls not in varnish, a 500 error may be returned, possibly resulting in the UI showing a block not fully loaded at all. A refresh typically seems to solve the problem.

Server can run out of php resources

When too many graphql calls wait, server side can run short of php resources. This time you can see 504 and 502 errors. Adding more php resources for the same server risks running out of RAM. Adding more servers does help with the problem, but that is a waste of resources.

Graphql and caching issues

Graphql in Magento needs many caches to perform well. However, upon cache clear the “galloping horses” problem can bring down a busy website.

Upon cache clear the first access to graphql results in the schema being read. The performance for this action has improved substantially since Magento 2.3, but we yet highlight it as a concern. A cache warming of this is preferred.

Queries like filter in a category page can be slow and hence caching and warming some queries is always a good idea.

Enabling varnish cache is also important – varnish does not use sessions.

Recommended way to use Magento graphql

Graphql needs authorization – it needs to know what customer is asking for the information. It needs it so that the results include say for example the correct tiered pricing for this customer. While sessions include this information, they also store state and hence need the protection of a lock.

Magento supports JWT customer tokens for graphql.
https://devdocs.magento.com/guides/v2.4/graphql/mutations/generate-customer-token.html

Use the generateCustomerToken mutation to create a new customer token.
Example :

mutation {
  generateCustomerToken(
    email: "bobloblaw@example.com"
    password: "b0bl0bl@w"
  )
}

Response

{
  "data": {
    "generateCustomerToken": {
      "token": "ar4116zozoagxty1xjn4lj13kim36r6x"
    }
  }
}

Magento suggests NOT enabling cookie and JWT token authentication at the same time to reduce the chances of encountering problems caused by the differences between the two authorization methods. However, disabling cookie was added a feature only in Magento 2.4.5

However, Magento 2.4.5 varnish sample configuration disables varnish caching for logged in user. This can reduce performance.

Conclusion

Magento performance continues to be a troublesome aspect of the eCommerce platform. At luroConnect we look at performance in great depths.

Would you like to switch to a modern hosting platform?

Schedule a call of a free evaluation!

With features like ~0 downtime code deploy and many scaling options to reduce your hosting costs, luroConnect offers you unparalleled hosting environment for eCommerce – specifically Magento and various PWA / Headless technologies.

Schedule a call and we will show you how we can

  • Improve your hosting, possibly with autoscale
  • Have a managed dev, staging and production environment
  • Server performance measured every minute with alerts for a slowdown
  • A multi point health check every day
  • Optimize hosting costs

Core Web Vitals : Content Layout Shift (CLS)

Introduction

Core Web Vitals contain 3 factors to consider for the speed of your website – LCP or Largest Contentful Paint, FID or First Input Delay and CLS or Cumulative Layout Shift.

This article explores CLS or Cumulative Layout Shift.

CLS measures visual stability of your web page on a given device.

What is CLS?

A Layout Shift occurs anytime a visible element (for example top of fold on an iPhone) changes position (i.e an element’s top left corner moves) as the page is being rendered.

Cumulative Layout Shift is the total of layout shifts until viewport is fully rendered.

When you open a web page and the content moves suddenly, it causes high irritation and can result in a visitor to “bounce” from the site. Some news websites with dynamic ad inserts are notorious, but even slight movement on an eCommerce site will cause visitor frustration.

Imagine you are on a product detail page (PDP) and as the page opens, you do see an “add to cart” button. You try to hit the add to cart button, but by this time the image begins loading and you have to wait until the UI stabilises. In Magento a configurable product page often behaves like this. Another common scenario is that the page shifts as a new font is loaded as the original text was rendered in the substitute or default font.

What are the common reasons for CLS in a Magento Website?

A fundamental reason why CLS occurs is that a responsive HTML is not designed for all widths under consideration.

  1. A javascript loading a dynamic DOM with styles not considered in the original HTML page
  2. A 3rd party plugin takes over the HTML design of the page.
  3. During HTML design, responsiveness to a mobile layout is not planned, but happens due to use of a responsive css such as bootstrap.

Let us look at specific reasons with the hope to come up with best practices.

  1. Use of <img> tag without width and height. Since space is not reserved and fixed, the width will be the actual width of the image – which will be known only once the image is loaded.
  2. Use of javascript to display images late. Most likely the javascript will redo the DOM causing a layout shift at the point of the insertion.
  3. Use of multiple fonts. As the fonts are loaded the text changes its flow
  4. Not knowing how layout shift is calculated. Chrome calculates layout shift as the page loads. It is cumulative. A small shift in the top part of a page can cause a large shift below.

Examples

  1. If a lazy loader is used to load images in a category page, it is common to see the placeholder image being a loading gif. Quite often the gif is not of the same size as the image being loaded. When the final image loads, a layout shift happens. This may apply to various widths designed for.
  2. In designing a product page with image on the left and product name and brief description on the right, it is common for the UI designer to use “float:right” on the text on the right and let the image load in javascript later. However, for mobile, the text will most likely come below the image and a layout shift will happen when the image is loaded.
  3. Hero images and carousels may be loaded later as following a guideline to run “all javascript late”. If space is not reserved, a layout shift will occur.

In the example below, a menu is inserted after the banner is loaded for the mobile, causing a CLS.

Stay tuned to this series on Core Web Vitals for Magento. We will be looking at specific examples and solutions.

What should (and should not be) in a robots.txt file?

What is robots.txt?

robots.txt is a file, accesible as a URL https://<domain>/robots.txt that tells crawlers and BOTs what URLS crawlers can access on the domain / website.

However, there is a lot of confusion among internet marketers on its value and power.

The format of the file is very simple. Instructions to allow or disallow paths per user agent are grouped together. The user agent itself is a shortened version of the user agent header and each BOT publishes its own shortcode. Google for exanple has a top level Googlebot but has many others for images, ads, etc.

In addition a global setting in robots.txt is sitemap. This directive tells all crawlers where the sitemap file is. Most commonly it is an xml file.

Each crawlers is free to interpret the robot.txt, including if it should honor the directives.

Do you use Disallow in robots.txt

SEO experts and internet marketers would like to think that by adding a Disallow of an existing URL will cause google to not index the page.

That is not true – google states this in many documentations. At one place it puts up a warning

“Warning: Don’t use a robots.txt file as a means to hide your web pages from Google search results.

If other pages point to your page with descriptive text, Google could still index the URL without visiting the page. If you want to block your page from search results, use another method such as password protection or noindex.”

We actually see internet marketers in the Magento space browse the internet and get a suggested robots.txt with perhaps the longest content they can find, thinking the more Disallows they have the better google will crawl.

Security Issues with bad disallows

We see commented sections saying “#Directories”.

Little do they know they are exposing the internal directory structure of the application with this. And since robots.txt is available to everyone including bad actors, you have just created a security issue.

We also see some marketers give a huge list of crawlers they don’t want on their website. This is completely unnecessary. Tell your hosting provider. If you are with luroConnect, all our plans include “BOT Blockers”. Just let us know and we will match a real HTTP User Agent header and block it.

What would be the best way to structure a robots.txt?

There is no significance of allow!

User-Agent : *
Disallow : /<path visible to user you don’t want crawler to go like product compare>
User-Agent : bingbot
Crawl-delay : 1
Sitemap : <sitemapurl>

Note though Google suggests using noindex on the pages you put in disallow.

Another interesting fact is google documents that it caches robots.txt for 24 hours. Essentially meaning that cache control headers are ignored by google. However, a browser does cache robots.txt, so when checking the current robots.txt of a website that google will see, please disable your browser cache.

So, why a note on robots.txt on a managed hosting blog?

We are a hosting platform that understands the importance of SEO and have acted on it.
We know and keep track of robots.txt, sitemaps and feeds for all our customers.

We also feature a very strong BOT Blocker – regular expression match any user agent string, identify googlebot imposters and block them, basic authentication password protected staging and dev sites to ensure they don’t get crawled.

If you are SEO knowledgeable and do not find the recommendation acceptable, we encourage you put your comments in this blog. We will acknowledge and publish all updates we accept.

Varnish, Magento and frequent product updates

In response to Erwin Hofman’s M2 performance post, a reader commented

Varnish and Magento 2

Use of varnish with Magento has been a popular speed increase. So popular, that Magento decided to incorporate in M2.

This has spread endless debates though

  • Would it not have been better to improve the core code quality instead?
  • How often and which pages do we put in cache?
  • How to organize user personalization content? (Magento has answers in terms of ajax and ESI, but I am afraid the documentation is so spread around, many developers and plugin vendors are not sure).

The question raised by the reader is another point of debate – how can a Magento store, with frequent product updates be made ready for using varnish.

Let us see how Magento works with Varnish – or what it means to say M2 supports varnish.

(Refer this article for a detailed analysis.)

Magento cache has always (yup M1 also) supported tags in caches. A typical cache is a key->value pair, typically implemented as a hash table to lookup the key and return the value. In case of a full page cache, the key is made up of the the URL and parts of the HTTP header that forms the request. When the key is found in the hash table lookup operation, it is a hit and the content is returned. When the key is not found, the request is forwarded to magento to process it. When Magento processes it, the key and the content is stored. The content is the entire HTTP response, including HTTP response headers and HTTP body.

When Magento processes a request, it returns in a HTTP response header, a “hint” header called “X-Magento-Tags”. This header has information about what type of content the page holds.

For example, a category page will have tags that represent the category and each product in that category. A product page would have a tag that represents that product.

This allows Magento to selectively clear the Varnish FPC, say when a product’s content is modified, by sending a special header to varnish to PURGE pages that match the product’s tag.

This is a communication between Magento and Varnish.

(Magento is aware of the location of varnish by the “http_cache_hosts” setting, Varnish is aware of Magento backend IP in its acl_purge list).

Indexing

In order to understand how Magento triggers cache clear events, we need to understand indexing.

Magento has many indexes – a mechanism by which changes made to admin are reflected on the frontend (the end user UI). Varnish only needs to be cleared when Magento moves products to frontend.

If index modes are set to update on save, each product save will result in corresponding pages to be removed from varnish. When making large changes such as with a csv upload, this will result in many pages being cleared multiple times. Note that a product save results in both the product as well as all the category pages of the category / categories the product belongs to.

If indexing mode is set to on schedule, a cron task that processes the “index” group is responsible for running indexing. If acceptable to the business, indexing can be set to run every few hours, reducing the rate at which varnish clears.

If you want a no hassles managed hosting, get in touch with luroConnect. We even give you a free assessment of your website.

We can analyze your site for free

Schedule a call

Not happy with your website performance and want an expert to look at it?

  • We will analyze your site using public information.
  • We will ask you to give us a 1 day web server log file.
  • We will try to identify what steps if any you should take to improve your sites performance goals.

Case Study : How a simple code change had a major impact on website performance

During black Friday sale, one of our customers’ websites was too slow and unusable for long periods. We were embarrassed, but all our analysis pointed to code. As part of our active support, we help with identifying code bottlenecks. We use many techniques including profiling, but a simple technique for a really slow website is analyzing the php slow log.

Observations

We track slowest URLs and chart them on our dashboard. A typical days chart looked like the one below. Most of the slow URLs were category listing page urls.

What is the php slow log?

The PHP slow request log is where PHP records information about any request that takes more than “x” seconds to execute. The number of seconds is configurable in the php-fpm.d/www.conf file.

The PHP slow request log records stack traces (also known as tracebacks) of each slowly executing script at the moment the request passed “x” seconds in execution. For example the following trace was generated for the website we were optimizing. The first line gives the date and time, the php-fpm pool name and the pid of the fpm process. The second line names the script – for Magento it will be the index.php always. The following lines have the memory address, the function name, the file name and the line number. A stack trace always lists the last call first.

[03-Dec-2020 22:54:57]  [pool www] pid 19267
script_filename = /www/usa/index.php
[0x00007ffa5ec21d30] execute() /home2/customer/77faf58/vendor/magento/zendframework1/library/Zend/Db/Statement/Pdo.php:228
[0x00007ffa5ec21c80] _execute() /home2/customer/77faf58/vendor/magento/framework/DB/Statement/Pdo/Mysql.php:93
[0x00007ffa5ec21be0] _execute() /home2/customer/77faf58/vendor/magento/zendframework1/library/Zend/Db/Statement.php:303
[0x00007ffa5ec21b30] execute() /home2/customer/77faf58/vendor/magento/zendframework1/library/Zend/Db/Adapter/Abstract.php:480
[0x00007ffa5ec21aa0] query() /home2/customer/77faf58/vendor/magento/zendframework1/library/Zend/Db/Adapter/Pdo/Abstract.php:238
[0x00007ffa5ec219d0] query() /home2/customer/77faf58/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php:541
[0x00007ffa5ec218a0] _query() /home2/customer/77faf58/vendor/magento/framework/DB/Adapter/Pdo/Mysql.php:615
[0x00007ffa5ec21810] query() /home2/customer/77faf58/vendor/magento/zendframework1/library/Zend/Db/Adapter/Abstract.php:737
[0x00007ffa5ec21760] fetchAll() /home2/customer/77faf58/vendor/magento/framework/Data/Collection/Db/FetchStrategy/Query.php:21
[0x00007ffa5ec216e0] fetchAll() /home2/customer/77faf58/vendor/magento/framework/DB/Query.php:182
[0x00007ffa5ec21660] fetchAll() /home2/customer/77faf58/vendor/magento/framework/Data/AbstractSearchResult.php:233
[0x00007ffa5ec215c0] load() /home2/customer/77faf58/vendor/magento/framework/Data/AbstractSearchResult.php:107
[0x00007ffa5ec21550] getItems() /home2/customer/77faf58/vendor/magento/module-catalog-inventory/Model/StockRegistryProvider.php:143
[0x00007ffa5ec21440] getStockItem() /home2/customer/77faf58/vendor/magento/module-catalog-inventory/Model/StockRegistry.php:88
[0x00007ffa5ec213c0] getStockItem() /home2/customer/77faf58/vendor/magento/module-catalog-inventory/Model/Plugin/AfterProductLoad.php:40
[0x00007ffa5ec212b0] afterLoad() /home2/customer/77faf58/vendor/magento/framework/Interception/Interceptor.php:146
[0x00007ffa5ec21150] Magento\Framework\Interception\{closure}() /home2/customer/77faf58/vendor/magento/framework/Interception/Interceptor.php:153
[0x00007ffa5ec21070] ___callPlugins() /home2/customer/77faf58/generated/code/Magento/Catalog/Model/Product/Interceptor.php:65
[0x00007ffa5ec20fe0] load() /home2/customer/77faf58/app/code/Plazathemes/Bestsellerproduct/Block/Bestsellerproduct.php:112
[0x00007ffa5ec20ef0] getBestsellerProduct() /home2/customer/77faf58/app/code/Mageplaza/CatalogPermissions/Observer/ProductLoadAfter.php:79

How do you trace from a hit to a slow log entry

From nginx we can trace to the slow log using a match in the php-fpm access log. We format nginx and php access log in a way that helps tracing.

The nginx log entry that we traced using the format

$remote_addr|$remote_user|$time_local|$request_time|$upstream_response_time|$request|$status|$body_bytes_sent|$http_referer|$http_user_agent|$host|$upstream_addr

127.0.0.1|-|03/Dec/2020:22:55:01 -0600|8.966|8.967|GET /usa/health-and-rejuvenation/cereals-health-mix HTTP/1.1|200|20458|https://www.ishalife.com/usa/yoga-gear/bedding|Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36|www.ishalife.com|10.120.0.7:9000

php access log format used
access.format = %R - %u %t %m %r%Q%q %s %f %{mili}d %{kilo}M %{total}C%% %p

php access log entry to the traced url (matched by time and response time)

10.120.0.2 - 03/Dec/2020:22:54:52 -0600 GET /usa/index.php 200 /home2/customer/www/usa/index.php 8962.264 4096 48.20% 19267

The %p records the process id of php-fpm that processed this hit. Based on the time and process id, the slow log entry can be accurately traced.

Analysis

The stack trace clearly indicated that an observer was called when the event “ProductLoadAfter” was called. For this website, it slowed down the category display as the call was made for each product. A peep into the code in productloadafter observer was loading “Best Seller Products”. So, for each product in the category, it would load the (almost) identical set of best seller products. The observer object was not even used.

Let us look at the code

public function execute(Observer $observer)
    {
        /** @var AbstractCollection $productCollection */
        
           $productCollection = $this->productBest->getBestsellerProduct();
       
           foreach ($productCollection as $key => $product) {
                /** @var product $product */

                $productObject = $this->productRepo->getById($product->getId());
                if ($this->helperData->validateConditionDefault($productObject, ObjectTypeAction::TYPE_PRODUCT)
                    && !$this->helperData->checkIPPassAllRestrict()
                ) {
                    /** @var DataObject $productObject */
                    if ($productObject->getData('mpcp_usecf_hideaction') === '0'
                        && $this->helperData->getProductHideCatAndWidget()) {
                        $productCollection->removeItemByKey($key);
                    }
                    // do not use config hide action
                    if (!$productObject->getData('mpcp_usecf_hideaction')
                        && $productObject->getData('mpcp_hideaction') === '1') {
                        $productCollection->removeItemByKey($key);
                    }
                }
            }
    }


The solution

Remove the observer as apart from badly written, the result was never used on the website. Code analysis revealed 2 unused plugins.

2 unused plugins and 1 observer call was removed, resulting some category pages loading 10x faster!

Before and After stats

We monitor hits in real time and chart hits / minute and avg response time per minute. In the graphs below, the left axis is hits per minute (plotted in turquoise) and the right axis is average response time in seconds per minute (plotted in black).

BEFORE

AFTER

CPU usage of php servers

BEFORE

AFTER

Database CPU usage across multiple days

Conclusion

The importance on good code and possibly the negative effects of unused functionality was highlighted in this case study.

As users of plugins we need to understand the motivation of plugin vendors to pack in substantial functionality leads to undesired side effects, especially on functionality you don’t want to use. Budget to clean up vendor code before using.

Sansec reports new Magento 1 hack

Over the weekend of Sept 11, 2020, Sansec reported a web skimming attack on Magento 1 stores. It was the largest single day automated attach recorded by Sansec.

What are skimming attacks?

“Skimming” attacks are malicious code added to your website so when a site visitor is entering any personal information including credit card, the content is “skimmed” and sent to the attacker.

The website looks completely normal.

In September 2018, British Airways revealed that 380,000 passenger information had been skimmed from the website. The modus operandi for this attack was access to the code (possibly the version control of a 3rd party javascript module used on the website). The attack went undetected for months.

Some attacks are also called “Magecart” attacks.

In Magento we see 2 popular ways

  • Break the admin password and upload content to “Miscellaneous Header” or “Miscellaneous Footer” sections.
  • Upload a php code file which in turn loads the real malicious code in javascript in the page – either directly into the page or modify a known javascript file.

The current attack is of the second variety.

Stores on luroConnect were not attacked!

The attack as described by Sansec in the article used the Magento connect to bypass Magento admin and upload malicious code in javascript files to facilitate skimming of credit card information.

luroConnect has many rules that helped prevent this attack from affecting any of our Magento 1 websites.

Rule 1 : “/downloader” URL  is not accessible on any live or staging website. We expect code to be deployed through git and expect the developer to use a manual process to install modules. We disallow magento connect based installation in any of our managed websites.

Rule 2 : Our web directory owner and hosting users are different. Hosting user is the user php code runs as. Moreover, /skin folder is not writable by the hosting user.

Rule 3 : We use a static minifier and deploy the code to a folder skin.min which is not in git. The /skin folder itself is never used.

Rule 4 : Staging and dev environments are protected using a HTTP Basic Authentication. Automated attack vectors would need to add a password guesser before they can reach the staging URLs. This is assuming a developer would have relaxed permissions in the dev environment.

Rule 5 : Our platform bars ssh access to the hosting user. This prevents any accidental change in permissions being permanent. Even in the rare case ssh access is given (for debugging purposes), upon relinquishing the access, we sanitize the environment with default permissions.

How to protect your store?

One of the best ways is to sign up for Sansec’s security scanner eComscan

luroConnect is a very secure platform for Magento hosting. We call it layered security – from a secure file system and strict folder permissions, to an inbuilt WAF with configurable rules to partnering with Sansec for security scans.

We host you on your cloud or physical hardware using our stack. Learn more about our plans here.

Supercharge your Magento with a Varnish cluster

What is a varnish cluster?

A varnish cluster is a set of varnish nodes, each in a different geographical location, in front of the same Magento backend.

As shown a Magento hosted in the US East region can serve varnish nodes from across the world. Access to the website from each region is directed towards the nearest varnish, benefiting from lower latency and faster page loads.

If you serve customers in different regions – internationally or across the USA, your store can benefit from a varnish cluster.

Varnish and Magento performance

Magento 2 was architected to work with Varnish for improved performance. A typical webpage – category listing or product detail page – when returned from a cached varnish page (called a HIT in varnish) typically has a TTFB of a few milliseconds. An uncached page (also called a MISS in varnish) results in TTFB of a few seconds. Optimizing a MISS is very crucial, but we will not cover that in this article. We believe varnish is one portion of optimizing your website.

What is Network Latency

Latency is the time it takes for a network packet to go from your computer to the server with a request and come back with a response – assuming the response from the server was available immediately. When a page scores a HIT in varnish, the response is almost immediate from the server. Any  Time To First Byte (TTFB) recorded on the browser can be attributed to network latency.

If you market your website to many regions around the world, and you host varnish in a single location, your visitors may be faced with higher latency. When latency of access is in a few hundred milliseconds, it becomes the bottleneck and needs optimization.

The below information from https://wondernetwork.com/pings/ gives an idea of expected latencies. To read the table use the row header as source and column header as destination. So, a ping latency from Los Angeles to Mumbai would be 267 msec.

What is the cause of latency?

Core latency is a function of distance – even light will take ~40msec to travel 13,000 km – the distance from New York to Mumbai. A network packet travels through wires at that speed, and network wires are much longer than a straight line. Moreover, a response packet has to travel all the way back.

Latency is also caused as traffic goes through network equipment. The number of switches / servers a packet has to go through depends on the network and service providers – yours as well as at the server you are connecting to.

A varnish cluster architecture

Using a GeoIP based DNS service such as AWS Route53 a users request for a domain is redirected to one of several IPs. The Magento backend is in the “default” region – where maximum traffic is expected. A varnish in the regions desired – shown US East and Europe below.

Geo-IP based DNS

A geoip based dns router such as AWS route53 can help direct traffic to the nearest varnish node based on the guessing the country the IP requesting name resolution is coming from. So users on a browser say in Australia would be directed to be served from the varnish node in Australia and one from the west states of California would be directed to the varnish there.

Since IP to region or country can never be so accurate, it should be possible for all varnish nodes to serve customers from any region. Specifically, a language or currency switcher should be available on the website.

Magento 2 supports multiple varnish nodes out of the box

An important feature in using a cache in production is its need to automatically and quickly clear contents on demand from the user and application. For example, when a product goes out of stock, the corresponding page should display the out of stock label. Magento uses a tag-based system to flush appropriate content from the cache. Magento allows setting up multiple varnish hosts and a tag-based cache clear is sent to all the hosts.

bin/magento setup:config:set --http-cache-hosts=<varnish internal ip>:6081,<varnish internal ip 2>:6081

Refer : https://devdocs.magento.com/guides/v2.4/config-guide/varnish/use-multiple-varnish-cache.html



Challenges in a varnish cluster

A varnish cluster is more complex to manage

Ensure the varnish vcl files, front end security configuration (WAF, rate limiting, etc) is managed and kept in sync on all edge nodes.

Managing includes monitoring to ensure none of the servers go down. Now, your site can be down in a specific region for example. Typical monitoring tools such as Pingdom would not work. A purposeful monitoring solution is needed.

A varnish cluster costs for additional servers

Since these would be frontend servers, the amount of RAM and their network speed requirement would depend on what traffic they get.

Number of varnish nodes

Increasing the number of nodes in a varnish cluster does not always help in improving site speed. That is because each varnish node has a different hit ratio. A lower hit ratio leads to more users getting the latency and performance penalties combined – due to a varnish MISS. Traffic pattern and latency have to be taken into account to decide on how many nodes to use in a cluster.

Difficult to warm the cache

Given the distributed nature of the cache, warming each cache independently takes more resources on the server side as well as some changes to the way a cache warmer works.

luroConnect : A modern cloud hosting platform

Schedule a call of a free evaluation and demo!

  • Is horizontal scaling manually or with autoscale right for you?
  • Evaluate if a varnish cluster will help your website performance
  • Show managed dev, staging and production environments
  • How we measure application performance every minute with alerts for a slowdown
  • Can your hosting costs be optimized?
  • How improved hosting can lead to better ROI!

On Magento Cloud? We have special offers if you switch your enterprise license to luroConnect managed AWS cloud.


Magento Cloud and Fastly

It is important to discuss the Magento 2 cloud decision to use Fastly as a frontend.

 

Magento 2 cloud pro version architecture is given below. (Reference : https://devdocs.magento.com/cloud/architecture/pro-architecture.html)

The architecture uses Fastly as a Full Page Cache. Varnish is not installed on the Instances in AWS.

Fastly is a CDN that uses varnish. With the Magento 2 Cloud integration, a custom plugin is used on Magento along with a custom vcl file that runs on Fastly.

Fastly has many “POP” locations. As per fastly documentation, there are 20 POP locations in North America, https://www.fastly.com/network-map

mostly in the USA. Each has its own varnish cache. When a page is not in a POP, it fetches content from the origin. A single page may have to be rendered 20 times for each POP location in North America.

Drawbacks of this architecture from a varnish cluster perspective

  • More POPs do not lead to better experience as a higher percentage of MISS on varnish results in a worse experience for more users.
  • As POPs increase the load on Magento infrastructure increases.
  • It is not possible to use a cache warmer to warm the Fastly cache.

What next?

An ideal situation would be a layered varnish configuration – each “satellite” varnish node serving a local user, caching a subset of the “main” varnish node, reducing the penalty of a varnish MISS.

Share your thoughts here or on social media.


Choosing Hosting platform for Magento : Digital Ocean

One in a while you may want to check on the technology and options available for Magento hosting – technologies change, hosting costs reduce, life becomes easier for you and the merchant. In this multi-part series we will explore cloud platforms and their suitability to hosting a production Magento website.

In this article we will explore how seriously you should consider Digital Ocean as a platform for hosting.

Digital Ocean (DO) offers VMs it calls “droplets”. With an excellent blog and a friendly attitude towards developers, DO is a serious hosting contender. Many developers naturally recommend using DO to merchants for hosting Magento. How real is it? Let me explore a few pros and cons.

DO Choice of vCPU and memory

DO offers “shared CPU” and “dedicated CPU”. For eCommerce hosting we always prefer dedicated CPU. Shared CPUs work differently on all cloud providers and quite often when you need it the most, you do not get enough. Each vCPU is a Intel hyper-thread. As of this writing (July 2020), we see use of Intel Xeon Gold 6140 @ 2.30 GHz with DDR4 memory at 2.6 GHz. Our simple memory speed test showed about 1.8GHz effective throughput to memory.

DO disk : Love / hate relationship!

I love the disk speed – you get good SSD performance with no throttling. Even for attached storage. Unlike other cloud platforms, you don’t have to juggle with figuring out how many IOPS you need, once you understand for that platform how IOPs translates to speed.

To test the disk I use a simple but effective method to test the disk speed. I create a file with /dev/zero using dd of 1GB. dd gives me the write speed. Here is the command I use :

dd if=/dev/zero of=$tempFile bs=1M count=1024 conv=sync oflag=direct
tempFile has the path to a temp file in the mounted disk I am testing

For both direct disk as well as mounted block device we see 200-350 Mbps disk speeds across all droplets our customers use. This is the best we have seen in cloud platforms. Physical hardware can give upto 450Mbps speeds.

So, why the hate?

We have seen disk crashes – thankfully on staging servers. So, when it comes to production servers, we always recommend customers to have a DR plan to minimize loss of data when this happens. Our suspicion is that the storage is not in a managed RAID, hence a disk crash is a droplet specific event.

Network

File transfer speed test

: A large file transfer using scp on the internal network is done in about 130 MB/sec – about a 1 Gb/sec speed.

NFS

NFS of block storage performs poorly. We use nfscache so most reads are served from cache. However, if there is a need for a large number of reads or writes, the performance can drop dramatically.

Examples of NFS hurting performance

Magento 1 : As images are created in the frontend, there is a check to see if an image file exists before an image url is included in a page. This invariably results in slowdown.

Magento 1 and 2 : large log files. Magento stores log files in a shared folder. Multiple lines per hit of logs will result in slowdown.

Experience :  We were in the process of taking a Magento 2 website live that had about 2500 errors written to a log file. The issue was related to M1 migration resulting in some attributes not defined in M2. App servers saw 30% CPU in I/O wait and the site came to a crawl. Php access logs showed under 10% CPU utilization.

Non availability of autoscale

DO does not come with an autoscale option (you could get autoscale in their Kubernetes which we are not considering here). This means you may have to keep capacity for a holiday sale for example.

Managing server costs

Server costs even when you shut them down

DO charges for servers that are shut down. If you do not want to be charged fully for a server, storing an image is an option.

Recommendation

We host customers on Digital Ocean if they do not have a need for scaling and are willing to have a DR plan. This generally increases the cost of the overall solution.
Also stores that do not need scaling such as :

  • PWA – Vue-storefront based stores which use nodejs
  • A high hit ratio varnish FPC magento store (depends on many factors, but essentially requirement for scaling multiple app servers is less likely)

 

Interview with Carmen Bremen, Mage-One

Carmen Bremen is an accomplished Magento developer and community member for many years. She is a certified developer and she has been awarded Magento Master She is one of the founding members of Mage-One – a paid long-term-support (LTS) for Magento 1. We chatted with her over email recently.

luroConnect : Tell us about yourself (how many years in Magento, feathers in your cap)
Carmen : My name is Carmen and I met Magento in 2010. At first I thought: I’ll never learn this, took the challenge and didn’t want to give up Magento afterwards. Now Adobe unfortunately announced the end of Magento 1 and maybe that’s why I became part of Mage One. I just don’t want to give up Magento 1 so fast 🙂
luroConnect : You must have researched and talked to merchants about M1 end-of-life. Can you summarize your finding?
Carmen : Many merchants would like to continue using their Magento 1 shop a little longer. Some have not yet decided which system they want to switch to. Others think that the investment in the shop has to pay off first and others are so used to Magento 1 that they just want to continue using it.
luroConnect : What type of patches does one expect from mage-one?
Carmen : We will offer security and compatibility patches. We will not develop new features or remove old Magento bugs.
luroConnect : Php 7.2 EOL is nearing – will you deliver patches for php version upgrades?
Carmen : Of course. The first patch we release will be the one for PHP 7.3.
luroConnect : What type of tests do you run before you release your patches?
Carmen : We use Cypress for our tests and have been working on numerous different tests for weeks.
luroConnect : How many years of mage-one support can we expect?
Carmen : We intend to offer our service for at least 5 years.
luroConnect : I know this is not legal advice I am asking, but for merchants who process payments by Paypal or Visa, a big question is what would happen after June 2020? Will they lose PCI compliance? (Refer resources if you can). 
Will mage-one satisfy the 6.2 PCI rule.
Carmen : From our perspective 100%. We offer a certificate in our customer account that can be presented to payment providers. Nevertheless, the final decision is up to the payment provider itself. However, we already have a statement from PayPal, for example, that you can continue using PayPal with Mage One.

luroConnect Support for Magento 1 past EOL

We have a 4-point plan to support you. Signup Now and we will contact you. No credit card required.

From EUR 29

Mage-One Patches for post EOL Magento 1 support. Will satisfy PCI vendor requirement.

mage-one logo

From EUR 99

Sansec eComscan examines a store for malware, vulnerabilities and unauthorized accounts. Written by well known security expert Willien De Groot, it scans files, databases and 3rd party components of Magento.

From USD 50*

Inbuilt into our Nginx, with M1 rules, protects from OWASP Top 10, with the ability of virtual patching.

From USD 50*

Staging environment to ensure patches are tested before taken live.

Signup now! And we will be in touch with you. No credit card required.

How Magento can get near 0 downtime deployment

Factor III of the 12 Factor App says “Store config in the environment”.

12 Factor App is what devops lives by – a set of 12 principles written by Adam Wiggins for predictable web app deployments.

Storing configuration in environment, separate from code has the advantages of reliable deployment along with reduced time to deploy. It allows separation of the build stage from the deploy stage, with some deploys being just a change in a softlink to the web root folder.

Historical preview : Magento 1

Magento 1 did not have much of a build process – js and css were not versioned, magnification was “online” first access based as was database upgrade information, configuration was stored in the database.

The most reliable way to go from a dev configuration to a live configuration would require a set of known steps that would work or changes directly to the database.

luroConnect developed its own build and deploy process. In our build step we

  • get source code from git
  • minify css and js files in the skin and js folders using a grunt based process
  • set appropriate file ownership and permissions

During the deploy phase, we

  • Copy app/etc/local.xml from a secure deployment configuration area (our environment)
  • modify the core config data to add a version string in the skin and js URLs
  • access the website once through the index.php to cause the update scripts to run

Deploy process is of course run with the site in maintenance – we prefer to do this at the nginx level. Mostly it is a small blip.

Historical preview – pre Magento 2.2

Early Magento 2 builds were similar – except there was some help from the bin/magento command. Our deploy process did not need to version the static access anymore. Plugin enable / disable was given via config.php. Our deployment environment contained env.php.

However, developers had to manually configure and experiment with some options.

Site bringup required devops to access the admin panel or update the database with custom sql – enabling varnish, setting up CDN with a static URL, etc.

Magento 2.2 and beyond

Magento adopted the direction of the 12 factor app and presented in Magento Live UK 2017 a new set of features that would help in ensuring an ability to split the application configuration and environment configuration. Application configuration was defined in app/etc/config.php which is advised to be in git and hosting environment and secure details are kept in env.php which should not be kept in git.

It is a slightly weak conformance – as commented by 12factor app “This is a huge improvement over using constants which are checked into the code repo, but still has weaknesses: it’s easy to mistakenly check in a config file to the repo; there is a tendency for config files to be scattered about in different places and different formats, making it hard to see and manage all the config in one place. Further, these formats tend to be language- or framework-specific.”

Magento has fixed this in 2 ways

  1. The language specific aspect is addressed to some extent in Magento by allowing to use bin/magento cli to edit env.php for sensitive data. The config:sensitive:set directly writes to env.php. These commands no not require the database, hence, can be set in a pre-deploy step.
  2. Use of scoped environment variable names. These would be set in Nginx configuration or an include file such as fastcgi_params.

However, there is no documented way to set database details – except to manually edit the env.php file.

The app:config:dump command

A great help in maintaining a known configuration of the application (which 12factor app suggests be committed to git). This ensures communication between developer to operations.

The app:config:dump command writes to config.php and env.php. While config.php is suggested to be committed to git, env.php should not be committed to git.

If a value is in config.php, the Magento admin panel does not allow the parameter to be edited. This locking helps with giving stability to the application configuration. It ensures the application is developed and tested with a known configuration.

The figure alongside shows the suggested flow.

Suggested flow for using app:config:dump

Why is Magento deployment yet keeping site in maintenance?

However, we find that even after 2 1/2 years of announcement, the acceptance and understanding of these features is weak. Leaving websites in maintenance mode as code is deployed.

Developers are failing to maintain a discipline to own the configuration or devops to understand the application’s build and deploy process.

There are some practical problems as well. An eCommerce manager would like to have control on the live website on say, when backorders would be allowed storewide. Since this is locked in config.php, this request has to go through developers or devops.

luroConnect near 0 downtime deploy

luroConnect’s Magento 2 build is in a pipeline – such as a bitbucket pipeline. A commit triggers the pipeline that does the following

  • composer install (with the compose cache to speed this process)
  • bin/magento setup:di:compile
  • bin/magento setup:static-content:deploy

The contents are then tarred and sent to the staging and production servers.

Upon deploy the contents are untared, deployment related files like env.php are copied, media and var are softlinked. The web root softlink is changed to point to this new release. The process is slightly more complicated when multiple autoscale instances are running, as running instances are replaced with ones with new code.

If required the bin/magento setup:upgrade command is run and only then is it required to keep the site in maintenance.

Would you like to switch to a modern hosting platform?

Schedule a call of a free evaluation!

With features like ~0 downtime code deploy and autoscale to reduce your hosting costs, luroConnect offers you unparalleled hosting environment for Magento.

Schedule a call and we will show you how we can

  • Improve your hosting, possibly with autoscale
  • Have a managed dev, staging and production environment
  • Server performance measured every minute with alerts for a slowdown
  • A multi point health check every day
  • Optimized hosting costs