【Rails】アプリケーションサーバー「Unicorn」の基本情報と実装方法

はじめに

Railsアプリケーションを本番環境で安定して稼働させるには、Webサーバーの導入が不可欠です。Webサーバーは、クライアントからのHTTPリクエストをRailsアプリに伝達し、アプリで処理されたレスポンスをクライアントに返す役割を担います。

本記事では、Railsアプリ用のアプリケーションサーバー「Unicorn」の基本的な使い方を解説します。Unicornの役割や設定方法、Nginxとの連携方法など、Unicornを使いこなすために必要な知識を詳しく説明していきます。

Unicornの基本

Unicornについて

UnicornとはRackアプリケーション用のアプリケーションサーバーです。

開発者のみがリクエストを送信する開発環境と違い、本番環境ではいつ、どれくらいのリクエストが送信されてくるかはわかりません。24時間365日、間断なくリクエストが送信されてくるかもしれませんし、複数のリクエストが同時発生的に送信されてくる可能性もあります。

Unicornは送信されてきた多数のリクエストを捌き、分散してRackアプリケーションに伝達するという機能を持ちます。リクエストをアプリケーションへ伝達するとアプリケーションからレスポンスが戻ってくるまで待機し、最終的にリクエストに対するレスポンスをクライアントに返します。

※図中には「Unicorn (Web Server)」と記載されていますが、正しくは「Unicorn (Application Server)」です

この画像はクライアントからRailsアプリへリクエストを送信したときの概念図です。リモートサーバーにあるUnicornがクライアントからのリクエストを受け取り、アプリケーションに伝達していることがわかります(レスポンスシーケンスは省略しています)。

この概念図を見て、「Rackってなに? Railsはどこ?」「なんでUnicornとNginxを組み合わせるの?」など疑問があるかと思いますので、詳しく解説していきます。

Rackってなに? Railsはどこ?

※図中には「Web Servers」と記載されていますが、正しくは「Application Servers」です

Rackとは、Rack専用のアプリケーションサーバーとRubyで作られたWebアプリケーションフレームワークを繋ぐためのAPIを提供するアプリケーションサーバーです。「Rubyで作られたWebアプリケーションフレームワーク」とはもちろんRuby on Railsのことですが、他にもSinatraというWebアプリケーションフレームワークがあります。

Rackというアプリケーションサーバーという土台があり、その上にRuby on RailsやSinatraが乗っていると思ってください。概念的にはRack = Ruby on Railsと思っていただいて構いません。

Rack専用のアプリケーションサーバーには本記事の主役であるUnicorn以外にも、PumaやThin、WEBrickといったたくさんの種類があります。これらのRack専用のアプリケーションサーバーとWebアプリケーションフレームワーク(Ruby on Rails/Sinatra)が、互いを意識することなく柔軟に通信できるようにしているのがRackというわけです。

なんでUnicornとNginxを組み合わせるの?

なぜUnicornとNginxというふたつのサーバーを組み合わせる必要があるのでしょうか。その理由を具体的に説明します。

  • 負荷分散とスケーラビリティ
    • Nginxはリバースプロキシとして機能し、複数のUnicornワーカープロセスに負荷を分散する
    • これにより、アプリケーションの処理能力とスケーラビリティが向上する
  • 静的ファイルの効率的な処理
    • Nginxは静的ファイル(画像、CSS、JavaScript等)の配信に非常に優れている
    • Unicornに静的ファイルの処理を任せるよりも、Nginxで処理する方が効率的
  • セキュリティの強化
    • Nginxはバッファオーバーフロー攻撃やDDoS攻撃などに対する保護機能を提供する
    • アプリケーションサーバー(Unicorn)を直接インターネットに晒さずに済む
  • SSL/TLS終端
    • Nginxで SSL/TLS 通信を終端することで、Unicornの負荷を軽減可能
  • リクエストのバッファリングと圧縮
    • Nginxはリクエストをバッファリングし、必要に応じて圧縮することができる
    • これにより、Unicornの処理効率が向上する
  • 柔軟なルーティング
    • Nginxの柔軟なルーティング機能を利用して、複雑なリクエスト処理やリダイレクトを実現可能
  • ゼロダウンタイムデプロイの実現
    • Nginxを使用することで、Unicornのワーカープロセスを順次再起動しながら、サービスを中断せずにアプリケーションを更新できる
  • 長時間接続の処理
    • WebSocketなどの長時間接続をNginxで処理し、Unicornの負荷を軽減可能
  • ログ管理とモニタリング
    • Nginxの詳細なログ機能を利用して、アクセス解析やトラブルシューティングが容易になる

