RailsやRSpecの起動が遅いと感じたときの高速化テクニック

開発をしていてRailsRSpecの起動がなんか遅いなーと感じることはないでしょうか。

はじめは遅いと思っても次第に慣れていってしまいがちですが、日々の開発を通してRailsRSpecの起動は何回も実行するので塵も積もれば山となってしまうので改善したいものです。もしあなたの環境で常に起動までに2-3秒以上かかっているのであれば黄色信号だと思います。

この記事では、ローカル環境のRailsRSpecの起動を早くするためのテクニックについて説明します。開発環境の状況によりうまく適用できないケースもあると思いますができるだけ汎用的な手法について説明しています。

ちなみに、私が携わったとあるプロジェクトで改善して次のように高速化できました。

  • railsコマンドの実行時間が「10秒から0.6秒」
  • rspecコマンドの実行時間の「23秒から1.1秒」

ぜひ、参考になれば嬉しいです。

1. spring gemでRailsの起動時間を速くする

Railsの起動速度を向上させる方法の1つにspringを使う方法があります。

springはRailsアプリケーションのプリローダーで、bin/railsコマンドを実行すると自動的にバックグラウンドで実行されるので普段の開発で気にする必要はありません。

springがバックグラウンドで動くことで、初回のrailsの起動は遅いのですが、2回目以降は事前にロードされるので起動がかなり早くなります。

以下は、とあるプロジェクトで実際にspringをいれたときのrails runnerの時間です。10秒程度かかっていたのが0.67秒まで短縮されています。

# springを入れる前
time bin/rails runner "puts 'hello, world'"
4.73s user
2.92s system
10.780 total

# springを入れた後 (※2回目のコマンド実行時)
time bin/rails runner "puts 'hello, world'"
0.08s user
0.06s system
0.673 total

springのインストールとセットアップ方法は次の通りです。詳細は、READMEをご覧ください。

まず、Gemfilespringを追加して、bundle installを実行します。

# Gemfile
gem "spring", group: :development

そして、spring binstub --allコマンドを実行すると、bin/ディレクトリ配下のファイルが更新され、bin/railsなどを実行するとspringが自動で実行されるようになります。

$ bundle exec spring binstub --all

springの注意点としては、springがうまくインストールできなかったり、springを使うと一部テストがこけるケースが稀にあります。また、Dockerと一緒に使うにはうまくspringが動かないケースもあったりするのでご注意ください。

参考: Using Spring with a containerized development environment

2. spring-commnads-rspec gemでRspecの起動時間を速くする

Rspecの起動速度を向上させる方法の1つにspring-commnads-rspec gemを使う方法があります。これはspring用のrspecコマンドです。

こちらもbin/rspecと実行することで自動的にspringがバックグラウンドで起動し、2回目以降のbin/rspecコマンドの起動時間を1秒以内のスピードまで早めることができます。

spring-commands-rspecのインストールとセットアップ方法は次の通りです。

まずGemfilespring-commands-rspecを追加して、bundle installを実行します。

# Gemfile
gem 'spring-commands-rspec', group: :development

次に、bundle exec spring binstub rspecを実行することで、bin/rspecコマンドが作成されます。

$ bundle exec spring binstub rspec

3. bootsnapRailsの起動時間を速くする

bootsnap は負荷が高い計算を最適化とキャッシュをすることで起動時間を短縮します。

Shopifyが開発したgemで、とあるShopifyの大きなアプリケーションでは、起動時間が約25秒から6.5秒と75%早くなったという結果があります。

インストール・セットアップ方法は次の通りです。

まず、Gemfilebootsnapを追加します。

# Gemfile
gem 'bootsnap', require: false

そして、config/boot.rbrequire 'bootsnap/setup'を追加します。

# config/boot.rb
require 'bootsnap/setup'

bootsnapの仕組みについてはREADMEのHow does this work?にわかりやすく記載されていますが、ざっくり次の2つのことをしています。

  • パスを事前にスキャン: Kernel#requireKernel#loadが修正され、$LOAD_PATHスキャンを実施しなくする。
  • コンパイルのキャッシュ: Rubyバイトコードの結果をキャッシュしたり、YAMLオブジェクトのロード結果をMessagePackのフォーマットでキャッシュする。

4. テストデータの作成タイミングを変えてRspecの起動時間を速くする

テストデータの生成に10秒ほどかかっており、bin/rspecコマンドの実行からテストの実行が開始されるまでの時間がかかるケースがありました。そういった場合は、テストデータの生成タイミングを変えることでRspecの起動時間を高速化できます。

例えば、次のようにテストスートの実行前に毎回実行時間がかかる初期データの生成をしていたとします。

RSpec.configure do |config|
  # テストスートの実行前の処理
  config.before(:suite) do
    # 時間がかかる初期データの生成処理(イメージ)
    Seed.generate(fixture_paths)
  end
end

そういった場合は、テストの実行ごとに初期データを生成するのではなく、テスト環境のセットアップ時に初期データを生成できるようにするのがおすすめです。テスト時には、トランザクションdatabase_rewinderを使うことでテストごとにデータベースの状態を戻すので問題になるケースはほとんどないでしょう。

# テスト環境のセットアップ時に初期データを生成する(イメージ)
$ RAILS_ENV=test bin/rails runner "Seed.generate(fixture_paths)"