question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Slow compilation of SCSS files in Rails apps

See original GitHub issue

Hello šŸ‘‹ from GOV.UK Publishing. Weā€™ve been having some problems with speed of CSS compilation of GOV.UK apps for a while and after doing some detective work weā€™ve traced that a large volume of the time is due to importing files from govuk-frontend.

Backstory

GOV.UK apps use the Rails asset pipline to transpile SCSS to CSS. They mostly continue to use the deprecated Ruby SASS library, but are likely to start switching to sassc in the forseeable future. Nearly all GOV.UK apps use the govuk_publishing_components gem which pulls in the govuk-frontend node module and imports all components.

Gradually over the last few months or years weā€™ve been seeing the time that is needed to compile assets for GOV.UK has increased and once we switched to a docker based development environment we started to investigate as it seemed abnormally poor. Developers are finding that frequently an app wonā€™t seem to work the first time you try run it locally as the CSS could be taking more than a minute to compile. We did start to wonder why other Rails devs werenā€™t going crazy about the slowness of CSS precompilation as it felt increasingly broken.

Looking into this further we found that most of the time was spent around the process of using @import to include files. Looking at a particular app, Finder Frontend, we could see that 7054 files were being imported, despite less than 400 distinct files being used, and every one of these files requires processing which seemed to be where the majority of the time was spent. Following this further we could see that from the point where we import all govuk-frontend components was where we were seeing the big increase in imported files.

Some basic benchmarks from my MBP 2015 on time to generate Finder Frontendā€™s application.css file:

Environment SASS Cache Time (s)
govuk-docker Ruby SASS Cold 59.983
govuk-docker Ruby SASS Warm 0.228
govuk-docker sassc Cold 32.693
govuk-docker sassc Warm 17.478
Local machine Ruby SASS Cold 31.926
Local machine Ruby SASS Warm 0.063
Local machine sassc Cold 11.805
Local machine sassc Warm 2.413

Cause and effect

The problem of slow CSS compilation for GOV.UK seems to stem from the @import approach used in govuk-frontend and how this can affect Rails apps.

If we follow govuk/all.scss we import ā€œsettings/allā€ (>= 13 files), ā€œtools/allā€ (>= 9 files), ā€œhelpers/allā€ (>= 12 files). Then for each component the same files are then imported. As sass executes each @import whether or not itā€™s been included before this can exponentially increase the amount of work done. Some basic maths would have the 30 components adding up to at least 384 files imported but I think the number ends up being quite significantly greater.

Weā€™re theorising that the sheer quantity of imported files affects the Rails asset pipeline particularly badly as everyone one of these files is checked to see if it uses any Rails specific functions (asset-url, asset-path). It could well be paired with IO operation pain too, as it probably searches a whole bunch of directories for every single file.

I put together some isolated test cases to show the effects in a Rails app free from the rest of the GOV.UK infrastructure. As a basis for comparison I used a version of govuk-frontend I put together that was optimised for the all.scss file where all other imports were removed.

Some benchmarks were:

SASS Cache Time (s)
sassc Cold 7.494180
sassc Warm 1.667473
Ruby SASS Cold 15.556202
Ruby SASS Warm 0.007234

Where we can see this performs relatively consistently with Finder Frontend app, just clearly has a lot less to do being isolated.

And compared to the version with duplicate @import calls removed:

SASS Cache Time (s) Decrease
sassc Cold 0.552520 92.6 %
sassc Warm 0.007781 99.5 %
Ruby SASS Cold 2.913921 81.3 %
Ruby SASS Warm 0.005758 20.4 %

Here we can see quite how dramatic the effects of @import can be.

I did ponder whether things would be quicker using webpacker with Rails. There I saw it take ~5s to compile with @imports duplicated and ~2.5s with them removed. So it seemed difference is still significant here but as the scale is much reduced the impact is less. Iā€™ll also add that I really didnā€™t put much time into setting up webpacker so these numbers could be flawed.

Finally, I tried running the sassc command line utility to see if this is slow I found that it took 0.27s to compile with @imports duplicated against 0.21s with them removed. Which isnā€™t a very significant difference.

Impact on GOV.UK

Some of the effects this has had on GOV.UK have been:

  • Apps never seem to work on first request for local dev, as Rails hangs waiting for assets to preload. Unaccustomed developers assume the app is broken, abort it and ask for help
  • First test was frequently failing on apps due to timeout. We switched to running a manual pre-compile before tests to resolve it.
  • Switching to sassc is concerning because of the heavy performance impact on even a warm cache.

In general we seem to have ended up with a perfect storm on GOV.UK by having some apps needing to precompile multiple files with the same contents, importing all of govuk-frontend and then switching to the slower docker environment for development.

Weā€™re currently pondering about switching away from the approach of including all govuk-frontend components which would likely offer some performance improvement. Though weā€™d still be paying a reasonable performance penalty for duplicate @import calls.

Although we could see performance improve a reasonable amount by switching to use webpacker with Rails this seems like a long way off for GOV.UK given the quantity of apps and the amount of existing asset-pipeline based code we have. Weā€™re still a reasonable distance from being able to migrate one app to webpacker

Things weā€™re wondering about

  • Whether something is broken in sassc-rails for the warm cache performance to perform so badly
  • Quite what is going on with each import that impacts time so badly
  • @use seems the ideal solution for resolving this problem but itā€™s not available on libsass.
  • Whether other teams on different stacks are experiencing same performance issues
  • Whether the govuk-frontend style of @import usage is common or not - I notice itā€™s not an approach tachyons or bootstrap use - as to whether wider world is affected.

Itā€™d be great if we can try work out if there are things that can be done to reduce this problem. Although aspects of this seem quite specific Rails it does seem to be a problem that is likely to affect other environments (as multiple execution is a property of @import) and is likely to only increase in severity as more components are added.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:27 (24 by maintainers)

github_iconTop GitHub Comments

3reactions
36degreescommented, Feb 20, 2020

I have a WIP branch that tries to provide a non breaking-change way of reducing the number of imports when importing all:

https://github.com/alphagov/govuk-frontend/tree/sass-perf

2reactions
36degreescommented, Feb 27, 2020

@kevindew weā€™ve published a pre-release of the changes in #1752 ā€“ would you be able to benchmark it and see how it compares?

npm install --save alphagov/govuk-frontend#6735d43a
Read more comments on GitHub >

github_iconTop Results From Across the Web

Rails/Compass/Sass Compile Super Slow - css - Stack Overflow
The compass watch command is used when you are using compass on a bare bones project or some framework without an assets pipeline....
Read more >
Optimise SCSS Sprockets Performance in Rails - Made Tech
Sprockets will refresh every new or updated asset on every page load. This alone could be the cause of your slow down, depending...
Read more >
Solve slow webpack compilation when using Tailwind @apply
Workaround. The workaround I used is to separate it to two CSS (SCSS) files, one containing the Tailwind CSS stuff, and another one...
Read more >
Lightning-Fast Sass Reloading in Rails | mattbrictson.com
It's true, unfortunately: out of the box and in development mode, Rails is way too slow for editing and previewing Sass. But you...
Read more >
Organizing Your Asset Pipeline in Development for Speed ...
The sass-rails README says not to use require/ require_tree/require_self in ... The thing they don't talk about is the compile times are incredibly...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found