これらの理由により、UnicornとNginxを組み合わせることで、高性能で安定したWebアプリケーション環境を構築することができます。

特に大規模なトラフィックを処理する必要がある場合や、セキュリティを重視する場合に、この組み合わせは非常に効果的です。

Unicornの動作

Unicornのプロセスモデル

Unicornは、マスタープロセスと複数のワーカープロセスを使用するプリフォークモデルを採用しています。

  • マスタープロセス
    • アプリケーションをロードし、設定を読み込む
    • 子プロセス(ワーカー)を生成・管理する
    • シグナルハンドリングを行い、子プロセスの終了を監視する
  • ワーカープロセス
    • マスタープロセスによって生成する
    • アプリケーションロジックを実行し、HTTPリクエストを処理する
    • 処理が完了したら、マスタープロセスに制御を返す

リクエストの処理の流れ

  1. クライアントからHTTPリクエストが送信される
  2. Webサーバー(例:Nginx)がリクエストを受け取り、Unicornのソケットに転送する
  3. マスタープロセスがリクエストを受け取り、利用可能なワーカープロセスに割り当てる
  4. 割り当てられたワーカープロセスがアプリケーションロジックを実行し、レスポンスを生成する
  5. レスポンスがマスタープロセスに返され、Webサーバーを経由してクライアントに送信される

メモリ管理

Unicornは、Copy-on-write (CoW) メモリモデルを活用してメモリ効率を高めています。

  • マスタープロセスがアプリケーションをロードし、メモリ上に展開する
  • ワーカープロセスが起動する際、マスタープロセスのメモリ領域を共有する
  • ワーカープロセスが何かしらの変更を加えた場合、その部分のメモリ領域がコピーされる
  • これにより、メモリ使用量を抑えつつ、各ワーカープロセスが独立して動作できる

設定と制御

Unicornの動作は、設定ファイルで制御されます。

  • ワーカープロセスの数、タイムアウト値、リスニングソケットなどを設定できる
  • シグナルを使用して、マスタープロセスを制御できる
    • QUIT:グレースフルシャットダウンを実行
    • TERM:即時終了
    • USR2:ゼロダウンタイムデプロイ用のマスタープロセスの再起動
  • 設定ファイルはRubyで記述されるため、動的な設定が可能

プリフォークモデルとCopy-on-writeを活用することで、高速なリクエスト処理と効率的なメモリ管理を実現しています。Nginxなどのリバースプロキシと組み合わせることで、Railsアプリケーションを安定して稼働させることができます。

Unicornの使い方

Unicornのインストール

Unicornをインストールするには、Gemfileに以下を追記してbundle installを実行します。

Gemfile

group :production do
  # 以下を追記
  gem 'unicorn'
end

Unicornの設定

Unicornを起動する際に必要となる設定ファイルを作成します。作成するディレクトリとファイル名はなんでもいいのですが、config/ディレクトリ配下にunicorn.rbというファイル名で作成するのが一般的です。

設定ファイルは大別して「変数の設定」と「フォーク前後の処理」に分けられます。ここではそれらを分けて説明していますが、実際にはひとつの設定ファイルにまとめて書いてください。

変数の設定

変数の設定は以下の通りです。

unicorn.rb

# Railsアプリのルート
rails_root = File.expand_path('../../', __FILE__)

# Gemfileの場所
ENV['BUNDLE_GEMFILE'] = rails_root + "/Gemfile"

