We are the Dev Teams of
  • brands
  • ebay_main
  • ebay
  • mobile
<
>
BLOG

How to Tweak Your Website Performance

by Mateusz Szczap
in Tutorials

Research shows that users tend to leave sites that perform slowly. There is also a direct correlation between site performance and revenue, a drop of even a few percent in site performance can have serious financial implications. That is one of the many reasons why we take site performance seriously.

Performance has to be consistently monitored. Even if a page is fast, performance tends to drop over time as features are added. Therefore it's important to keep an eye on which features are used most frequently, and remove the ones that are not to reduce complexity, simplify and improve performance.

Slow site performance is often due to advertisement, but not always: it is often a combination of a few bad practises, technical debts or simply omissions, all of which add up to a slow user experience.

This post describes an idea I had to improve site performance in a project internally referred to as Showroom, which consists of three sites, serving PolishCzech and Romanian language versions.

Innovation Days
This blog post highlights performance improvements I plan to introduce as part of the "Innovation Days" initiative we have here at mobile.de, in which just about everyone can become a short-term "product owner" and pick a feature, bug, or improvement to work on. We're freed from ordinary work assignments for two days a month to improve what we consider the most important issue. As part of this initiative, I decided to improve the site performance of the Showroom project.

1. Early Head Flush
As outlined by Yahoo, in their famous site performance recommendations, it is a good idea to flush HTML head early, so that CSS can be parsed and loaded by the browser fast, so the  browser can start loading images in parallel, before the main content on the page is returned. After we upgraded a home-grown Google Soy library to a generic one, we removed an early head flush logic. Naturally, I believe we fixed more problems and made things more generic by introducing a new library, but this one thing stopped working like it should.

If you have ever worked with the Google Soy Templating library, you probably know that you cannot call any code from Soy templates to the Java layer, so it's hard to perform an early head flush by traditional means.

Just about any common framework supports flushing to the buffer from within a template file. In JSPs there is:

<% out.flush(); %>

PHP has a similar method:

<?php flush(); ?>

After analyzing the problem, I found two ways of introducing an early head flush support, one by writing a plug-in to Google's Soy rendering library, another using the concept of delayed execution callbacks from the spring-soy-view library. If we can delay the execution of model creation to the moment a rendering is taking place, we can incorporate an early head flush (since we have control of this).

Traditional spring mvc call:

protected ModelAndView createBaseModelAndView(final Model model, final MainPodModel mainPodModel, final VehicleCategory vehicleCategory, HttpServletRequest request) throws IOException {
        final BasePageModel basePageModel = new BasePageModel.Builder()
                .contentModelPod(mainPodModel)
                .headPodModel(headPodModelAssembler.assemble(getTemplatePage()))
                .headerPodModel(headerPodModelAssembler.assemble(vehicleCategory, getTemplatePage(), mainPodModel, request))
                .footerPodModel(footerPodModelAssembler.assemble(getTemplatePage(), request))
                .build();
        model.addAttribute("model", basePageModel);
        return new ModelAndView(getTemplatePage().getFullTemplateName(), model.asMap());
}

After fixes:

protected ModelAndView createBaseModelAndView(final Model model, final Callable<Object> mainPodModel, final VehicleCategory vehicleCategory, final HttpServletRequest request) throws IOException {
        final BasePageModel.Builder builder = new BasePageModel.Builder();
        builder.headPodModel(new Callable() {
            @Override
            public Object call() throws Exception {
                return headPodModelAssembler.assemble(getTemplatePage());
            }
        });
        builder.headerPodModel(new Callable<HeaderPodModel>() {
            @Override
            public HeaderPodModel call() throws Exception {
                return headerPodModelAssembler.assemble(getTemplatePage(), request);
            }
        });
        builder.contentModelPod(mainPodModel);
        builder.footerPodModel(new Callable<FooterPodModel>() {
            @Override
            public FooterPodModel call() throws Exception {
                return footerPodModelAssembler.assemble(getTemplatePage(), request);
            }
        });
        model.addAttribute("model", builder.build());
        return new ModelAndView(getTemplatePage().getFullTemplateName(), model.asMap());
    }

Each call is wrapped in a callable, which means this method executes very fast and assembly of those elements is delayed to the time of rendering.

At the moment of rendering, the HTML head is flushed and further elements are rendered and assembled. For that, it was necessary to override the spring-soy-view renderer class and provide our own render method:

