Skip to content

Instantly share code, notes, and snippets.

@alexiamcdonald
Last active October 2, 2016 09:55
Show Gist options
  • Save alexiamcdonald/c6146ac2e8c5127f9824fa7cc2ba07bc to your computer and use it in GitHub Desktop.
Save alexiamcdonald/c6146ac2e8c5127f9824fa7cc2ba07bc to your computer and use it in GitHub Desktop.
title categories
Parallel rendering in Rails
ruby
code

I will continue to expand the features in the pagelet_rails gem. Since it offers a new architectural way of thinking to Rails I've found that there are so many new things we can do. In this post, I will focus on the parallel rendering. Yes, that's right, parallel rendering in Rails. Although, it may sound advanced the concepts behind it are extremely simple. This post however is only relevant to web page rendering.

Background

When we want to run things in parallel with Ruby we only have two options - threads and processes.

Threads don't give us much in MRI because of the Global Interpreter Lock (GIL). Although, threads do have the advantage in applications with a lot of IO(??). However, web servers like puma and passenger take advantage of that and can be served multiple requests at the same time.

Processes don't have the GIL problem, but they do suffer from high memory usage as each process has copy of the application loaded into memory.(not sure if this sounds correct??) It's common to have multiple instances of the application running and to distribute incoming requests between all of them. The advantage being that processes can easily scale horizontally to multiple machines.

In this post, I will focus on processes and leave the threads discussion for my future posts.

Ajax

The first and the easiest way to get parallel rendering is to use Ajax. Instead of fetching data and rendering all of it in the one request, we can render an empty page without any data. Then with each individual portion of data we can render it within it's own ajax request.

<body>
  <%= pagelet :pagelets_account_info, remote: :ajax %>
</body>

Whilst this is the easiest way, it's not the most efficient for multiple reasons.

  1. Because the request is initiated by the browser thus creating a large delay between the initial request and the ajax request
  2. Network overhead is multiplied for each request. The slower the visitor's connection the slower rendering can become.
  3. Browsers are limited to around 3 concurrent requests to the same domain. If you many requests then the browser will queued them up. There are many ways to work around it but that can make an ajax solution more complicated.

Despite all of the negatives of using Ajax, I believe Ajax is still a best option for non critical data such as advertisements or suggestions.

Server Side Includes

Most of those negatives are related to network overhead and could be eliminated with Server Side Includes (SSI). The way it works - instead of the browser initiating requests and having an extra delay for the connection, the request is sent by the web server (like nginx or apache). Web servers are usually located close to your web application therefore making a fast connection between them (between what??).

SSI are great for caching, because you can cache the whole page and the web server will include dynamic data relevant to the visitor. For example:

<body>
  <!--#include virtual="/account_info.html" -->
</body>

This will place the account_info response inside the body tag. There is however a small setup required to get SSI working.

  1. You will need to have the serve requests go through a web server like nginx
  2. Server Side Includes (SSI) features (Which features???) have to be enabled in the web server

Once you've got SSI working you can start using pagelet_rails gem with SSI rendering mode.

<body>
  <%= pagelet :pagelets_account_info, remote: :ssi %>
</body>

Benchmark

I've run some basic benchmarks to get an idea of the performance improvements. While it's impossible to predict how big the performance boost you be for a particular page, the goal is to give you an idea of what to expect.

All benchmarks were done with the Apache Bench tool. The aggregated data consist of 100 benchmarks, each with 50 requests. There are 3 variables which can differ in the each of the benchmarks:(this doesn't sound right still???)

1. Number of pagelets

Each pagelet represents the data which could be fetched and rendered independently. Values are from 1 to 10.

2. Delay for each pagelet

This simulates the time spent receiving the data. (from where?) It's assumed that we are not bound by the CPU. Values are 0, 10, 20, 50, 100 and 200ms.

3. Rendering mode

The different rendering modes

  • view partial
  • pagelets inline
  • pagelets SSI (Server Side Include)

Benchmark Results

Labels on the horizontal axis for example 7 x 50ms, mean 7 pagelets with a 50ms delay each. Sorted by size.

![](image (1).png)

It's very unlikely that pages will have more than 5 pagelets, so let's focus on, 5 pagelets.

![](image (2).png)

A few observations:

    1. view partial is always faster than pagelets inline. The higher number of pagelets the bigger the difference between them.
    1. pagelets SSI is the slowest for 1 pagelet
    1. By doubling the delay, the time for the view partial and the pagelets inline are multiplied by the number of pagelets. However, pagelets SSI result time grows linearly.(doesn't sound right?)

Let's sort them by delay:

![](image (3).png)

    1. Interestingly those with the pagelets SSI don't show a difference with how many pagelets are rendered with the same delay. This is because it's processed in parallel and the slowest time for one of them always wins.
    1. With 0ms delay both pagelets are much slower, as there is the extra overhead in rendering.

Let's eliminate size 1 and delay 0ms as we have concluded there is no benefit. Also, let's sort by view partial response time and convert the lines for better visibility.

![](image (4).png)

    1. With the increasing load the rate of growth for the pagelets SSI is significantly smaller than others

Conclusion

While the benchmark does not show the benefits you would get in a real application, it clearly shows the scaling factor of using Server Side Includes (SSI). The more pagelets you have and the slower the pagelets are, the higher overall performance improvement. (both or seperate?)

Because of extra overhead in the pagelets, theoretically your overall throughput(output?) (requests per second) will slightly drop. However, it will result in significant improvements in response time for pages with 2 or more pagelets. In a real application there could also be additional overhead to verify a logged in user for each pagelet.

Finally, the rendering in parallel with threads is also another possibility, but I haven't managed to get that working as yet. The good thing is once Ruby 3 gets the "Guilds" feature, it will be possible to switch rendering with Server Side Includes (SSI) with Guilds without any changes to the application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment