DIGGLE開発者ブログ

予実管理クラウドDIGGLEのプロダクトチームが、技術や製品開発について発信します

Rails+ApartmentにPostgreSQLのプロシージャを導入して多軸分析の集計速度を向上させた話 その4 ~Rails + plv8編~

こんにちは。DIGGLEエンジニアのzakkyです。

今回でレポート(多軸分析)の集計処理部分のパフォーマンス向上施策の第4弾となります。そろそろゴールが見えてきました。

前回までの記事の紹介

diggle.engineer diggle.engineer diggle.engineer

今回のお題

前回はRails+ApartmentでRails Wayに乗っかりつつPL/pgSQLを導入しましたので、今回は同じ流れでRails+ApartmentでRails Wayに乗っかりつつplv8を導入しようとして一筋縄では行かなかったあれやこれやをご紹介します。

書かないこと

以下の内容は含みませんので予めご了承ください

  • Rails、Apartmentの導入などの基礎的な部分
  • PostgreSQLのプロシージャに関する説明
  • plv8に関する説明

どれくらい速くなったのか

前回も記載しましたが、以下が今回改善した集計処理部分のLatency推移となります。 リリース後には低下していることが見て取れるかと思います。

大体3倍速程度には改善することができました。

赤線の辺りで今回の改善版のリリースを行っています

plv8を導入する

いつも通り早速本題に入ります。

前回はPL/pgSQLをRails Wayで管理する所まで説明しましたので、今回はplv8でも同様にRails Wayに乗っかって管理できるようにしてあげようと思います。 最終的な変更内容については結論に記載していますので、結果だけ知りたい方は最下部を参照ください。

localでplv8を動かす

前段の話にはなりますが、そもそもlocalでplv8が入ったPostgreSQLを用意できないと辛い。ということで、Dockerイメージを用意します。

残念ながらPostgreSQLのDockerイメージにはplv8が入っていないので自前で作る必要があります。 基本的にはplv8のドキュメントを見ながらDockerfileを書くだけの簡単なお仕事ですので、そこまで難しい内容ではありません。

参考までに、私が作ったものを下記に置いておきます。

github.com

db:resetでplv8を適用する

plv8はCREATE EXTENSION plv8を実行してあげる必要があるのですが、structure.sqlに上記内容が含まれないため、db:resetを行った際にplv8が存在しないと怒られてしまいます。

Apartmentのドキュメントに沿って修正する(上手くいかない)

以下のREADMEに沿って修正しても、test側でエラーとなってしまいます。 github.com

test側でエラーになる原因を探る

pg_dumpの仕様として、--schemaオプションを付けてるとExtensionはexportされない

要約すると上記の通りとなります。

Apartmentを使っている以上、前回の記事でpublicテナントだけをダンプ対象にする方法として説明した通り、--schemaオプションを付けない道はありません。また、--schemaではなく--exclude-schemaを付ける事でも解消できるとの記載がありますが、schemaがどんどん増えるapartmentとの相性は考えるまでもなく最悪でしょう。

詳しい内容を知りたい方は下記issueが参考になるかと思います。

github.com github.com

ライブラリのソースを追いかける

上記の時点で、かなり詰んでる気がしますが、まだもう少し頑張ります。

ここからは、db:resetの仕様を把握して何とかできる糸口がないかを探していきます。

ActiveRecordのdb:resetの仕様を読み解く

ソースを読み進めていくと、大きな流れとして、db:drop -> db:create -> db:schema:loadの順に動いているようです。

  task reset: [ "db:drop", "db:setup" ]

  namespace :setup do
    task all: ["db:create", :environment, "db:schema:load", :seed]
    ...(略)...
  end

code link

db:createやdb:schema:loadを読み解く

下記に抜粋した通り、db:createやdb:schema:loadのロジックの中で、database全体に対してループしながらcreateやloadを行っていることが分かりました。今回行いたいこととしては、そのロジックの中でextensionの追加をしてあげる必要があります。

先ほどのApartmentのREADMEで指定された方法では、db:createの後にextensionの追加を行っており、この方法ではデフォルト(≒先頭)のデータベースに向けてしか実行できないことが分かります。(つまり2つ目のデータベースであるtest側のデータベースへは適用できずに今回の問題が発生する)

db:create

      def create_all
        old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
        each_local_configuration { |db_config| create(db_config) }
        if old_pool
          ActiveRecord::Base.connection_handler.establish_connection(old_pool.db_config)
        end
      end

code link

db:schema:load

    namespace :load do
      ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
        ...(略)...
      end
    end

code link

もっとソースを読む

もう少し読み進めてdb:createのロジックを見てみると、↓のcreateの中でextensionを登録できれば何とかなりそうです。

each_local_configuration { |db_config| create(db_config) }

code link

結論

最終手段ではありますが、以下のようなモンキーパッチを当ててあげることで問題を解決しました。

module PostgreSQLDatabaseTasksMonkeyPatch
  def create(*args)
    super(*args)
    ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS plv8;'
  end
end
ActiveRecord::Tasks::PostgreSQLDatabaseTasks.prepend(PostgreSQLDatabaseTasksMonkeyPatch)

We're hiring!

DIGGLEでは、ライブラリのソースを読んで必要な時にはモンキーパッチも書く開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。

herp.careers

Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。

meety.net