Running Grav CMS in a High Performing Set Up

by Moises Jafet — on  ,  ,  ,  ,  ,  , 
Tiempo de Lectura aprox.: 7 Minutos, 54 Segundos

cover-image

Grav is a Content Management System, a CMS, written in PHP, quite handy because its simplicity, ease of use, a comprehensive ecosystem of extensions and great community.

"Grav is a Fast, Simple, and Flexible file-based Web-platform. There is Zero installation required. Just extract the ZIP archive, and you are already up and running."

Off-the-bat it is perfect though if you don't have to use it as a dashboard for registered users in order to provide there some sort of application or services; just as a content consuming platform (80% reads / 5% writes mainly for posting up the content / 15% embedded services coming from third parties).

So, let's say you are operating your instance in a mission-critical task such as running your main company's website, a user guide knowledge base site or a high traffic blog.

For a high performing set up, in my opinion, you will need to architecture a 5 pronged system in order to guarantee scalability by virtue of following SOLID ideas, in this case applied to infrastructure.

  1. A firewall
  2. A reverse proxy for SSL termination
  3. A caching reverse proxy
  4. A Redis / Memcached server
  5. The backend running the Grav instance

Things to ponder

I am assuming you are instrumenting everything

You have probes listening to all your key permormance points: webserver metrics, server processor loads, free inodes, etc.

Grav is a flat-file system

Hence has no dependency on databases; a fantastic feature but a bad thing for SysOps.

Without a database in the picture the data is persisted within the CMS file structure. A battle tested front-end to back-end ratio is not possible here.

In a database as the datastore architecture you usually use three front-end servers running the CMS behind a load balancer. Those servers are pulling/pushing data from a single database server. If you get more traffic or load you just add in more front-end boxes and maybe scale up your database server if performance crosses some threshold you may set.

Horizontal scaling is trickier

Just becase the reason above.

If you are using several boxes behind a load balancer you will have to keep in sync all the files across the fleet. Thats fine for a read-only site, but what if you have editorial users in need to manage the content?

Or worse, you are running a user engagement workflow that requires capturing the user feedback in the form of comments (in a blog) or questions (in a knowledge base).

With Grav you will have to outsource those services to a SaaS vendor, or you can get fancy by building yourself one on top of Redis that you keep in a separate box available to all front ends (which takes you back to the front-end boxes / database box paradigm). But, you don't want to do that, right?!, thats why you chose Grav for over Wordpress/Joomla/Drupal/etc.

Abstracting the content directory

Grav's content directory is located at your/instance/path/user/pages/.

Flysystem

Writing code to use S3, Azure Blobs any other file repository using the Flysystem library to simulate your content directory for each of your instances.

I haven't written that code yet; if I ever do I will push it to Github.

A local approach

Symlinking the content directory to some mounted device shared from a box in the network.

I haven't tested this idea thoroughly yet because my guts keep saying it's a bad one. It consists of setting up a common file storage somehow (maybe a separate box) and symlinking that resource to your front-end boxes.

I can see here that a lot of I/O issues will start popping up everywhere just because the limitations with the speed of light and the resulting file locking.

The Set Up

Given all the stuff from above, we are going to keep it simple here using a single instance (one box) that we will try to scale up if performance metrics degrade as a result of increased traffic and/or load. We are also providing enough padding in order to handle graciously any ocassional transient traffic surge.

  1. We need a Layer 7 firewall. You can set up yourself one or provision it from a SaaS vendor. Consider the later option better. Vendors such as Cloudflare and Stackpath are really good at their job, a job you don't want to learn by doing in a production environment. This layer is listening on port 80.
  2. We need a reverse proxy for SSL termination. You can use your SaaS vendor for HTTS termination, but that may not work for your use case if for example you need Extended Validation for your SSL certificates or you are just like me in the sense of your level of InfoSec paranoia. This layer is listening on port 80 if your firewall is outsourced.
  3. We need to shield Grav from getting hitten by direct traffic hence we need a Caching reverse proxy. Varnish, Squid or Nginx can do the job.
  4. We need a Redis or Memcached server. The idea is to let Grav to use that server to offload there session bytes and commonly used data. I prefer Redis over Memcached for many reasons out of the scope of this post. You can provision this server in the same box with Grav or just on the network.
  5. And finally, we have the Grav's backend.

Everything put together should look like this:

graph LR A[1. Firewall:80] -- Encrypted --> B(2. Reverse Proxy) B -- Plain text --> C(3. Caching server:6180) C --> D(5. Grav Backend:8080) D --> E((4. Redis:6379)) D --> F((APIs))

As you can see, there are a couple of places that you can scale up before scaling up your Grav backend if traffic takes off for good.

