Active Record x Oracle 12c (19c) における `ALL_SYNONYMS` のパフォーマンスリグレッション回避

勤務先で行っている今月のパッチ会で、yahonda さんに相談した Active Record Oracle enhanced adapter の発行しているスロークエリ対策について書き残しておきます。先に結論を書くと処理によっては、処理によってはパフォーマンスが3〜5倍ほど改善されました。

問題点を端的に書くと Oracle 12c で ALL_SYNONYMS が遅くなっています (Oracle 19c も同様) 。以下はそのリグレッションについて言及された記事です。

blog.pythian.com

Rails アプリケーションでシノニムを使っていないようであれば、Active Record Oracle enhanced adapter での以下のクエリを削除するモンキーパッチを作成することで改善できます。もしシノニムを使っていたら、、、分かりません。頑張ってください!

さて、ここからはシノニムを使っていない前提で書きます。

Oracle enhanced adapter 6.1.4

以下の範囲のクエリを削除してください。

github.com

Oracle enhanced adapter 6.0.6

対応は Oracle enhanced adapter 6.1.4 と同様です。

github.com

モンキーパッチコード

具体的には config/initializers/oracle.rb のようなファイルとして以下のようなファイルを、アプリケーションに置くことになります。

module ActiveRecord
  module ConnectionAdapters
    # interface independent methods
    module OracleEnhanced
      class Connection #:nodoc:
        def describe(name)
          name = name.to_s
          if name.include?("@")
            raise ArgumentError "db link is not supported"
          else
            default_owner = @owner
          end
          real_name = OracleEnhanced::Quoting.valid_table_name?(name) ? name.upcase : name
          if real_name.include?(".")
            table_owner, table_name = real_name.split(".")
          else
            table_owner, table_name = default_owner, real_name
          end
          sql = <<~SQL.squish
            SELECT owner, table_name, 'TABLE' name_type
            FROM all_tables
            WHERE owner = '#{table_owner}'
              AND table_name = '#{table_name}'
            UNION ALL
            SELECT owner, view_name table_name, 'VIEW' name_type
            FROM all_views
            WHERE owner = '#{table_owner}'
              AND view_name = '#{table_name}'
          SQL
          if result = _select_one(sql)
            case result["name_type"]
            when "SYNONYM"
              describe("#{result['owner'] && "#{result['owner']}."}#{result['table_name']}")
            else
              [result["owner"], result["table_name"]]
            end
          else
            raise OracleEnhanced::ConnectionException, %Q{"DESC #{name}" failed; does it exist?}
          end
        end
      end
    end
  end
end

(Oracle enhanced adapter 内部のいくつかの主要箇所にある) describe メソッドを使っているあらゆる箇所のパフォーマンスリグレッションを回避できる見立てです。開発環境でのスキーマ読み込みに関する例で言えば、このモンキーパッチで bin/rails db:migrate で実行される bin/rails db:schema:dump が3〜5倍くらい高速になりました。

今後の展望

モンキーパッチからの本対応としては、既存の use_old_oracle_visitor のように use_synonyms といった新規設定オプションを用意して、削除しているクエリをバイパスできるようにするのが一案です。

 ActiveSupport.on_load(:active_record) do
   ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
     # true and false will be stored as 'Y' and 'N'
     self.emulate_booleans_from_strings = true

     # start primary key sequences from 1 (and not 10000) and take just one next value in each session
     self.default_sequence_start_value = "1 NOCACHE INCREMENT BY 1"

     # Use old visitor for Oracle 12c database
     self.use_old_oracle_visitor = true

+    # Use `all_synonyms`, true by default.
+    self.use_synonyms = false
+
     # other settings ...
   end
 end

互換性のためデフォルトでは ALL_SYNONYMS をバイパスしない設定にすると思います。

ただ、以下の理由から私の方で本対応への着手をする場合はまだ先の話になりそうです。

  • この日記を書いている時点の rsim/oracle-enhanced リポジトリは master ブランチが落ちているので、まずそこを直す必要がある
  • https://github.com/yahonda/rails-dev-box/tree/runs_oracleOracle enhanced adapter の開発環境が使えなくなったため、新たに構築が必要で時間の捻出が必要 (先が長そう)
  • 新機能になるため、既存の安定版へのバックポーティングがされないと思われることから、急ぎではない (いまなら Rails 7.0 に間に合うかも)

yahonda さんアドバイスありがとうございました!