より安全にadhoqを実行したい
追記:だいたいの内容が反映された、adhoq v0.4.0がリリースされていますので、ご利用ください。
adhoqを使うと、管理画面などで任意のSQLができるようになって便利そうだ。 ただ調査をしていると、sandbox機構は、Transactionを実行してRollbackする、という方式になっているので、アプリケーション実行ユーザーがDROP TABLEできる権限で動いていたりすると、DROP TABLEなどすることができてしまうので、通常のアプリケーションユーザーとは別のユーザーで実行したかった。(アプリケーションユーザーがDROP TABLEできない方がのぞましいが。
また、本番と同じ系統で任意のSQLを実行できるようにした場合、重いSQLなどを実行されてしまったときに本番で別の部分がSlow queryになってしまうので、サービスから隔離した系統で実行させたいというのもあった。
Adhoqの設定には、 config.database_connection
というものがあって、ここを置きかえればよさそうにも思えるが、
https://github.com/esminc/adhoq/blob/master/lib/adhoq.rb#L17
config.database_connection = proc { ActiveRecord::Base.connection }
ここを置きかえただけだと、Adhoqのユーザーの入力したSQL実行終了後に、Adhoqのレコード入れるときなどのconnectionを元に戻すことができないので、うまくいかないっぽかったのでAdhoqそのものを変更した。
Comparing esminc:master...walf443:connection_switchable · esminc/adhoq · GitHub
インターフェースを変えてしまうのでPRにして通るかどうかわからないし英語で書いてうまく伝わるかなー、といった感じだったのでひとまずブログで書いてみた。
なお、手元ではreadonlyな接続を利用するのに、switch_pointとあわせて利用していて、上記のパッチに加えて、
# config/initializers/adhoq.rb Adhoq.configure do |config| config.hidden_model_names = %w[SwitchPoint::Proxy::MainReadonly] end module AdhoqReadonlyable def with_connection result = nil SwitchPoint.with_readonly_all do result = yield(ApplicationRecord.connection) end result end end Adhoq::Executor::ConnectionWrapper.prepend(AdhoqReadonlyable)
といった感じにして、connectionまわりを差し変えている。
また、触っていて、いくつか気になったところを見つけたのでいくつか別途PRにした。
- クエリのページのN+1クエリの修正 github.com
- テーブル一覧を表示しようとすると全テーブルのcountを取得しようとする問題の修正 github.com
https://github.com/esminc/adhoq/commit/c104c36018744f22fccd537177b8cfa38051a27b
SQLが実行できなかったときに失敗状態にならない問題を修正 Fix exexution failure handling by walf443 · Pull Request #160 · esminc/adhoq · GitHub
preloadがうまくいかないときに何とかするbulk_loaderというライブラリを作ってみた
というやつが先々週のgithub trendで流行っていたんですが、もっとうまく書けそう、という気がしたので書いてみました。 (GraphQL使ってないので同じ用途に使えるかは今のところ不明ですが。
# app/models/post.rb class Post < ApplicationRecord include BulkLoader::DSL bulk_loader :comment_count, :id, default: 0 do |ids| Comment.where(id: ids).group(:post_id).count end end
こんなふうに書いておくと、次のように使えます。
# app/controllers/posts_controller.rb class PostsController < ApplicationController def index @posts = Post.limit(10) # load comment_count blocks with mapping by #id # you can avoid N+1 queries. Post.bulk_loader.load(:comment_count, @posts) render(json: @posts.map {|post| { id: post.id, comment_count: post.comment_count } }) end end
まだ試していないですが、Polymorphic Associationの関連を効率良く読みこんだり、memcachedとかにcacheしつつなかったときにloadしたりするのにも使えると思います。
bulk_loaderの定義時に、:idをキーに持つHashを返すのがポイントで、:idの情報を利用して元のobjectに紐づけてくれるので、mappingの処理を泥くさくなく書くことができます。(もちろんこのサンプルは非常にきれいに書けるパターンですが。
ActiveRecordに特に依存しないように実現しているので、drapper などでも定義できるはずですし、railsのupdate時に困ったりする心配も特にはないはずです。
今日からあるRubyKaigi 2017 に参加しているのでご意見などいただけると幸いです。 pixivブースでPawooの象さんのTシャツを着ている人がいたらたぶん私です。
bundle update 2016-01-25
activerecord-import v0.11.0
https://github.com/zdennis/activerecord-import/compare/v0.10.0...c0a4393a
AR 5.0 betaまわりの対応を進めているもよう。ただテスト一部通ってないのでまだ微妙かもしれない。
capistrano-rails v1.1.6
https://github.com/capistrano/rails/compare/v1.1.5...v1.1.6#files_bucket
- zshだとassets:cleanが適切に走らないことがあるのを修正
- assets:clobberを実行できるようになった
- deploy:migratingにhookすることで、rake db:migrate以外のことをできるようになった
twitter v5.16.0
https://github.com/sferik/twitter/compare/v5.15.0...v5.16.0#files_bucket
- http gemを更新
- mp4のアップロードに対応
mp4のアップロード対応は、入ったとおもってたけど、masterのみにあって、v5.16.0には含まれていないっぽい。
http v1.0.2
https://github.com/httprb/http/compare/v0.9.8...v1.0.2#files_bucket
http 1.0.0のリリース時に一部のobsolateなAPIが削除されているので使っている場合は、注意が必要
nokogiri v1.6.7.2
https://github.com/sparklemotion/nokogiri/compare/v1.6.7.1...v1.6.7.2#files_bucket
libxml2のセキュリティアップデートへ追従
rubocopがもっさりするときは除外ディレクトリを見直してみるとよいかもしれない
前のエントリで書いたrubocopがもっさりする問題を調べてみると、どうやら走査する必要のないディレクトリのファイルまでチェックしてしまっていて非常に遅くなっているのが原因だったようだ。
なお、rubocopは**/*で終わる文字列のパターンをディレクトリを除外する設定と認識して先に除外する、ということをやっているため、ディレクトリを指定するか、他のパターンで指定するかどうか、というのはパフォーマンスにけっこうな影響があるようだ。
AllCops: Exclude: - 'public/**/*' - 'tmp/**/*' - 'log/**/*'
あたりは最初に自動生成したファイルには含まれていないが、railsプロジェクトであれば入れておくとよいようだ。
vendor/**/*だけは、Rubocopのdefaultの設定に含まれているので何もしなくても除外してくれる。tmp/**/*もdefaultでこの候補に入れてくれるとうれしいケースが多いんじゃないかと思いつつ、どう提案しようかな... (今回は、tmp以下に大量にファイルがあるが対象から除外されていないのが遅い原因だった。)
bundle update 2016-01-17
rubocop v0.36.0
- 大量にCopが追加されている関係でいくつかこけるようになる。
- 設定ファイルにも非互換な変更が含まれているので更新する必要がある。
- なんか最初の実行開始までがもっさりするようになった気がする。
https://github.com/bbatsov/rubocop/compare/v0.35.1...v0.36.0#files_bucket
.rubocop.ymlの修正
AllCops: TargetRubyVersion: 2.3
とか追加しておかないとキーワード引数とか使ったときにrubocopがエラーになるもよう
メッセージ通りだけど、Style/TrailingCommaがStyle/TrailingCommaInLiteralなどに名前がかわったので変更する必要があるみたい。
新しくひっかかったやつ
endの位置をifと揃える
a = if b else end
が
a = if b else end
みたいにifのところで揃えないといけなくなった。AutoCorrectで修正はしてくれないらしい。
if else if end endを if elsif endにする
if a else if b end end
if a elsif b end
上の方式の方が個人的には理解しやすいことが多いように感じるので好みだけどまぁあわせておく。
他にもいくつかひっかかったけど、手直しが必要なやつの多くは上の2パターンだった。
parser v2.3.0.1
- ruby2.3.0への追従がメイン ~HEREDOC対応が含まれている。
https://github.com/whitequark/parser/compare/v2.3.0.pre.6...v2.3.0.1
sass v3.4.21
- いくつかの細かいバグがなおったもよう。わりと細かいケースみたいなのでそんなに踏まなそうなバグではある。
guard-rubocopでsystem経由の呼出しを止めると快適になった
guard-rubocopの中身を見たらsystemで呼びだしていたので、直接Rubocop::CLIを呼びだして起動するようにしてみたら爆速になって快適になりました。
もっさりするのでテストのあとにrubocopを実行するようにしていたけど、これなら先に実行してもいいんじゃないかなーという速度感。
rubocop.ymlのsyntaxエラーとかの場合のエラーがうまくハンドリングできてないっぽいですが、通常の実行には(まだちょっとしか使ってないけど)問題なさそうです。
お試しは、Gemfileに
gem 'guard-rubocop', github: 'walf443/guard-rubocop', branch: 'do_not_system'
でどうぞ。
bundle update 2016-01-13
aasm v4.7.0
https://github.com/aasm/aasm/compare/v4.5.1...v4.7.0#files_bucket
新しくhookポイントが追加されている。 全イベントの前後やTransactionの前後にイベントが仕込めるようになったもよう。
カラム名が存在しないやつに設定してしまったときのエラーがNoMethodErrorではなくわかりやすいエラーになった。
tilt v2.0.2
https://github.com/rtomayko/tilt/compare/v2.0.1...v2.0.2#files_bucket
babel対応が入っているもよう
sidekiq v4.0.2
https://github.com/mperham/sidekiq//compare/v4.0.1...v4.0.2#files_bucket
テスト用のSidekiq::QueueというAPIが追加されたけど、ActiveJob経由から使うのであまり使わなそう。
spring v1.6.2
https://github.com/rails/spring/compare/v1.6.1...v1.6.2#files_bucket
bin/rakeなどでエラーになっていたのが治ったもよう