リモートIPアドレス計算の最適化

rails/rails

ActionDispatch::RemoteIp::GetIp#calculate_ip の実装を最適化することで、クライアントIPアドレスの取得処理が約3倍高速化されました。本番環境のプロファイリングで判明したボトルネックを解消し、リクエスト全体の実行時間を削減します。

背景

本番環境のウォール/CPUプロファイリングによって、クライアントIPアドレスの取得処理がリクエスト全体の実行時間の数パーセントを占めていることが判明しました。特に rack-attack のようなミドルウェアが計算済みIPアドレスに繰り返しアクセスする環境では、この処理コストが無視できない影響を与えていました。

アロケーションプロファイリングでも、IP計算処理が上位にランクインしており、オブジェクト生成の観点からも最適化の余地があることが示されていました。#56805 はこのパフォーマンスボトルネックに対処しています。

技術的な変更

actionpack/lib/action_dispatch/middleware/remote_ip.rbcalculate_ip メソッドと filter_proxies メソッドが最適化されました。主な変更は以下の3点です。

配列操作の最適化

変更前:

ips = forwarded_ips + client_ips
ips.compact!

filter_proxies(ips + [remote_addr]).first || ips.last || remote_addr

変更後:

ips = forwarded_ips + client_ips
ips.compact!
ips << remote_addr

first_non_proxy(ips) || ips[-2] || ips.last

remote_addrips 配列に直接追加することで、filter_proxies 呼び出し時の配列結合操作(ips + [remote_addr])を削減しています。また、フォールバック処理を ips[-2] || ips.last に変更し、配列アクセスの効率を向上させています。

プロキシフィルタリングの最適化

filter_proxies メソッドを first_non_proxy に置き換え、処理ロジックを刷新しました。

変更前:

def filter_proxies(ips)
  ips.reject do |ip|
    @proxies.any? { |proxy| proxy === ip }
  end
end

変更後:

def first_non_proxy(ips)
  ips.find do |raw_ip|
    return unless raw_ip

    ip = IPAddr.new(raw_ip)
    @proxies.none? do |proxy|
      if proxy.is_a?(IPAddr)
        proxy.include?(ip)
      else
        proxy === raw_ip
      end
    end
  end
end

reject + first の組み合わせを find に置き換えることで、最初の非プロキシIPが見つかった時点で走査を終了します。また、IPAddr オブジェクトの場合、include? メソッドを用いてIPアドレスがプロキシの範囲に含まれるかを確認します。これにより、CIDR表記されたプロキシ範囲との比較が可能になります。それ以外の場合は === 演算子を使用する分岐を追加しています。

ベンチマーク結果

PRに添付されたベンチマークでは、以下の環境で約3.11倍の性能向上が確認されています。

ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [x86_64-darwin21]

Comparison:
           optimized:    33102.0 i/s
            original:    10638.0 i/s - 3.11x  slower

1回の実行時間が94.00μsから30.21μsに短縮され、スループットが大幅に向上しています。

設計判断

早期リターンとアロケーション削減を組み合わせた最適化 が採用されました。

reject による全要素の走査を find による早期リターンに置き換えることで、プロキシでないIPアドレスが見つかった時点で処理を終了します。加えて、ips + [remote_addr] による一時的な配列生成を削減し、アロケーションプロファイリングで観測されたオブジェクト生成コストを抑制しています。

フォールバック処理の ips[-2] は、remote_addr が配列末尾に追加される実装を前提とした最適化です。プロキシでないIPが見つからない場合、末尾から2番目の要素(remote_addr の直前)を返すことで、従来の ips.last と同等の結果を得ています。

まとめ

本PRは、配列操作の最適化と早期リターンの導入により、リモートIPアドレス計算処理を約3倍高速化しました。rack-attackのようなミドルウェアが頻繁にIPアドレスを参照する環境では、この改善がリクエスト全体のパフォーマンスに直接寄与します。本番環境のプロファイリングに基づく実装の見直しにより、Railsアプリケーションの実行効率が向上しました。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術詳細(各論)→まとめ(結論)の3部構成が明確に適用されており、ガイドラインに準拠しています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト(```ruby:path/to/file.rb)およびPR番号のリンク記法([#56805](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

プロファイリング、アロケーション、IPAddrなどの技術用語が適切に使用されており、専門知識を持つエンジニアという対象読者に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られています。可読性が非常に高いです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内のコードブロックは、提供されたDiffの内容を正確に反映しています。変更前後のコード比較も適切です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

ActionDispatch, rack-attack, IPAddr, reject, findなど、RailsおよびRubyの技術用語が文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

配列操作の最適化や`find`メソッドによる早期リターンの効果など、コード変更に関する技術的な説明は論理的かつ正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内の主張(パフォーマンス改善の背景、rack-attackの影響など)はすべてPRのDescriptionで裏付けられており、ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#56805)、ベンチマーク結果(約3.11倍、各i/s値)などの数値や固有名詞はすべて正確に引用されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトル「リモートIPアドレス計算の最適化」は、PRのタイトル「Optimize calculating remote IP address」と完全に一致しており、内容を的確に表しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事に含まれる情報はすべてPR内の情報に基づいており、PRに記載のないバージョン情報やサポート状況などの外部知識は追加されていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

記事内には時間表現に関する記述がなく、PR情報との齟齬はありません。