こんにちは、SmartHRのDPEユニットでエンジニアをしている@alpaca-tcです。 この記事では、Railsで頻発しているけれども見落とされがちな未ログインユーザーのセッションと、その削減方法について紹介します。
目次
未ログインユーザーのセッションとは
Railsではログイン情報等を管理するためにセッションを利用します。
通常はControllerで session にアクセスすることでセッション情報を取得・更新できます。
def create session[:user_id] = @user.id ... end
セッションは、CSRF攻撃を防ぐためのトークンを session[:_csrf_token] に格納することにも利用されています。
実はセッションは、csrf_tokenのために未ログインのアクセスに対しても自動的に発行されています。そのため、未対策では気づかぬ間に大量にセッションを発行してストレージを圧迫しています。
経験に基づくと、ログイン必須のサービスであってもセッション全体の8割が未ログインユーザーによって占められます。そうなると、塵も積もれば山となり、セッションストアのメモリ逼迫を引き起こす要因となります。
セッション削減の方法
ミドルウェアを導入して、未ログインユーザーのセッションの有効期限を短くすることで、セッションの削減が可能です。
class UnauthenticatedSessionExpireAfter def initialize(app, expire_after: 1.hour.freeze) @app = app @expire_after = expire_after end def call(env) @app.call(env) ensure if env[Rack::RACK_SESSION_OPTIONS] && !authenticated?(env) env[Rack::RACK_SESSION_OPTIONS][:expire_after] = @expire_after end end private def authenticated?(env) # 任意のキーでログイン方法を格納している場合 env["rack.session"] && env["rack.session"]["user_id"] # deviseの場合 Warden::SessionSerializer.new(env).stored?(:user) end end
上記のコードでは、未ログインのユーザーに対してセッションの有効期限を短時間に設定しています。これにより、未ログインユーザーのセッションが短期間で削除され、セッションストアのストレージ使用量を抑制できます。
なお、ミドルウェアの挿入は以下のように行います。 重要なポイントとして、ミドルウェアはセッションの保存前に有効期限を設定する必要があるため、セッションストアの直後に挿入する必要があります。
ご自身の利用しているセッションストアのミドルウェア名については ./bin/rails middleware コマンドで確認してください。
# config/application.rb config.middleware.insert_after ActionDispatch::Session::YourSessionStore, UnauthenticatedSessionExpireAfter
なぜミドルウェアを使うのか
例えば、古いgitlabでは元々はコントローラーで設定していました。
class ApplicationController < ActionController::Base before_action :limit_unauthenticated_session_times private def limit_unauthenticated_session_times return if current_user return unless request.env['rack.session.options'] request.env['rack.session.options'][:expire_after] = 1.hour end end
しかし、最新のコードベースではミドルウェアで設定するように変更されています。。 コントローラーで設定する方法では、コントローラーに到達せずミドルウェアで処理されるようなリクエストに対してはセッションの有効期限が設定されなかったためです。
多くのRailsアプリケーションでも同じことが言えるため、ミドルウェアで設定する方法を推奨します。
成果とまとめ
この変更を導入して既存のセッションにも適用したところ、redisのセッションストアの使用量は大きく削減されました。
- メモリ 13GB 減 (27%程度削減)
- キー数 56,410,000 減 (52%程度削減)
その後もキーの増加が抑制されており、セッションストアの逼迫を防止できています。
未ログインユーザーのセッションは見落とされがちですが、多くのRailsアプリケーションは潜在的にこの問題を抱えているはずです。ぜひ参考にしてみてください。