Serving Compressed Content with Rails and AWS Elastic Beanstalk

Slow web pages frustrate users. Imagine people opening your web application, and after 3-4 seconds, the page is still loading. Their attention is lost – they either leave or switch to something else. Speed is a significant factor when it comes to web page loading time. A simple website that loads very fast is the best user experience. Speed is always in style.

If you wonder whether your website is fast or slow, you can always ask Google to measure that for you. PageSpeed Insights analyzes web pages' performance. That handy tool checks your website against a list of bad practices and creates an overall score and a detailed report of improvements to be made.

One of the main aspects PageSpeed Insights looks for is compression. Compression shrinks a text file by a factor. Since most websites receive more hits from mobile devices than desktop browsers, shrinking your content becomes a major concern.

Why AWS Elastic Beanstalk?

I do not have to manage infrastructure. I can focus on application development instead – a promise made by almost every Platform-as-a-Service solution. AWS Elastic Beanstalk (EB) is an easy-to-use service for deploying and scaling web applications. You don't need an Ops team to manage your infrastructure.

A web developer with nearly zero experience deploying applications can go live smoothly. The UX is similar to other popular PaaS like Heroku or OpenShift, but the running costs are lower. There is no additional charge for Elastic Beanstalk – you pay only for the AWS resources needed to store and run your web application.

When serving compressed content, you have two distinct problems to solve. How to serve your static content and how to serve your dynamic content.

Serving static content

Here you have a number of options. Just to name a few:

  • Use of Content Delivery Network (CDN).
  • Use of front-end proxy to handle the compression.
  • Use of web server to serve the pre-compressed assets.

The best approach for large-scale apps would be using a CDN for your static assets. However, for smaller apps, the approach I usually go for is much simpler: do nothing. Yes, job done out of the box. Thank you, Amazon! Thank you, Rails team! But enough with the kudos and let's look under the hood.

As static assets may change only between deployments you have to setup everything during each deployment. There are two tasks on the to-do list here: (1) compress the assets and (2) serve the compressed version.

Compress the assets

Luckily the Rails asset pipeline handles this one for us by default. By default, gzipped version of compiled assets will be generated, along with the non-gzipped version of assets. If you ssh to any of your EC2 instances you can verify that.

$ ls public/assets/
application-225482854f70e3b5a0286fc874a35769ad5820f6176bb88f6440c14c472d3388.js
application-225482854f70e3b5a0286fc874a35769ad5820f6176bb88f6440c14c472d3388.js.gz
application-775bd24ca6f1e0684e3f2f04f9d0793df0860346c95558acabca4f67e48e70a0.css
application-775bd24ca6f1e0684e3f2f04f9d0793df0860346c95558acabca4f67e48e70a0.css.gz

As I already said – nothing to do at this step.

Serve the compressed version of the assets

AWS EB handles this one for us by default. When you create your application environment, a Nginx configuration file is created for you: /etc/nginx/conf.d/webapp.conf (could be different in different versions of EB) with the following configuration inside:

  location /assets {
    alias /var/app/current/public/assets;
    gzip_static on;
    gzip on;
    expires max;
    add_header Cache-Control public;
  }

  location /public {
    alias /var/app/current/public;
    gzip_static on;
    gzip on;
    expires max;
    add_header Cache-Control public;
  }

Notice here the gzip_static setting which tells Nginx reverse proxy to serve compressed version of the assets. So nothing to do in here as well.

We managed to serve compressed static content with zero effort.

Serving dynamic content

Let's take a look at compressing dynamic content. Here again we have several options and just to name a couple:

Using any of these approaches is better off to serving uncompressed content. The Rack::Deflater middleware compresses responses at runtime using deflate or gzip. It reduces the size of your responses (HTML or JSON) by a factor. Still I don't want my application server to be worrying about compression. I would prefer to delegate that job to a reverse proxy. On AWS Elastic Beanstalk it is quite easy to configure the Nginx reverse proxy to compress your application dynamic content. You can achieve that using ebextensions to create additional Nginx configuration files which will be included in the main configuration file like this:

include /usr/share/nginx/modules/*.conf;

That line from the main config file causes all conf files in the /etc/nginx/conf.d/ directory to be included in the main Nginx configuration. You add a new configuration file by creating a file under the .ebextensions folder in your application. For example, you create a file .ebextensions/01_gzip.conf like this:

files:
  "/etc/nginx/conf.d/gzip.conf":
      mode: "644"
      owner: "root"
      group: "root"
      content: |
        gzip on;
        gzip_types text/plain text/html application/json;

container_commands:
  01_reload_nginx:
    command: service nginx reload

That file will create a 01_gzip.conf file inside the Nginx conf.d folder that will be included by the main configuration file. The container command will make Nginx reload its configuration. As order matters, you may want to prefix your configuration files with a number to be certain of their order of inclusion. As my gzip.conf file is my first and only one I have named it 01_gzip.conf.

Back to serving gzipped content. You have two options that you have to configure on Nginx to compress your application dynamic content - gzip on; and gzip_types. Since we already serve compressed assets using gzip_static on here you may want to list only your dynamic content types. There are a number of other gzip options you may want to play around with here but those two are enough for your reverse proxy to start serving gzipped content. That is how you use EB extensions to configure your environment from your application source code.

With minimal efforts we were able to shrink our application content by a factor. Users love our web application even more than before. Bots love it. We just made the whole world of humans and machines a better place.