# Unicornの設定
worker_processes  2
timeout           15
working_directory rails_root
pid               File.expand_path 'tmp/pids/unicorn.pid', rails_root
listen            File.expand_path 'tmp/sockets/.unicorn.sock', rails_root
stdout_path       File.expand_path 'log/unicorn.log', rails_root
stderr_path       File.expand_path 'log/unicorn.log', rails_root
preload_app       true

CapistranoでUnicornを起動する際、Gemfileの場所が必要になるので、ここで環境変数に設定しています。

その他の変数の設定は以下の通りです。

設定 説明
worker_processes 同時起動するワーカーの最大数。ワーカーが多ければ多いほどメモリ消費も激しいので、サーバーのスペックとアプリケーションのアクセス数によって決定する。
timeout ワーカーが処理を開始し終了するまでの最大時間(秒数)。設定した時間を超えた場合、そのワーカーは破棄される。
working_directory Unicornの起動コマンドが実行されるディレクトリ。
pid UnicornのプロセスIDを保存しておくファイル。
listen リクエストを受信するポート番号。Nginxとの接続に使用。
stdout_path Unicornの実行ログを出力するファイル。
stderr_path Unicornのエラーログを出力するファイル。
preload_app Unicornの再起動をダウンタイムなしで行う。この設定によりアプリケーションがダウンすることなくデプロイができるようになる(ホットデプロイ)。詳細は後述。

フォーク前後の処理

フォーク前後の処理は以下の通りです。

unicorn.rb

# フォークが行われる前の処理
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill "QUIT", File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

# フォークが行われた後の処理
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

フォーク前後の処理では、大きく分けて「UnicornとActive Recordの切断/接続」と「マスタープロセスの再起動」のふたつを行っています。まず、「UnicornとActive Recordの切断/接続」の処理を抜き出すと以下になります。

unicorn.rb

# フォークが行われる前の処理
before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
end

# フォークが行われた後の処理
after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

フォーク前にActive Recordとの接続を切断し、フォーク後に再び接続しています。これは、マスタープロセスで使用しているActive Recordとの接続をワーカープロセスで使用させないために行っているそうです(明確に説明している情報が見つかりませんでした)。

続いて「マスタープロセスの再起動」の処理を抜き出すと以下になります。

unicorn.rb

# フォークが行われる前の処理
before_fork do |server, worker|
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      Process.kill "QUIT", File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

preload_appを有効にしていると、ダウンタイムなしでUnicornの再起動を行うことができます(-USR2オプション指定時)。具体的な処理の流れは以下のようになります。

  1. 現在のプロセスIDが保存してあるunicorn.pidunicorn.pid.oldbinにコピー
  2. 新しいUnicornのプロセスを起動(新しいプロセスIDをunicorn.pidに保存)
  3. unicorn.pid.oldbinを参照して古いプロセスを停止

一時的にふたつのプロセスが起動されることになり、それによってダウンタイムがなくなるという仕組みです。そして、3.を実行しているのが上記の「マスタープロセスの再起動」になります。

Unicornの起動/停止/再起動

Unicornの起動

Unicornを起動するには以下のコマンドを実行します。

$ bundle exec unicorn -c config/unicorn.rb [-E $RAILS_ENV] [-D]

-Eオプションをつけると環境を指定できます。-Dオプションをつけるとデーモンプロセス(常駐プロセス)として起動します。

Unicornの停止

Unicornを停止するには以下のコマンドを実行します。

$ kill -QUIT `cat /path/to/unicorn.pid`

Unicornの再起動

Unicornを再起動するには以下のコマンドを実行します。

# 通常の再起動
$ kill -HUP `cat /path/to/unicorn.pid`

# 緩やかな再起動
$ kill -USR2 `cat /path/to/unicorn.pid`

Capistranoによるホットデプロイを行うには、必ず-USR2オプションを指定して再起動を行います。なお、アプリのデプロイ時にはUnicornの再起動を行う必要がありますが、-HUP-USR2の再起動では修正が反映されないときがあります。そのときはUnicornの停止と起動を行なってください。

