こんにちは。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を書くだけの簡単なお仕事ですので、そこまで難しい内容ではありません。
参考までに、私が作ったものを下記に置いておきます。
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が参考になるかと思います。
ライブラリのソースを追いかける
上記の時点で、かなり詰んでる気がしますが、まだもう少し頑張ります。
ここからは、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
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
db:schema:load
namespace :load do ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name| ...(略)... end end
もっとソースを読む
もう少し読み進めてdb:createのロジックを見てみると、↓のcreateの中でextensionを登録できれば何とかなりそうです。
each_local_configuration { |db_config| create(db_config) }
結論
最終手段ではありますが、以下のようなモンキーパッチを当ててあげることで問題を解決しました。
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では、ライブラリのソースを読んで必要な時にはモンキーパッチも書く開発メンバーを募集しています!少しでも興味があれば、ぜひ下記採用サイトからエントリーください。
Meetyによるカジュアル面談も行っていますので、この記事の話をもっと聞きたい!という方がいらっしゃいましたら、お気軽にお声がけください。