@Component("showroomSoyRenderer")
public class ShowroomTemplateRenderer extends DefaultTemplateRenderer {
    @Override
    public void render(final RenderRequest renderRequest) throws Exception {
        if (!renderRequest.getCompiledTemplates().isPresent()) {
            return;
        }
        final BasePageModel basePageModel = (BasePageModel) renderRequest.getModel();
        renderHead(basePageModel, renderRequest);
        renderHeader(basePageModel, renderRequest);
        renderMainPod(basePageModel, renderRequest);
        renderFooterPod(basePageModel, renderRequest);
    }
  private void renderHead(final BasePageModel basePageModel, final RenderRequest renderRequest) throws Exception {
        final Optional<SoyTofu.Renderer> renderer = setup(renderRequest, basePageModel.getHeadPodModel());
        post(renderRequest, renderer); //post method will perform output stream (out.flush()).
    }
...
}

2. Caching AJAX Calls
Yahoo also recommends caching AJAX endpoints, but we missed that during the first phase of development. As part of recent contributions, we will be adding AJAX caching for:

  • Presearch (result counter) : public, 10 seconds cache
  • Car models on homepage and DSP (Detail Search Page) : 24 hours public cache
  • Truck makes on DSP (Detail Search Page) : 24 hours public cache
  • Translation description endpoint : public cache of 24 hours, this kicks in only if the user comes back to an ad because we cache this normally in JavaScript
  • Check distance feature on SRP (Search Result Page) : public cache for 24 hours
  • Navigation through ads (previous, next) on VIP (View Item Page): public 10 minutes cache
  • Similar Ads on VIP (View Item Page): public 10 minutes cache
  • a few location search endpoints on DSP (Detail Search Page): all public 24 hours cache

3. Delaying Page Element Loading via JavaScript onLoad Event

One rarely used, but highly effective, method of improving performance is delayed loading of elements on a page that are expensive to compute.

I introduced a delayed loading via JavaScript of two elements that are not critical to the user experience on the page and are both fairly expensive to calculate since they need to perform extra remote calls to fetch data:

  • Delayed loading of ad navigation (previous and next buttons) on VIP (View Item Page)
  • Delayed loading of Similar Ads on VIP (View Item Page)


Delayed Ad Navigation Loading (source: http://mobile.de/pl):

showroom ad navigation

Delayed similar ads loading (source: http://mobile.de/pl):

showroom similar ads

Both ad navigation and similar ads is delayed until the page loads and then a JavaScript performs an AJAX call to return data. I rendering HTML elements via JavaScript using part of the spring-soy-view library. This functionality allows us to render HTML retrieved from an AJAX call by simply invoking a JavaScript function that renders a template:

 

function addAdNavigationPod() {
        var vipUrlParts = parseVipUrl();
        var url = ...
        $.getJSON(encodeURI(url), function(data) {
            $("#backForthNavId").append(ajax.results_navigation({model: data}));
        });
    }
    function addSimilarAdsPod() {
        var vipUrlParts = parseVipUrl();
        var url = ...
        $.getJSON(encodeURI(url), function(data) {
            $("#like-this").append(ajax.like_this({model: data}));
        });
    }

and finally delay execution via setTimeout function:

$(window).load(function () {
        window.setTimeout(function () {
            addAdNavigationPod();
        }, 0);
        window.setTimeout(function () {
            addSimilarAdsPod();
        }, 0);
    });

4. Wro4j: Further Improve Performance by Splitting CSS Into Two WRO Groups

In the Showroom project, we use an open source library called `wro4j` in runtime mode for development and static mode (Maven plugin) for production to minify CSS and JavaScript and run various other performance-related processing tasks.

What I have observed is that our compiled and minified CSS for VIP (View Item Page) takes about 110 kb (uncompressed). I decided to introduce two groups for CSS files, similar to the JavaScript groups we introduced before. There is a wro4j group: common-all-pages, which would consist of compiled CSS (from Less) and page-specific CSS, which should be much smaller and needless to say would contain only CSS elements needed for that page that are not generic enough for inclusion in the main CSS.

This means that once a common-all-pages compiled CSS file loads for one of the pages, it will be cached and reused for other pages. This change will have little effect on desktop users, but mobile users will be able to see the difference in cost and bandwidth savings on their mobile data plans, and improved page load time as high speeds are often not available to mobile users.

performance, wro4j, site speed, ajax, google closure templates, soy

?>