Capistranoとの連携

Capistranoを使ってデプロイの自動化を行っている場合、Unicornの起動または再起動を自動化することができます。Capistranoの使い方については以下の記事を参照してください。

まとめ

以上、Unicornを使ってRailsアプリケーションを本番環境で稼働させる方法を詳しく解説しました。Unicornを導入することで、以下のような利点が得られます。

  • 高いスケーラビリティ
    • マスタープロセスが複数のワーカープロセスを管理し、リクエストを効率的に処理できる。
  • ゼロダウンタイムデプロイ
    • preload_appオプションを使うことで、アプリケーションを停止せずにデプロイが可能。
  • 高速なリクエスト処理
    • プリフォークモデルにより、リクエストごとにプロセスを生成する必要がない。

Unicornを使えば、Railsアプリケーションを安定して本番環境で稼働させることができます。ただし、Unicornだけでは不十分で、Nginxとの連携が重要です。

ぜひUnicornを活用して、高性能で信頼性の高いWebアプリケーションを構築してみてください。

関連記事

【Rails】Paranoiaを使用した論理削除(ソフトデリート)
# はじめに Paranoiaは、Railsアプリケーションで論理削除(ソフトデリート)を実現するためのGemです。 論理削除は、データベースのレコードを物理的に削除するのではなく、削除フラグを設定することで「削除済み」とみなす方法です。こ [...]
2024年7月20日 21:33
【Rails】activerecord-multi-tenantを使用したマルチテナントアプリケーションの作成
# はじめに マルチテナントアプリケーションでは、複数の顧客(テナント)が同じアプリケーションを利用するため、データの分離が必要です。 activerecord-multi-tenantは、このようなマルチテナント環境をサポートするための便 [...]
2024年7月18日 16:50
【Rails】RubyとRailsにおけるattr_reader, attr_writer, attr_accessorの概念と使用方法
# はじめに RubyとRailsの開発において、`attr_reader`,`attr_writer`,`attr_accessor`は非常に便利なメソッドです。これらは、クラス内でインスタンス変数に対するゲッターおよびセッターメソッドを簡単に [...]
2024年7月17日 18:11
【Rails】RubyとRailsにおけるyieldの概念と使用方法
# はじめに RubyとRailsにおける`yield`は、メソッドやテンプレートの中で動的にコードブロックを実行する能力を提供し、これによってコードの再利用性と拡張性が大幅に向上します。本記事では、RubyとRailsにおける`yield`の概 [...]
2024年7月17日 13:15
【Rails】AASMを使用してオブジェクトの状態遷移を効率的に管理
# はじめに Railsアプリケーションにおいて、オブジェクトの状態管理は重要な課題の一つです。AASM (Acts As State Machine) gemは、複雑な状態遷移を効率的に管理します。本記事では、AASMの基本的な使い方を解説して [...]
2024年7月16日 18:00
【Rails】RSpec + Swagger + rswagでアプリケーションのAPIをテストおよびドキュメント化する方法
# はじめに Railsアプリケーションの開発において、APIのテストとドキュメント化は重要な要素です。 RSpecはテストフレームワークとして広く利用されており、SwaggerはAPIの設計とドキュメント化を支援します。これらを統合するr [...]
2024年7月16日 14:27
【Rails】mailcatcherを使用して開発環境でメール送信をテストする方法
# はじめに mailcatcherは、開発環境でのメール送信をキャプチャするためのツールです。ローカルで送信されたメールをブラウザ上で簡単に確認できるようにします。mailcatcherをRailsアプリケーションで使用する方法について説明しま [...]
2024年7月15日 16:37
【Rails】impressionistを使用してページビューやクリック数を追跡する方法
# はじめに impressionist Gemを使用してRailsアプリケーションでページビューやクリック数を追跡する方法について説明します。 # 実装方法 ## impressionist Gemのインストール まず、impre [...]
2024年7月15日 14:18