Grav Finetuning

As a result of running Grav on port 8080 in this example, internal routes using the PHP's $_SERVER superglobal will be computed as http://www.example.com:8080 which is not good: because you are operating your site on HTTPS, and because -hopefully- you are not exposing the 8080 port to the open Internet.

<meta name="generator" content="GravCMS" />
--
  | <meta name="description" content="" />
  | <meta name="keywords" content="" />
  | <meta name="og:sitename" property="og:sitename" content="" />
  | <meta name="og:title" property="og:title" content="" />
  | <meta name="og:type" property="og:type" content="article" />
  | <meta name="og:url" property="og:url" content="http://www.example.com:8080/myurl" />
  | <meta name="og:image" property="og:image" content="http://www.example.com:8080/path/to/image.jpg" />

Because that thing will break critical functionality as you can see, I filed the issue at the official repo on Github. I got an answer, within just a few minutes, from one of core developers: https://github.com/getgrav/grav/issues/1907.

"Enabling reverse proxy setup option in system.yaml, cleaning up the cache in Grav and in the caching proxy solve the port issue."

Such a fast response from the community speaks very high of the product itself as you can count on timely help if you ever get stuck.

That's awesome, but you still have to deal with the URIs in HTTP. This is an easy task for your reverse proxy doing the firewall or the SSL termination job.

You have to scroll down the whole configuration file for other key settings to activate and configure such as the Redis/Memcached server, template caching, session handler, among others.

Testing

There are two ways to test out this array:

curl -I -H "www.example.com" localhost

The -I flag will limit the response to the headers only.

You should see something like:

foo@baz:~$ curl -I -H "www.example.com" localhost
HTTP/1.1 200 OK
Date: Sun, 11 Mar 2018 03:24:00 GMT
Content-Type: text/html
Content-Length: 27
Last-Modified: Sun, 04 Mar 2018 10:34:58 GMT
ETag: "5a9bcbd2-1b"
Age: 5
X-Cache: HIT
X-Cache-Hits: 1
Accept-Ranges: bytes
Connection: keep-alive

The cache hits counter should keep incrementing if you ran the same command sucessively.

Try hitting the Grav backend to analyze E-tags if you are using them to control further the caching layers.

curl -I -H "www.example.com" localhost:8080

The second way is to actually stress test the set up. For doing it properly you will need an externalized service such as Loader.io.

Even with a single small VM I can bet my cup of coffee that you will achive 10,000 concurrent requests per second without breaking a drop of sweat.

But, don't claim you won the bet without first disabling all DDoS protection mechanisms engaged across all the layers in the set up (I.e., Nginx caps by default the number of requests coming from a single IP address up to a number that you can change in the settings). If you don't do that Loader.io or any other stress tester will error out around 500/minute.

Advanced Stuff

Let's say you are very serious about runing your Online operations professionally. That will mean that you have a scientific approach to what you do and all your decisions are backed up by analytics data. So, you need to iterate systematically using A/B testing and in some cases if you are dealing with registered users somehow, using canary testing as well.

Credits: Wikipedia

A/B Testing

Grav uses Twig as its templating engine. It would be trivial to write some code to pull that off.

Canary Users

I haven't seen yet a Grav plugin for Feature Toggling on the official site, but it looks trivial to me to write one yourself.

Wrapping up

With the proper set up for caching and in-RAM data structures, you can beef up your themes and plugins in order to create a very engaging experience for your users without any concerns on affecting the overall performance.

As you hopefully verified yourself with your tests and instrumentation, Grav is a very powerful tool for running serious business on it.

Blog Comments powered by Disqus.

Moisés Jafet Cornelio-Vargas

About Moisés

Profile picture

Physicists, award-winning technologist, parallel entrepreneur, consultant and proud father born in the Dominican Republic.
Interested in HPC, Deep Learning, Semantic Web, Internet Global High Scalability Apps, InfoSec, eLearning, General Aviation, Formula 1, Classical Music, Jazz, Sailing and Chess.
Founder of pluio.com and hospedio.com.
Author of the Sci-fi upcoming novel Breedpeace and co-author in dozens of publications.
Co-founder of MunicipiosAlDia.com, Jalalio Media Consultants and a number of other start-ups.
Former professor and Key-note speaker in conferences and congresses all across the Americas and Europe.
Proud member of the Microchip No.1 flying towards Interestellar space on board NASA's Stardust Mission, as well as member of Fundación Municipios al Día, Fundación Loyola, Fundación Ciencias de la Documentación and a number of other non-for profit, professional organizations, Open Source projects and Chess communities around the world.
All opinions here are his own's and in no way associated with his business interests or collaborations with third-parties.